How does Spring solve the problem of circular dependency

Posted by sonnieboy on Sun, 02 Jan 2022 20:58:23 +0100

1, What is circular dependency

 Two or more objects refer to each other, i.e A rely on B,B rely on C,C Again dependent A

For example:

@service
public class A {  
    private B b;
    
    @Autowired
    public void setB(B b) {  
        this.b= b;  
    }  
}  

@service
public class B {  
    private A a;
    
	@Autowired
    public void setA(A a) {  
        this.a= a;  
    }  
}  

There are three ways of bean injection:
1. @ Autowired is marked on the field
2. @ Autowired is marked on the set method
3. Constructor injection (unable to solve the circular dependency problem)

Spring instantiates bean s through ApplicationContext GetBean () method. If the object to be obtained depends on another object, it will first create the current object, and then recursively call ApplicationContext GetBean () method to obtain the dependent object, and finally inject the obtained object into the current object. Here, we will take the above example of initializing A object as an example. First, spring tries to use ApplicationContext GetBean () method obtains the instance of A object. Since there is no A object instance in the spring container, it will create an A object, and then find that it depends on B object, so it will try to recursively pass ApplicationContext The getBean () method obtains the instance of B object, but there is no instance of B object in the spring container at this time, so it will create an instance of B object first.
At this time, both a object and B object have been created and saved in the Spring container, but the attribute B of a object and the attribute a of B object have not been set. After Spring created the B object earlier, Spring found that the B object depends on attribute a, so it will still try to call ApplicationContext recursively The getBean () method obtains the instance of object a, because there is already an instance of object a in Spring. Although it is only a semi-finished product (its property B has not been initialized), it is also a target bean, so it will return the instance of object A. at this time, the property a of object B is set, and then it is returned recursively by the ApplicationContext.getBean() method, That is, the instance of B object will be returned. At this time, the instance will be set to property B of a object. At this time, note that both attribute B of object a and attribute a of object B have set the instance of the target object.

As mentioned above, constructor injection cannot solve the circular dependency problem because it initializes the A object. Because it is constructor injection, it cannot initialize the A object of the semi-finished product and put it into the cache, and then initialize the B object. The B object cannot find the A object from the cache, so the program terminates.

2, Source code analysis

How does Spring mark that the A object to be generated is A semi-finished product, and how to save the A object. The marking work here is saved by Spring using the property Set singletonsCurrentlyInCreation of ApplicationContext, while the A object of semi-finished products is saved through map < string, ObjectFactory <? > > The ObjectFactory here is A factory object, which can be obtained by calling its getObject() method. In abstractbeanfactory The method of obtaining objects in dogetbean() method is as follows:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // Try to get the target bean object through the bean name, such as the A object here
  Object sharedInstance = getSingleton(beanName);
  
  // The target objects here are all singletons
  if (mbd.isSingleton()) {
    // Here, try to create the target object. The second parameter passes an object of ObjectFactory type. Here is lamada using java 8
    // The expression is written. As long as the return value of the above getSingleton() method is null, the getSingleton() method here will be called to create the
    // Target object
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // Attempt to create target object
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}

The doGetBean() method here is a very key method (other codes are omitted in the middle). There are two main steps above. The getSingleton() method in the first step is to try to obtain the target object from the cache. If it is not obtained, try to obtain the target object of the semi-finished product; If the first step does not get the instance of the target object, then go to the second step. The getSingleton() method of the second step is to try to create the target object and inject its dependent properties into the object.

spring mainly relies on three-level cache to solve circular dependency.

The L3 cache is:

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

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

	/** Cache of early singleton objects: bean name to bean instance. */
	//Cache of singleton object exposed in advance. [used to detect circular references, mutually exclusive with singletonFactories] 
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

When we create a bean, we first think of getting the singleton bean from the cache, which is singletonObjects. The main calling methods are:

@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Try to get the target object of the finished product from the cache. If it exists, it will be returned directly
		Object singletonObject = this.singletonObjects.get(beanName);
		 If the target object does not exist in the cache, judge whether the current object is already in the process of creation
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
			//Get from the Cache of the singleton object exposed in advance
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
				//Try to obtain from the singleton project. If the object is obtained, put it into earlySingletonObjects and remove it from singletonFactories
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

Here, we will have A problem: how to instantiate the semi-finished instance of A, and then how to encapsulate it as an ObjectFactory object and put it in the singletonFactories attribute above. This is mainly because in the previous second getSingleton() method, it will eventually call the createBean() method through the second parameter passed in. The final call of this method is entrusted to another doCreateBean() method. There is the following code:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  throws BeanCreationException {

  // Instantiate the bean object you are trying to get, such as A object and B object, which are instantiated here
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  
  // Judge whether Spring is configured to support exposing target beans in advance, that is, whether it supports exposing semi-finished beans in advance
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // If it is supported, the currently generated semi-finished bean s will be placed in singletonFactories
    // This is the singletonfactors attribute used in the first getSingleton() method, that is, here is
    // A place to encapsulate semi-finished beans. In essence, getEarlyBeanReference() here is the third parameter that will be directly put in, that is
    // The target bean returns directly
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  try {
    // After initializing the instance, this is to judge whether the current bean depends on other beans. If so,
    // The getBean() method will be called recursively to try to get the target bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    // Omit
  }
  
  return exposedObject;
}

So far, we have analyzed the circular dependency of the whole bean

Topics: Java Spring Back-end