How does Spring solve circular dependency? Do you really understand?

Keywords: Java Spring Attribute

Guide reading

  • This is a typical constructor dependency. Please refer to the above two articles for details, which will not be described in detail here. This article will deeply analyze how Spring solves circular dependency from the source code? Why can't we solve the cyclic dependency of constructors?

What is circular dependence

  • In short, A depends on B, B depends on C, and C depends on A, which constitutes A circular dependency.

  • Circular dependency can be divided into constructor dependency and attribute dependency. It is well known that Spring can solve the circular dependency (set injection) of attributes. The following will analyze how Spring solves the circular dependency of attributes from the source point of view.

thinking

  • How to solve the circular dependency? Spring's main idea is to call doGetBean when instantiating a and find the instance of B on which a depends. At this time, it calls doGetBean to go to instance B, and when instantiating B, it finds and depends on A. if this circular dependency is not solved, then the doGetBean at this time will cycle indefinitely, resulting in memory overflow and program crash. Spring refers to an early object, and injects the "early reference" into the container. Let B complete the instantiation first, and then a will get the reference of B and complete the instantiation.

Three level cache

  • Spring can easily solve the circular dependency of attributes by using the three-level cache, which has detailed comments in AbstractBeanFactory.
/**The first level cache is used to store the fully initialized beans. The beans taken from the cache can be used directly*/
 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 /**The three-level cache stores bean factory objects to solve circular dependency*/
 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 /**The second level cache holds the original bean object (properties have not been filled) to solve the circular dependency*/
 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  • Level 1 cache: singletonObjects, which is used to store the fully instantiated Bean.
  • Second level cache: earlySingletonObjects, which stores references of earlier beans and beans that have not been attribute assembled
  • Three level cache: Singleton factories, which stores the Bean factory after instantiation.

Open and close

  • Let's take a look at the previous flowchart to see how Spring solves circular dependency

  • The blue part of the above figure is related to the operation of level 3 cache. Let's analyze it one by one

[1] getSingleton(beanName): the source code is as follows:

//Query cache
  Object sharedInstance = getSingleton(beanName);
  //The cache exists and args yes null
  if (sharedInstance != null && args == null) {
   //.......Omit some codes
            
       //Direct access Bean Example
   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
  }
  
 //getSingleton Source code, DefaultSingletonBeanRegistry#getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     //First, get the instantiated property assignment from the first level cache Bean
  Object singletonObject = this.singletonObjects.get(beanName);
     //The first level cache does not exist, and Bean In the process of creating
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   synchronized (this.singletonObjects) {
                //Query from L2 cache, get Bean Early references to, instantiated but not assigned Bean
    singletonObject = this.earlySingletonObjects.get(beanName);
                //Does not exist in the L2 cache and allows creation of early references (added in the L2 cache)
    if (singletonObject == null && allowEarlyReference) {
                    //Query from level 3 cache, instantiation complete, attribute not assembled complete
     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
     if (singletonFactory != null) {
      singletonObject = singletonFactory.getObject();
                         //Add in L2 cache
      this.earlySingletonObjects.put(beanName, singletonObject);
                        //Remove from L3 cache
      this.singletonFactories.remove(beanName);
     }
    }
   }
  }
  return singletonObject;
 }
  • It can be seen from the source code that doGetBean is initially a query cache, with all queries in the first, second and third level cache. If the third level cache exists, the Bean's early references will be stored in the second level cache and the third level cache will be removed. (upgrade to L2 cache)

[2] Addsingleton factory: the source code is as follows

//Omit some code in the middle.....
  //Establish Bean Source code of AbstractAutowireCapableBeanFactory#In the doCreateBean method
  if (instanceWrapper == null) {
            //instantiation Bean
   instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  //Allow early exposure
  if (earlySingletonExposure) {
            //Add to L3 cache
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
  try {
            //Property assembly. When assigning a property, if another property is found to refer to another Bean,Then call getBean Method
   populateBean(beanName, mbd, instanceWrapper);
            //Initialization Bean,call init-method,afterproperties Methods and other operations
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
  }

//The source code added to the level 3 cache is DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  synchronized (this.singletonObjects) {
            //Level 1 cache does not exist
   if (!this.singletonObjects.containsKey(beanName)) {
                //Put into Level 3 cache
    this.singletonFactories.put(beanName, singletonFactory);
                //Remove from L2 cache,
    this.earlySingletonObjects.remove(beanName);
    this.registeredSingletons.add(beanName);
   }
  }
 }
  • According to the source code, after the Bean is instantiated, it will directly store the unassembled Bean factory in "level 3 cache" and "remove Level 2 cache"

[3] addSingleton: the source code is as follows:

//The method of getting singleton object, DefaultSingletonBeanRegistry#getSingleton
//call createBean instantiation Bean
singletonObject = singletonFactory.getObject();

//. . . . Code omitted in the middle 

//doCreateBean After that, we call, instantiate and assign the property Bean Load the first level cache, which can be used directly Bean
addSingleton(beanName, singletonObject);

//addSingleton Source code, in DefaultSingletonBeanRegistry#In addSingleton method
protected void addSingleton(String beanName, Object singletonObject) {
  synchronized (this.singletonObjects) {
            //Add in L1 cache
   this.singletonObjects.put(beanName, singletonObject);
            //Remove L3 cache
   this.singletonFactories.remove(beanName);
            //Remove L2 cache
   this.earlySingletonObjects.remove(beanName);
   this.registeredSingletons.add(beanName);
  }
 }
 
  • In a word, beans are added to the first level cache and removed from the second and third level cache.

extend

[1] Why can't Spring solve the loop dependency of constructors?

  • It should not be hard to see from the flow chart that before the Bean calls the constructor for instantiation, the level-1, level-2 and level-3 cache does not have any information about the Bean, and it is put into the level-3 cache after instantiation. Therefore, when getBean is used, the cache fails to hit, thus throwing the exception of circular dependency.

[2] Why can't multi instance beans solve circular dependency?

  • Multiple instance Bean calls the doGetBean method every time it is created. It does not use the level-1, level-2, and level-3 cache at all. It certainly cannot solve the circular dependency.

summary

  • Based on the above analysis, it is probably clear how Spring solves circular dependency. Suppose that A depends on B, and B depends on A (Note: here is set attribute dependency). The steps are as follows:
  1. A executes "doGetBean", query cache, and "createBean" to create an instance in turn. After instantiation, the instance is put into the singletonfactors of the three-level cache. Then, the "populateBean" method is executed to assemble the attributes. However, it is found that there is an object whose attribute is B.
  2. Therefore, call the doGetBean method again to create an instance of B, execute doGetBean, query cache and createBean to create the instance in turn, and then put it into the three-level cache singletonfactors after instantiation, and execute the populateBean assembly attribute, but at this time, it is found that there is an attribute of A object.
  3. Therefore, call doGetBean again to create an instance of a, but when executing the getSingleton query cache, the instance of a is queried from the three-level cache (early reference, Incomplete Attribute assembly). At this time, it directly returns to A. without executing the subsequent process to create a, then B completes the attribute assembly. At this time, a complete object is put into the singletonObjects of the first level cache.
  4. When the creation of B is completed, A naturally completes the attribute assembly and puts it into the first level cache singletonObjects.
  • The application of Spring three-level cache perfectly solves the problem of circular dependency. The following is the flow chart of circular dependency solution.

Posted by MHardeman25 on Tue, 07 Apr 2020 05:54:02 -0700