How does Spring resolve circular dependencies?

In interviews about Spring, we are often asked a question, how does Spring solve the circular dependency problem? It is no stranger to this question being asked in interviews, and it is a common high-frequency interview question. If you don’t read deeply, I believe that even if you read the source code, you may not be able to answer well. This article will show you how to analyze and explain it from the perspective of source code.

1. Process Demonstration Analysis

1. Regarding the creation of Spring beans, it is essentially the creation of an object. It is an object. Everyone will understand that a complete object includes two aspects: the instantiation of the current object and the instance of the object attribute ization (i.e. injecting properties).

In Spring, the bottom layer of object instantiation is created and instantiated through reflection proxy, and the properties of the object are set in a certain way after the object is instantiated.

This overall process can be understood as follows:

insert image description here
After understanding through the above diagram, I believe that everyone has a little understanding of the understanding of circular dependence. Here we take A and B as examples to explain, and 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 can be seen above, here A and B each use the other as their own dependency injection object. First of all, it needs to be explained here that the Spring instantiated bean is obtained through the ApplicationContext.getBean() method.

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

Here we explain by first initializing the A object instance above.

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

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

Here you should understand that the A and B object instances have been created and stored in the Spring container, but there is one thing to note here is that the attribute b of the A object and the attribute a of the B object have not been set (injected) yet. .

After Spring created the B object earlier, Spring found that the B object depends on the A object, so it will still try to recursively call the ApplicationContext.getBean() method to obtain the A object instance.

Because there is already an A object instance in Spring at this time, although it is only a semi-finished product (attribute b is initialized), it is still the target bean that needs to be obtained, so the target bean instance (semi-finished product) will be returned.

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

At this time, notice that the attribute b of the A object and the attribute a of the B object have been set to the target object instance.

I believe that everyone will be a little confused after the analysis. When the attribute a was set for the B object earlier, the A object was still a semi-finished product. But one thing to note is that this A object is a reference, which is essentially the A object that was instantiated at the beginning.

And through the above recursive process to the end, Spring sets the acquired B object instance to the property b of the A object.

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

Let's explain this process through a flowchart:
insert image description here
getBean() in the above figure means calling Spring's ApplicationContext.getBean() method, and the parameters of this method indicate the target object we are trying to obtain.

The blue arrow in the figure indicates the direction of the method call at the beginning. At the end, after the A object cached in Spring is returned, it means that the recursive call has returned, which is indicated by a green arrow.

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

2. Source code explanation

As for how Spring solves the problem of circular dependency, we can easily understand it through the flow chart above.

But here is a point to pay attention to, how Spring marks the A object generated at the beginning as a semi-finished product, and how to save the semi-finished A object.

The marking work here is stored by Spring using the attribute singletonsCurrentlyInCreation of ApplicationContext, while the semi-finished A object is stored through the attribute singletonFactories, as follows:

// 高速缓存正在创建bean(单列)
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

// 缓存bean对象到ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

The ObjectFactory here is a factory object, and the target object can be obtained by calling its getObject() method. The method of obtaining 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的lambda
     表达式书写的,只要上面的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 codes are 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 get the target object from the cache, if not, try to get the semi-finished target object; if the first step does not get the instance of the target object, then go to the first step Two steps.

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

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

When calling for the first time, it will try to obtain the A object instance. At this time, the first getSingleton() method is used. Since there is no finished or semi-finished product of the A object that has been created, the result here is null;

Then it will call the second getSingleton() method to create an instance of the A object, and then recursively call the doGetBean() method to try to get an instance of the B object to inject into the A object;

At this time, since 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 obtain the instance of A it depends on as its property, so the doGetBean() method will still be called recursively;

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

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

Then, recursively return the instantiated B object, and then inject the instance into the A object, thus obtaining a finished A object.

Here 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 product instance of A, and then how to encapsulate it into 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, and the final call of this method is delegated to another doCreateBean() method ongoing.

Here 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);
    exposedObject = initializeBean(beanName, exposedObject, mbd);
  } catch (Throwable ex) {
    
    
    // 省略...
  }
  return exposedObject;
}

At this point, the implementation idea of ​​Spring's entire solution to the circular dependency problem is relatively clear. For the whole process, you only need to understand two points:

  • Spring obtains the target bean and the beans it depends on recursively;
  • When Spring instantiates a bean, it proceeds in two steps, first instantiates the target bean, and then injects properties 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 bean as the attribute of each upper-level bean.

Guess you like

Origin blog.csdn.net/weixin_43322048/article/details/114083580
Recommended