This is the sixth day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
When a similar error occurs during development: nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException Cyclic dependencies occur in a project, which is usually handled by moving the common call to another file to avoid cyclic references. How does Spring solve this problem? A references B, and B references A
Usage scenarios:
Depend on the situation | Dependency injection | Whether circular dependencies are resolved |
---|---|---|
AB interdependence (cyclic dependence) | All are injected using setter methods | is |
AB interdependence (cyclic dependence) | All use constructor injection | no |
AB interdependence (cyclic dependence) | Inject B in A as setter methods, and inject A in B as constructors | is |
AB interdependence (cyclic dependence) | A is injected into B as A setter method, and B is injected into A as A constructor | no |
As we can see from the above test results, not only can circular dependencies be resolved in the case of setter method injection, but circular dependencies can be handled normally even in the case of constructor injection.
How does Spring solve this:
When it comes to cyclic dependency, the first thing that comes to mind is “level 3 caching “,” pre-exposure “… As for the process and details in Balabala, I have almost forgotten them. Here is the whole process to deepen my memory
Spring resolves circular dependencies with a three-level cache, using three maps:
The first-level cache is singletonObjects pool, and the first-level cache stores the mapping of singleton beans. Notice that the bean is already created.
Level 2 cache is earlySingletonObjects, which stores early semi-finished (uninitialized) beans. The corresponding relationship is also bean name –> bean instance. It differs from {@link #singletonObjects} in that the beans it holds are not necessarily complete
The tertiary cache is an early Exposure object factory. The level 3 cache, which holds the ObjectFactory, can be understood as the Factory for creating an early semi-finished (uninitialized) bean
- When A, B two kind of circular reference occurs, after A complete instantiation, just use the instantiated object to create an object factory, and added to the three-level cache, if A is AOP agent, then through access to the factory is A proxy objects, if A is not AOP agent, then get to the factory is A instantiation of the object.
- When A does property injection, B will be created, and B will depend on A, so when B is created, getBean(A) will be called to get the required dependency, and getBean(A) will be fetched from the cache.
- The first step is to obtain the factory in the level 3 cache.
- The second step is to call the getObject method of the object factory to get the corresponding object, which is then injected into B.
- B then goes through its lifecycle, including initialization, post-processor, and so on. After B is created, B is injected into A, and A completes its life cycle. At this point, the loop dependency ends!
It was this diagram that helped me figure out the recurring dependencies I’d been reluctant to look at:
Features: 1. The first call completes initialization first. 2. The last level is empty
The source code interpretation
// DefaultSingletonBeanRegistry.java @Nullable protected Object getSingleton(String beanName, Boolean allowEarlyReference) {/ / loading beans from the singleton buffer Object singletonObject = this. SingletonObjects. Get (beanName); // The bean in the cache is empty, And the current bean is creating the if (singletonObject = = null && isSingletonCurrentlyInCreation (beanName)) {/ / locking synchronized (this singletonObjects) {/ / from earlySingletonObjects singletonObject = this. EarlySingletonObjects. Get (beanName); // earlySingletonObjects does not have, If (singletonObject == null && allowEarlyReference) {// Retrieve the corresponding ObjectFactory from singletonFactories ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) {/ / get bean singletonObject = singletonFactory. GetObject (); / / add bean into earlySingletonObjects enclosing earlySingletonObjects. Put (beanName singletonObject); / / removed from the singletonFactories corresponding ObjectFactory enclosing singletonFactories. Remove (beanName); } } } } return singletonObject; }Copy the code
-
First, fetch it from the level 1 cache singletonObjects.
-
If there is no beanName and the currently specified beanName is being created, it is retrieved from the secondary cache earlySingletonObjects.
-
If, again, none is retrieved and singletonFactories are allowed to be retrieved with #getObject(), then it is retrieved from the level-three cache singletonFactories. If it does, it gets the object with its #getObject() method and adds it to the secondary cache earlySingletonObjects and removes it from the tertiary cache singletonFactories
The above is fetched from the cache, but where is the data added from the cache?
// DefaultSingletonBeanRegistry.java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
Copy the code
As you can see from this code, the singletonFactories three-level cache is the key to solving Spring Bean loop dependencies. This code also happens at #createBeanInstance(…) Method, that is, the bean is actually created, but it is not quite perfect (no property population or initialization), but it is sufficient for other objects that depend on it (to locate objects in the heap based on object references) to be recognized.
Now that the primary cache singletonObjects and the secondary cache earlySingletonObjects are referenced, where is the primary cache set up
/ DefaultSingletonBeanRegistry.java protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}Copy the code
Add to level 1 cache and delete from Level 2 and level 3 cache.
Supplement:
Why didn’t Sping choose a second level cache instead of adding an extra layer of cache?
If you want to useThe second level cache
To solveCircular dependencies
, means that the Bean is instructure
Then create itProxy objects
This violatesSpring Design Principles
. Spring combines AOP with beans in the lifecycleBean fully created
After throughAnnotationAwareAspectJAutoProxyCreator
This is done by the back processor, and this is done by the back processorpostProcessAfterInitialization
Method to complete the AOP proxy on the initialized Bean. And if it does,Circular dependencies
There is no alternative but to create a proxy for the Bean first, but in the absence of cyclic dependencies, the design is to have the Bean complete the proxy at the last step of its life cycle rather than immediately after instantiation.