How does spring solve circular dependency

First of all, it should be clear that spring handles loop dependencies in three ways:

① Constructor's loop dependency: spring cannot handle this dependency, and directly throws a beancurrentylncreationexception.
② setter cyclic dependency in singleton mode: handle cyclic dependency through "L3 cache".
③ Non singleton circular dependency: cannot process.

The initialization of spring singleton object is roughly divided into three steps:

Createbean instance: instantiation, that is, calling the constructor of the object to instantiate the object
populateBean: fill in attributes. This step is mainly to fill in the dependent attributes of multiple beans
initializeBean: call the init method in spring xml.

From the single instance bean initialization steps described above, we can know that circular dependency mainly occurs in the first and second steps. That is, constructor loop dependency and field loop dependency.

Next, let's look at how spring handles three kinds of cyclic dependencies.

1. Constructor loop dependency

this .singletonsCurrentlylnCreation.add(beanName) records the bean currently being created in the cache
The Spring container puts the identifier of each bean being created in a "currently created bean pool", and the bean identifier
Bai: it will remain in this pool during the creation process, so if you find yourself in the "current" state during the creation process of the bean
When creating a bean pool, a beancurrentylncreationexception will be thrown, indicating circular dependency; The created beans will be cleared from the "currently created bean pool".

2. setter cyclic dependency

In order to solve the circular dependency problem of singleton, Spring uses three-level cache.

/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);

The functions of the three-level cache are:
Singleton factories: caches of singleton object factories entering the instantiation phase (L3 cache)
earlySingletonObjects: Cache (L2 Cache) of singleton objects that are instantiated but not initialized and exposed in advance
singletonObjects: cache (L1 cache) of the initialized singleton objects
When creating a bean, we will first get the bean from the cache, which is signetonobjects. The main calling methods are:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //Issingletoncurrent yincreation() determines whether the current singleton bean is being created
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            //Does alloweerlyreference allow you to get objects from singletonFactories through getObject
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    //Remove from singletonFactories and place in earlySingletonObjects.
                    //In fact, it is moved from L3 cache to L2 cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

From the above analysis of the three-level cache, we can know that the trick of Spring to solve circular dependency lies in the three-level cache of singleton factories. The type of this cache is ObjectFactory, which is defined as follows:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

This interface is implemented in AbstractBeanFactory, and the following methods are referenced in the core method doCreateBean():

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

This code occurs after createBeanInstance and before populateBean (), that is, the singleton object has been created (the constructor has been called). This object has been produced, java training At this time, the object will be exposed in advance for everyone to use.

What are the benefits of doing so? Let's analyze the circular dependency that "a field or setter of a depends on the instance object of B, while a field or setter of B depends on the instance object of a". A first completes the first step of initialization and exposes itself to singletonFactories in advance. At this time, in the second step of initialization, it is found that it depends on object B. at this time, it tries to get(B). It is found that B has not been created, so it goes through the create process. When B finds that it depends on object a in the first step of initialization, it tries to get(A), Try the first level cache singletonObjects (definitely not, because a has not been fully initialized), the second level cache earlySingletonObjects (also not), and the third level cache singletonFactories. Because a exposes itself in advance through the ObjectFactory, B can pass through the ObjectFactory GetObject gets object a (although a has not been fully initialized, it is better than nothing). After getting object a, B successfully completes initialization stages 1, 2 and 3. After complete initialization, it puts itself into the first level cache singletonObjects. At this time, it returns to A. at this time, a can get the object of B and successfully complete its initialization stages 2 and 3. Finally, a also completes the initialization and goes into the level-1 cache singletonObjects. What's more, because B gets the object reference of a, the object of a that B now hold s has completed the initialization.

3. Non singleton cyclic dependence

For "prototype" scoped beans, the Spring container cannot complete dependency injection because the Spring container does not perform dependency injection
Beans with "prototype" scope cannot be exposed in advance.

Keywords: Spring

Added by MrXander on Fri, 07 Jan 2022 04:30:44 +0200