A cyclic dependency is a loop of nested references in N classes, and if this happens in everyday development with a new object, the program will continue to be called at runtime until an error is reported. Here’s how Spring solves it.

First, you need to clear is the spring’s handling of circular dependencies have three conditions: (1) the constructor of the circular dependencies: this dependence spring is processing, direct selling BeanCurrentlylnCreationException anomalies. ② Setter loop dependencies in singleton mode: loop dependencies are handled by “three-level caching”. ③ Non-singleton loop dependency: cannot be handled.

The initialization of a Spring singleton is roughly divided into three steps:

  1. CreateBeanInstance: instantiates an object by calling its constructor
  2. PopulateBean: Populates properties. This step mainly populates the dependent properties of multiple beans
  3. InitializeBean: Calls the init method in Spring XML.

As we can see from the singleton bean initialization steps described above, loop dependencies occur primarily in the first and second steps. That is, constructor loop dependencies and field loop dependencies. Next, let’s look specifically at how Spring handles three kinds of loop dependencies.

Constructor loop dependencies

This. SingletonsCurrentlylnCreation. Add (beanName) record the current bean was created in the cache the Spring container will each and every one is creating bean identifier in a current to create large pools of bean, Bean id: parker will remain in the creation process in the pool, so if in the process of creating bean has found itself in the “current to create large pools of bean”, will throw BeanCurrentlylnCreationException abnormal said circular dependencies; Beans that have been created are cleared from the currently created bean pool.

2. Setter loop dependencies

Spring uses level 3 caching to solve the problem of loop dependency for singletons.

/** 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);Copy the code

The functions of these three levels of cache are as follows:

SingletonFactories: the cache (level 3 cache) of singleton object factories entering the instantiation phase

EarlySingletonObjects: Cache of pre-exposed singleton objects that have been instantiated but not initialized (level 2 Cache)

SingletonObjects: a cache of initialized singletonObjects (level 1 cache)

When we create a bean, we first fetch the bean from the cache, which is called sigletonObjects. The main call method is:

protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); / / isSingletonCurrentlyInCreation () to judge the current singleton bean is createdif(singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); AllowEarlyReference allows getObject to fetch objects from singletonFactoriesif(singletonObject == null && allowEarlyReference) { ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName);if(singletonFactory ! = null) { singletonObject = singletonFactory.getObject(); // Remove it from singletonFactories and put it into earlySingletonObjects. / / it is from the l3 cache moved to level 2 cache enclosing earlySingletonObjects. Put (beanName singletonObject); this.singletonFactories.remove(beanName); }}}}return(singletonObject ! = NULL_OBJECT ? singletonObject : null); }Copy the code

Spring uses the singletonFactories cache to solve this problem. This cache is of type ObjectFactory and is defined as follows:

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

AbstractBeanFactory this interface is implemented in AbstractBeanFactory and references the following methods 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); }}}Copy the code

This code occurs after createBeanInstance and before populateBean (), which means that the singleton object has already been created (the constructor has been called). The object has already been produced, so expose the object in advance for everyone to use.

What’s the good of that? Let’s look at the circular dependency case where A field or setter of A depends on an instance object of B and A field or setter of B depends on an instance object of A. After A completes the first initialization and exposes itself to singletonFactories, A tries to get(B) and finds that B has not yet been created. B finds that it depends on object A when it initializes the first step, so it tries get(A), singletonObjects(definitely not, because A is not fully initialized), earlySingletonObjects (also not), Try singletonFactories at level 3. Because ObjectFactory exposes itself, ObjectFactory. GetObject gets ObjectFactory (although A is not fully initialized, it’s better than nothing). B successfully completed the initialization stages 1, 2 and 3 after getting the object A, and put itself into the level-1 cache singletonObjects after complete initialization. At this point, A is returned to A, and the object that A can hold B has successfully completed its initialization phase 2 and 3. Finally, A has also completed its initialization and is included in the level 1 cache of singletonObjects. Fortunately, since B has obtained the object reference of A, the object that B now holds A has completed its initialization.

3. Non-singleton loop dependencies

For “Prototype-scoped beans, the Spring container cannot do dependency injection because it does not cache” prototype-scoped beans and therefore cannot expose a bean being created in advance.