How does Spring solve circular dependencies

1. What is a circular dependency?

A circular dependency is actually a circular reference, that is, two or more beans hold each other and eventually form a closed loop. For example, A depends on B, B depends on C, and C depends on A. As shown below:

write picture description here

Note that this is not a cyclic call of a function, but an interdependence of objects. A loop call is actually an infinite loop unless there is a termination condition.

The circular dependency scenarios in Spring include: 
(1) The circular dependency of the constructor 
(2) The circular dependency of the field property.

2. How to detect if there is a circular dependency

It is relatively easy to detect circular dependencies. The bean can be marked when it is created. If the recursive call comes back and finds that it is being created, it means a circular dependency.

3. How does Spring resolve circular dependencies

The theoretical basis of Spring's circular dependency is actually based on Java's reference passing. When we get a reference to an object, the field or property of the object can be set later (but the constructor must be before getting the reference).

The initialization of Spring's singleton object is mainly divided into three steps: 
bean initialization 
(1) createBeanInstance: instantiation, which is actually calling the object's constructor to instantiate the object

(2) populateBean: Populate properties, this step is mainly to fill in the dependency properties of multiple beans

(3) initializeBean: call the init method in spring xml.

From the singleton bean initialization steps described above, we can know that circular dependencies mainly occur in the first and second parts. That is, constructor circular dependencies and field circular dependencies.

Then we should start from the initialization process to solve the circular reference. For singletons, there is only one object in the entire life cycle of the Spring container, so it is easy to think that this object should be stored in the Cache. In order to solve the problem of singletons Circular dependency problem, using L3 cache .

First of all, let's look at the source code. The third-level cache mainly refers to:

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

These three-level caches refer to: 
singletonFactories: the cache of the singleton object factory 
earlySingletonObjects: the cache of the singleton object exposed in advance 
singletonObjects: the cache of the singleton object

When we create a bean, the first thing we think about is to get the singleton bean from the cache, which is singletonObjects. The main calling method is:

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 != NULL_OBJECT ? singletonObject : null);
}

The above code needs to interpret two parameters:

  • isSingletonCurrentlyInCreation() determines whether the current singleton bean is being created, that is, the initialization is not completed (for example, the constructor of A depends on the B object, so the B object must be created first, or in the process of A's populateBean depends on the B object, so Go to create the B object first, then A is in the state of being created.)
  • allowEarlyReference whether to allow getting objects from singletonFactories through getObject

Analyzing the whole process of getSingleton(), Spring first gets it from the first-level cache singletonObjects. If it cannot be obtained and the object is being created, it is obtained from the second level cache earlySingletonObjects. If it is still not available and singletonFactories are allowed to be obtained through getObject(), it is obtained from the third-level cache singletonFactory.getObject() (third-level cache), if obtained, then:

this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);

Removed from singletonFactories and put into earlySingletonObjects. In fact, it is moved from the third-level cache to the second-level cache.

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

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

This interface is referenced below

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);
        }
    }
}

Here is the key to solving the circular dependency. This code occurs after createBeanInstance, which means that the singleton object has been created at this time (the constructor is called). This object has been produced, although it is not perfect (the second and third steps of initialization have not been performed), but it can be recognized by people (the object in the heap can be located according to the object reference), so Spring At this time, this object is exposed in advance for everyone to know and use.

What's the benefit of doing this? Let's analyze the circular dependency situation of "a field or setter of A depends on the instance object of B, and a field or setter of B depends on the instance object of A". A first completed the first step of initialization, and exposed itself to singletonFactories in advance. At this time, the second step of initialization, and found that he was dependent on object B. At this time, he tried to get(B) and found that B had not been created. , so in the create process, B found himself dependent on object A when initializing the first step, so he tried get(A), tried the first-level cache singletonObjects (certainly not, because A has not been initialized completely), and tried the second-level cache earlySingletonObjects (No), try the three-level cache singletonFactories, since A exposes itself in advance through ObjectFactory, B can get the A object through ObjectFactory.getObject (although A has not been fully initialized, but it is better than nothing), B takes After reaching the A object, the initialization stages 1, 2, and 3 are successfully completed. After complete initialization, it puts itself into the first-level cache singletonObjects. At this time, returning to A, A can get the object of B and successfully complete its initialization stages 2 and 3. Finally, A also completes the initialization and enters the singletonObjects in the first-level cache. Fortunately, because B got A's object reference, so the A object that B now holds is initialized.

When you know this principle, you must know why Spring can't solve the problem of "A's construction method depends on B's instance object, and B's construction method depends on A's instance object"! Because the premise of adding singletonFactories L3 cache is to execute the constructor, the circular dependency of the constructor cannot be resolved.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324972068&siteId=291194637