How does Spring resolve circular dependencies?

 

1. Demonstration of the process

Regarding the creation of a Spring bean, it is essentially the creation of an object. Since it is an object, readers must understand that a complete object consists of two parts: the instantiation of the current object and the instantiation of the object properties.

In Spring, the instantiation of an object is achieved through reflection, and the properties of an object are set in a certain way after the object is instantiated.

This process can be understood as follows:

 

 

After understanding this point, the understanding of circular dependencies has helped a big step. Here we take two classes A and B as examples to explain, the following are the declarations of A and B:

@Component
public class A {
  private B b;
  public void setB(B b) {
    this.b = b;
  }
}
@Component
public class B {
  private A a;
  public void setA(A a) {
    this.a = a;
  }
}

As you can see, here A and B each regard each other as its own global attribute. The first thing that needs to be explained here is that Spring instantiates the bean through the ApplicationContext.getBean() method.

If the object to be obtained depends on another object, it will first create the current object, then recursively call the ApplicationContext.getBean() method to obtain the dependent object, and finally inject the obtained object into the current object.

Here we take the above first initialization of the A object instance as an example to explain.

First, Spring tries to obtain an instance of the A object through the ApplicationContext.getBean() method. Since there is no instance of the A object in the Spring container, it will create an A object.

Then found that it depends on the B object, so it will try to recursively obtain the instance of the B object through the ApplicationContext.getBean() method

But there is no instance of B object in the Spring container at this time, so it will still create an instance of B object first.

Readers need to pay attention to this point in time. At this time, the A object and the B object have been created and stored in the Spring container, but the property b of the A object and the property a of the B object have not been set in yet.

After creating the B object in the previous Spring, Spring found that the B object depends on the property A, so it will still try to recursively call the ApplicationContext.getBean() method to obtain an instance of the A object

Because there is already an instance of the A object in Spring, although it is only a semi-finished product (its property b has not yet been initialized), it is still the target bean, so the instance of the A object will be returned.

At this point, the attribute a of the B object is set in, and then the ApplicationContext.getBean() method returns recursively, that is, the instance of the B object is returned, and then the instance is set to the attribute b of the A object.

At this time, notice that both the attribute b of the A object and the attribute a of the B object have already set the instance of the target object

Readers may be more puzzled that when setting the attribute a for the object B earlier, this type A attribute is still a semi-finished product. But it should be noted that this A is a reference, which is essentially the A object that was instantiated from the beginning.

At the end of the above recursive process, Spring sets the obtained B object instance to the attribute b of the A object

The A object here is actually the same object as the semi-finished A object previously set to the instance B, and its reference address is the same. Here, the value of the b attribute of the A object is set, which is actually the a attribute of the semi-finished product. value.

Below we explain this process through a flowchart:

 

 

In the figure, getBean() means calling Spring's ApplicationContext.getBean() method, and the parameters in this method indicate the target object we are trying to obtain.

The black arrow in the figure indicates the direction of the method call at the beginning. At the end, after returning the A object cached in Spring, it indicates that the recursive call has returned. At this time, it is indicated by the green arrow.

From the figure, we can clearly see that the a attribute of the B object is the semi-finished object A injected in the third step, and the b attribute of the A object is the finished B object injected in the second step. At this time, the semi-finished product is The A object becomes the finished A object because its properties have been set.

2. Source code explanation

Regarding the way Spring handles the circular dependency problem, we can actually understand it easily through the above flowchart.

One point that needs to be noted is how Spring marks the beginning of the generated A object as a semi-finished product, and how it saves the A object.

The marking work Spring here is saved using the ApplicationContext property SetsingletonsCurrentlyInCreation, while the semi-finished A object is saved through MapsingletonFactories

The ObjectFactory here is a factory object, and the target object can be obtained by calling its getObject() method. The method to get the object in the AbstractBeanFactory.doGetBean() method is as follows:

protected  T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  
  // 尝试通过bean名称获取目标bean对象,比如这里的A对象
  Object sharedInstance = getSingleton(beanName);
  // 我们这里的目标对象都是单例的
  if (mbd.isSingleton()) {
    
    // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
    // 目标对象
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // 尝试创建目标对象
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}

The doGetBean() method here is a very critical method (other code is omitted in the middle), and there are mainly two steps above

The function of the getSingleton() method in the first step is to try to obtain the target object from the cache. If not, then try to obtain the target object of the semi-finished product; if the instance of the target object is not obtained in the first step, then enter the first step. Two steps

The function of the getSingleton() method in the second step is to try to create a target object and inject the attributes it depends on.

This is actually the main logic, as we have indicated in the previous figure, the doGetBean() method will be called three times during the whole process.

The first time it is called, it will try to obtain an instance of the A object. At this time, the first getSingleton() method is taken. Since there is no finished or semi-finished product of the A object that has been created, the value obtained here is null

Then the second getSingleton() method is called to create an instance of the A object, and then the doGetBean() method is recursively called to try to obtain an instance of the B object to inject into the A object

At this time, because there is no finished or semi-finished product of the B object in the Spring container, it will still go to the second getSingleton() method, in which an instance of the B object is created

After the creation is complete, try to get the instance of A that it depends on as its attribute, so it will still call the doGetBean() method recursively

At this point, it should be noted that since there is already an instance of a semi-finished A object, at this time, when trying to obtain an instance of the A object, the first getSingleton() method will be used.

In this method, an instance of the semi-finished object A will be obtained, and then the instance will be returned and injected into the attribute a of the B object. At this time, the instantiation of the B object is completed.

Then, the instantiated B object is returned recursively. At this time, the instance is injected into the A object, so that a finished A object is obtained.

We can read the first getSingleton() method above:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  
  // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回
  Object singletonObject = this.singletonObjects.get(beanName);
  
  // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象
  // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        
        // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的
        // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品
        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 is how to instantiate the semi-finished instance of A, and then how to encapsulate it as an ObjectFactory type object and put it in the singletonFactories attribute above.

This is mainly in the previous second getSingleton() method, which will eventually call the createBean() method through the second parameter passed in. The final call of this method is delegated to another doCreateBean() method ongoing

There is the following piece of code:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  throws BeanCreationException {
  // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    
    // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories
    // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是
    // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是
    // 目标bean直接返回
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
  try {
    // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,
    // 就会递归的调用getBean()方法尝试获取目标bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    // 省略...
  }
  return exposedObject;
}

At this point, Spring's entire implementation of solving the circular dependency problem has been relatively clear. For the overall process, readers only need to understand two points:

  • Spring obtains the target bean and its dependent beans in a recursive manner;
  • When Spring instantiates a bean, it is carried out in two steps. First, the target bean is instantiated, and then properties are injected into it.

Combining these two points, that is to say, when Spring instantiates a bean, it first recursively instantiates all the beans it depends on, until a bean does not depend on other beans, then the instance will be returned, and then Anti-recursively set the acquired beans to the properties of each upper-layer bean.

Guess you like

Origin blog.csdn.net/qq_33762302/article/details/115196751