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".
- 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>
- Create test cases
public class Test {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("spring-config.xml");
}
}
- 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.
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.
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();
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:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。