头图

Bean loading (3)

The previous article mainly explained the acquisition of objects from bean instances, the preparation process and the preprocessing of instantiation. Instantiating a bean is a very complicated process. This article mainly explains how Spring solves circular dependencies.

what is circular dependency

A circular dependency is a circular reference. In fact, two or more beans hold each other. For example, A refers to B, B refers to C, and C refers to A, which eventually becomes a ring.

循环依赖

Circular dependencies cannot be resolved, unless there is a terminal condition, otherwise it is an infinite loop until the memory overflows.

When can circular dependencies be handled

There are preconditions for solving circular dependencies in Spring:

  • A bean with a circular dependency must be a singleton, if it is a prototype, it will not appear
  • The way of dependency injection cannot be all injected by the constructor, only the case of pure setter injection can be solved
Dependency Dependency injection method can it be solved
A and B depend on each other Both use setter injection Can
A and B depend on each other Both use attribute automatic injection Can
A and B depend on each other Constructor injection Can't
A and B depend on each other A is injected as a setter, and B is a constructor Can
A and B depend on each other A is injected as a constructor, and B is a setter. Spring will sort it according to the natural order during the creation process, and A is created before B. Can't

How Spring resolves circular dependencies

First of all, Spring container circular dependencies include constructor circular dependencies and setter circular dependencies . Before understanding how Spring solves circular dependencies, let's create these classes first.

 public class TestA {
   
   private TestB testB;
  
   public TestA(TestB testB) {
         this.testB = testB;
     }
   
   public void a(){
      testB.b();
   }

   public TestB getTestB() {
      return testB;
   }
   
   public void setTestB(TestB testB){
      this.testB = testB;
   }
   
}
 public class TestB {

   private TestC testC;
  
   public TestB(TestC testC) {
          this.testC = testC;
     }

   public void b(){
      testC.c();
   }

   public TestC getTestC() {
      return testC;
   }

   public void setTestC(TestC testC) {
      this.testC = testC;
   }

}
 public class TestC {

   private TestA testA;
  
   public TestC(TestA testA) {
          this.testA = testA;
     }

   public void c() {
      testA.a();
   }

   public TestA getTestA() {
      return testA;
   }

   public void setTestA(TestA testA) {
      this.testA = testA;
   }
}

Constructor Circular Dependency

Constructor circular dependency indicates a circular dependency caused by constructor injection. It should be noted that this situation cannot be resolved, and an BeanCurrentlyInCreationException exception will be thrown.

The Spring container will place each bean identifier being created in a "currently created bean pool", and the bean identifier will remain in this pool during the creation process. If you find yourself in the "currently created bean pool" during the process of creating a bean, the above exception will be thrown to indicate a circular dependency. When the bean is created, it will be removed from the "currently created bean pool".

  1. Create configuration file
 <bean id="a" class="cn.jack.TestA">
   <constructor-arg index="0" ref="b"/>
</bean>
<bean id="b" class="cn.jack.TestB">
   <constructor-arg index="0" ref="c"/>
</bean>
<bean id="c" class="cn.jack.TestC">
   <constructor-arg index="0" ref="a"/>
</bean>
  1. Create test cases
 public class Test {
    public static void main(String[] args) {
         new ClassPathXmlApplicationContext("spring-config.xml");
    }
}
  1. operation result
 Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-config.xml]: Cannot resolve reference to bean 'c' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'c' defined in class path resource [spring-config.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

If we understand the situation just described, we can easily think that when creating the TestC object, we need to prepare its construction parameter TestA. At this time, the Spring container is going to create TestA, but it is found that the bean identifier is already in the "currently created bean pool". ", so the above exception is thrown.

setter circular dependency

The dependency caused by setter injection is accomplished by the Spring container exposing in advance the beans that have just completed the constructor injection but have not completed other steps (such as setter injection) , and can only resolve the bean circular dependency under the singleton scope. By exposing a singleton factory method ahead of time, other beans can reference the bean.

According to our code case flow is as follows:

  • The Spring container creates a singleton TestA, then calls the no-argument constructor to create a bean, and exposes the ObjectFactory, which returns a bean that is exposed in advance, and puts the testA identifier in the "currently created bean pool", and then setster Inject TestB
  • The Spring container creates a singleton TestB, then calls the no-argument constructor to create a bean, and exposes the ObjectFactory, which returns a bean that is exposed in advance, and puts the testB identifier in the "currently created bean pool", and then setster Inject TestC
  • The Spring container creates a singleton TestC, then calls the no-argument constructor to create a bean, and exposes the ObjectFactory to return a bean that is exposed in advance, and puts the testC identifier in the "currently created bean pool", and then setster Inject TestA, since the ObjectFactory has been exposed earlier, and use it to return a created bean exposed earlier. The rest are the same.

Prototype-scoped dependency handling

scope="prototype" means that each request will create an instance object.

The difference between the two is: stateful beans use Prototype scope, and stateless beans generally use singleton singleton scope.

For "prototype" scoped beans, the Spring container cannot complete dependency injection, because the "prototype" scoped beans are not cached by the Spring container, so a newly created bean cannot be exposed in advance. So the above exception will still be thrown.

Process analysis

Here is an example where TestA depends on TestB, and TestB depends on TestA.

循环依赖流程图解

In a scenario where TestA and TestB are circularly dependent:

TestB populatedBean looking for the dependency TestA, although TestA was not obtained from the first-level cache, it was found that TestA was being created.

At this point, get A's singletonFactory from the L3 cache and call the factory method to create getEarlyBeanReference an early reference to TestA and return it.

Can L2 Cache Resolve Circular Dependencies?

We know that during the instantiation process, the addresses of the objects in the semi-finished product are placed in the cache, and the objects are exposed in advance. Put it in the L1 cache, delete the L2 and L3 caches.

If the second level cache is not used, there will be semi-finished and finished objects in the first-level cache. When acquiring, the semi-finished objects may be obtained and cannot be used.

If you don't need the third-level cache, if you don't use AOP, you only need the first-level and second-level caches to solve Spring's circular dependencies; but if you use AOP for enhancements, you must use the third-level cache, because when you get the third-level cache During the caching process, the non-proxy object will be replaced by the proxy object. If there is no L3 cache, then the proxy object cannot be obtained.

The third-level cache is to solve the circular dependency problem generated in the AOP proxy process.

After we add aop-related aspect operations to the code, the changes are generated in the initializeBean method, and the applyBeanPostProcessorsBeforeInitialization method is called.

调用applyBeanPostProcessorsBeforeInitialization方法

applyBeanPostProcessorsBeforeInitialization

There is a processor in getBeanPostProcessors: AnnotationAwareAspectJAutoProxyCreator is actually the added annotation aspect, and then the call will jump to AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法 .

In the following code, the wrapIfNecessary method will determine whether the proxy condition is met, and if so, it will return a proxy object, otherwise it will return the current Bean.

postProcessAfterInitialization

Finally TestA is replaced with a proxy object. What is returned by doCreateBean and placed in the first level cache are proxy objects.

If both TestA and TestB use AOP dynamic proxy , the previous series of processes are no different from normal ones. The only difference is that when TestB is created, TestA needs to be obtained from the L3 cache.

At this point, the getSingleton method will be called: singletonObject = singletonFactory.getObject();

getSingleton

具体实现

getEarlyBeanReference

postProcessAfterInitialization

See wrapIfNecessary to understand it! This will get a 代理对象 .

That is to say, what is returned at this time and placed in the second-level cache is a proxy object of TestA.

In this way, TestB is created!

When TestA starts to initialize and execute the post-processor, because TestA also has an agent, TestA will also execute this part postProcessAfterInitialization !

But before executing wrapIfNecessary , it will first judge whether there is TestA in the proxy object cache.

判断是否存在代理对象

But this piece gets the proxy object of TestA. must be false. So the proxy object of TestA will not be generated again.

Summarize

The flowchart in the case where both TestA and TestB have dynamic agents:

存在动态代理循环依赖流程图


神秘杰克
765 声望382 粉丝

Be a good developer.