How to talk about Spring circular dependencies through code debugging?

I have been asked many, many times why Spring circular dependencies use third-level caching to solve this interview question, and it is difficult to explain clearly. I think that after reading the Spring source code, I still need to record Spring’s design ideas for solving circular dependencies and share them with technology enthusiasts.

 After being asked in the interview for the first time, I read many, many articles and got some inspiration, but few of them were based on actual case studies, which made it easy for me to forget, and I had to explain the truth clearly to the interviewer during the interview. It’s difficult. Later I made up my mind to thoroughly understand circular dependencies based on actual examples. Today I will use actual examples to learn how Spring handles circular dependencies while debugging.

illustrate

Students reading this article need to have an understanding of Spring ioc and di processes, as well as Spring bean creation and bean attribute filling.

Recall getting beans from Spring container

First, we can find Spring's method of obtaining beans, which will be obtained 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 are 3 caches needed here? Why not 2? Not even one? 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;
}
复制代码

Simulate manual injection of circular dependencies into each other

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 in A.

A a = new A();

B b = new B();

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

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

复制代码

Debugging Spring circular dependency injection processing flow

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 attribute b of object a

 When a injects b, the cache registers the factory class instead of the b instance.

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

 The b object begins 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 early exposed cache earlySingletonObjects (second-level cache). At this time, a has not completed injecting b, and it is still a semi-finished product, so a is still put into a Intermediate cache. 

Step 4: Inject b object into a object

 At this time, the injection of B is completed, but the injection of A has not yet been completed. B inside A inside B is not assigned a value

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

Step 6: Inject the b attribute of the 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 summarize, why use level 3 cache?

Next time I’m asked about circular dependencies in an interview, how will I explain it to the interviewer?

1. Spring uses three cache maps to solve the problem of circular dependencies, namely singletonObjects, singletonFactories, and earlySingletonObjects.

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

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

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

You need to wait for a to inject b, and then the cache will be removed.

5. SingletonFactories and earlySingletonObjects are both intermediate states of stored objects. They are relied upon to ensure that the final injected object is complete and will be cleared after dependency injection is completed.

Guess you like

Origin blog.csdn.net/2301_76607156/article/details/130557749
Recommended