Regarding the high-frequency interview question of Spring circular dependence, there are many wrong views on the Internet

Hello, my name is yes.

Today we will discuss Spring's classic interview questions about circular dependencies.

This interview question is very classic, and there are many corresponding articles on the Internet, but I still want to write it, because the views of some articles are wrong in my opinion.

  • For example, there is no way to resolve circular dependencies with constructors?
  • Must L3 cache to resolve circular dependencies?
  • Why in the end is L3 cache?

Well, not much to say, let's have a plate.

what is circular dependency

It's very simple, just look at the code below

@Service
public class A {
    
    
    @Autowired
    private B b;
}

@Service
public class B {
    
    
    @Autowired
    private A a;
}

//或者自己依赖自己
@Service
public class A {
    
    
    @Autowired
    private A a;
}

The above two methods are circular dependencies, which should be well understood. Of course, three beans or even more beans can depend on each other. The principle is the same. Today, we mainly analyze the dependencies of two beans.

This kind of circular dependency may cause problems. For example, A needs to depend on B, and it is found that B has not been created, so it starts to create B. During the creation process, it is found that B depends on A, but A has not been created yet, because it has to wait for B to be created. , so they both put this card bug .

How Spring resolves circular dependencies

The above circular dependency will appear in actual scenarios, so Spring needs to solve this problem, so how to solve it?

The key is to expose incompletely created beans in advance .

In Spring, the problem of circular dependencies can only be solved by satisfying the following two points:

  1. Dependent beans must be singletons
  2. The method of dependency injection must not be all constructor injection, and the beanName alphabetical order cannot be constructor injection

Why does it have to be a singleton

If you look at the source code, the circularly dependent Bean is the prototype mode, and it will throw an error directly:


So Spring only supports singleton circular dependencies, but why ?

According to understanding, if both beans are in prototype mode, then to create A1, you need to create a B1, when you create B1, you need to create an A2, when you create A2, you need to create a B2, when you create B2, you need to create an A3, and when you create A3, you need to create Create a B3…

It's a bug again, right, because the prototype mode needs to create a new object, and the previous object cannot be used.

If it is a singleton, creating A requires creating B, and creating B requires the previous A, otherwise it would not be called a singleton, right?

Based on this, Spring can operate.

The specific method is: first create A, at this time A is incomplete (without injecting B), use a map to save this incomplete A, and then create B, B needs A, so get "incomplete" from that map A, B is complete at this time, then A can inject B, then A is complete, and B is complete, and they are interdependent.

It seems a bit confusing to read, but the logic is actually very clear.

Why can't it be all constructor injection

There are three steps to creating a bean in Spring:

  1. Instantiation, createBeanInstance, is a new object
  2. Attribute injection, populateBean, is to set some attribute values
  3. Initialize, initializeBean, execute some methods in the aware interface, initMethod, AOP proxy, etc.

After clarifying the above three points, combined with the "incomplete" I said above, let's take a look.

If it is all constructor injection, for example A(B b), it means that when new, you need to get B, and you need new B at this time, but B also needs to inject A at the time of construction, that is B(A a), at this time, B needs to find a map in a map. Complete A, found not found.

Why can't I find it? Because A is not finished yet, an incomplete A is found, so if it is all constructor injection, Spring cannot handle circular dependencies .

A set injection, a constructor injection must be successful?

Suppose we inject B into A through set, and B injects into A through the constructor, which is successful at this time .

Let's analyze: after instantiating A, you can store A in the map at this time, start attribute injection for A, find that B is needed, at this time new B, find that the constructor needs A, then get A from the map, After B is constructed, B performs property injection, initialization, and then A injects B to complete property injection, and then initializes A.

The whole process went smoothly without any problems.

Suppose that A injects B through the constructor, and B injects A through the set, which fails .

Let's analyze: instantiate A and find that the constructor needs B. At this time, instantiate B, and then perform attribute injection of B. A can not be found from the map, because A has not yet succeeded in new, so B is also stuck. , and then just gg.

Seeing this, a careful thinking partner may say that you can instantiate B first, and insert an incomplete B into the map, so that A can be successfully instantiated.

Indeed, the idea is correct, but the Spring container creates beans in alphabetical order, and the creation of A always precedes the creation of B.

Now let's summarize:

  • Fails if circular dependencies are all constructor injection
  • If the circular dependency is not completely constructor injection, it may succeed or fail, depending on the alphabetical order of BeanName.

Spring solves the whole process of circular dependencies

After the above foreshadowing, I think you should have a little feeling about how Spring solves circular dependencies. Next, let's take a look at how it is implemented.

After clarifying the three steps for Spring to create beans, let's take a look at the three maps it does for singletons:

  1. The first level cache, singletonObjects, stores all created singleton beans (complete beans)
  2. The second-level cache, earlySingletonObjects, stores all beans that have only been instantiated, but have not yet been injected and initialized
  3. The third-level cache, singletonFactories, stores a factory that can build the bean, and the bean can be obtained through the factory, delaying the generation of the bean, and the bean generated by the factory will be stuffed into the second-level cache

How do these three maps work together?

  1. First, when obtaining a singleton bean, it will first go to singletonObjects (first-level cache) to find the complete bean through BeanName, and return directly if found, otherwise go to step 2.
  2. See if the corresponding bean is being created. If it is not returned directly, it cannot be found. If it is, it will go to earlySingletonObjects (second-level cache) to find the bean. If found, return it, otherwise go to step 3
  3. Go to singletonFactories (level 3 cache) to find the corresponding factory through BeanName. If there is a factory, create a Bean through the factory and place it in earlySingletonObjects.
  4. Returns null if none of the three caches are found.

From the above steps, we can know that if the query finds that the bean has not been created, it will directly return null in the second step, and will not continue to check the secondary and tertiary caches.

After returning null, it means that the Bean has not been created yet. At this time, it will mark that the Bean is being created, and then call createBean to create the Bean, but the actual creation is to call the method doCreateBean.

The doCreateBean method will perform the three steps we mentioned above:

  1. instantiate
  2. property injection
  3. initialization

After instantiating the Bean, a factory is inserted into the singletonFactories, and the Bean can be obtained by calling the getObject method of the factory .

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

It should be noted that at this time, Spring does not know whether there will be circular dependencies, but it doesn't care . Anyway, plug this factory into singletonFactories, which is exposed in advance .

Then it starts to perform attribute injection. At this time, A finds that B needs to be injected, so it goes to getBean(B). At this time, it will go through the logic described above again to the step of B's ​​attribute injection.

At this time, B calls getBean(A). At this time, it cannot be found in the first-level cache, but it is found that A is being created, so it goes to the second-level cache to find it, but it is not found, so it goes to the third-level cache to find it, and then it is found.

​And get A through the factory exposed in the third-level cache in advance, then delete the factory from the third-level cache, and add A to the second-level cache.

Then the result is that the B property is injected successfully.

Then B calls initializeBean to initialize, and finally returns, at this time, B has been added to the first-level cache.

At this time, it returns to the property injection of A. At this time, B is injected, and then initialization is performed. Finally, A will also be added to the first-level cache​, and A will be deleted from the second-level cache.

Spring solves the dependency cycle according to the logic described above.

The point is that after the object is instantiated, a factory will be added to the third-level cache to expose the incomplete bean in advance, so that if it is cyclically dependent, the other party can use this factory to get an incomplete bean and destroy the cycle. conditions of.

Why does circular dependency need L3 cache, is L2 not enough?

So much has been said above, so let's think about it, do we need a third-level cache to solve circular dependencies?

Obviously, if only to break the circular dependency, the second level cache is enough, and the third level is not necessary at all.

Think about it, after instantiating Bean A, I insert this A into the secondary map, then continue attribute injection, and find that A depends on B, so I want to create Bean B, then B can get A from the secondary map, complete After the establishment of B, A can naturally be completed.

So why do you want to build a third-level cache, and there is a factory for creating beans ?

Let's take a look at what the call to the factory's getObject does, which actually calls the following method:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    
    
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    
    
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    
    
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

The focus is on the judgment in the middle. If it is false, the return is the bean passed in by the parameter, and there is no change.

If it is true, it means that there are InstantiationAwareBeanPostProcessors, and the circular smartInstantiationAware type, if there is this BeanPostProcessor, it means that the Bean needs to be proxied by aop .

We all know that if there is a proxy, then what we want to get directly is the proxy object, that is to say, if A needs to be proxied, then the A that B depends on is A that has been proxied, so we cannot return A to B, and is the A to B that returns the proxy.

The role of this factory is to determine whether the object needs a proxy, if not, return it directly, and if so, return the proxy object.

When you see this, you will definitely ask, what does it have to do with the third-level cache? I can judge whether this bean needs a proxy when it is placed in the second-level cache. If I want to put the proxy object directly, it will be over. .

Yes, there seems to be no problem with this idea, the problem lies in the timing , which is related to the life cycle of the bean.

The generation of normal proxy objects is based on post-processors, which are generated after the initialization of the proxied object , so if you proxy early, it actually violates the life cycle of the bean definition .

So Spring first places a factory in a L3 cache. If there is a circular dependency, then the factory is called to get the proxy object in advance. If there is no dependency, the factory will not be called at all, so the life cycle of the bean is correct.

At this point, I think you should understand why there is a L3 cache.

I also understand that in fact, to destroy the circular dependency, only the second-level cache is enough, but due to the problem of the life cycle, the factory is exposed in advance to delay the generation of the proxy object.

By the way, don't worry about the problem of data accumulation in the third-level cache because there is no circular dependency. In the end, the singleton bean will be added to the first-level cache after it is created. At this time, the second and third-level caches below will be cleaned up.

At last

Well, seeing this, you must be very clear about Spring's circular dependencies, and it will certainly not be difficult for you during the interview.

Let me summarize a bit:

  • Constructor injection does not necessarily cause problems, depending on whether it is the alphabetical order of constructor annotation and BeanName
  • If you just want to break the circular dependency, you don't need three levels of cache, and two levels are enough.
  • Whether the L3 cache is a delayed proxy creation, try not to break the Bean life cycle

I have already written more interview questions about Spring, and I should finish writing them in two days. Wait for my article!

I'm yes, from a little to a million, see you in the next part~

Welcome to click the card below to follow me~

Guess you like

Origin blog.csdn.net/yessimida/article/details/123311432