The interview asked about Spring circular dependencies? Let you remember through code debugging today

Why does Spring circular dependency use three-level cache to solve this interview question? I have been asked many, many times, and it is difficult to explain clearly. I think that after reading the Spring source code, it is still necessary to record Spring's design ideas for solving circular dependencies and share it with technology enthusiasts.

 After I was asked in the interview for the first time, I read many articles and got some inspiration, but few of them were explained based on actual case articles, which made it easy for me to forget, and I had to explain the truth to the interviewer during the interview. It's hard. Later, I made up my mind to thoroughly figure out the circular dependency based on the actual example. Today I will use the actual example to learn how Spring's circular dependency is handled while debugging.

illustrate

Students who read this article need to have an understanding of Spring ioc and di processes, know Spring bean creation and bean property filling.

Recall to get beans from Spring container

First, we can find out how Spring gets beans, which it gets from three caches.

    //一级缓存,存储可以直接使用的bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //二级缓存 存储不完整对象 不能直接用的 循环依赖时,某些依赖的属性没有设置,这个时候bean就是不完整对象
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    //三级缓冲 存储bean工厂 可以延迟调用实例化 使用的时候才调用工厂方法获取对象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //从Spring容器获取bean对象方法
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //一级缓存
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
            //二级缓存
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                    //三级缓存
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
复制代码

Why do we need 3 caches here? Why not 2? Not even 1? Let's start to see why?

Simulate circular dependency scenarios

Classes A and B have each other's class attributes

@Service
public class A {
    
    private B b;
}
复制代码
@Service
public class B {

    private A a;
}
复制代码

Mock cyclic dependencies with mutual manual injection

Object a depends on object b, and object b depends on object a. The attribute injection process is to instantiate B first, inject A, and then fill A

A a = new A();

B b = new B();

b.setA(a); //B里面注入不完整的A了

a.setB(b); //注入依赖的对象,A里面有B了,a注入完成变成完整的a

复制代码

Debug Spring cyclic dependency injection processing flow

For the same scenario, see how Spring handles it.

The first step is to create an object a of class A

 In Spring, after class A is instantiated, a factory is cached instead of instance A, why?

The second step starts to inject the property b of the a object

 When a is injected into b, the cache registers the factory class instead of the b instance

In the third step, the b object starts to inject the attribute a object again

 The b object starts to inject the a object again. At this time, a only has the corresponding factory.

The proxy object of a is returned through the A factory, and the proxy object is put into the cache earlySingletonObjects (secondary cache) exposed earlier. At this time, a has not injected b into it, and it is still a semi-finished product, so a is still put into a intermediate cache. 

The fourth step is to inject the b object into the a object

 At this time, the injection of B is completed, and the injection of A has not yet been completed. B in A in B is not assigned

The fifth step is to inject the complete B object, (actually it is not complete yet)

Step 6 b attribute injection of a object

 Finally, inject the B attribute of the A object. At this time, the A object is complete, so the B object is also complete (to make up for the previous step)

complete a object graph

complete b object graph

To sum up, why use L3 cache?

If I am asked about circular dependencies in the next interview, how will I explain it to the interviewer?

1. Spring solves the problem of circular dependency through three cache maps, namely singletonObjects, singletonFactories, earlySingletonObjects

2. The singletonObjects cache stores complete objects and can be used directly.

3. The singletonFactories cache is for delayed initialization and is the key to solving circular dependencies. When there is circular dependency injection, the objects that may be injected need to be proxied. This factory class instantiates a proxy class.

4. The earlySingletonObjects cache is to inject b into a, and b is injected into a. A is a semi-finished product, which needs to be used to store the state of a semi-finished bean.

After you need to wait for a to inject b, the cache will be removed.

5. Both singletonFactories and earlySingletonObjects are intermediate states for storing objects, relying on them to ensure that the final injected object is complete, and will be cleared after the dependency injection is completed.

Guess you like

Origin blog.csdn.net/ww2651071028/article/details/130553071