Circular dependence refers to the interdependence between two beans and forming a loop.
In the currently used spring version, the circular dependency is turned off by default at startup. Assuming that two beans in the code use the @Autowired annotation to automatically assemble each other, an error will be reported when starting:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting -circular-references to true.
Translated:
The use of circular references is discouraged, and by default, it is prohibited. Update the application to remove the dependency loop between beans. As a last resort, the loop can be automatically broken by setting -circular-references to true.
Spring still supports circular dependencies, but only if we have to turn it on manually. We set the property -circular-references = true in the configuration file.
Solution to circular dependencies:
1. After bean A is instantiated, if it is detected that it supports circular dependencies, A will be cached first.
// Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. //Judge the current bean as a singleton, and circular dependencies are allowed, and the bean is being created boolean earlySingletonExposure = (() && && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (()) { ("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
The second parameter of the addSingletonFactory method is passed in a lambda expression that implements the ObjectFactory<?> functional interface. The getEarlyBeanReference method is called in the expression to obtain the early reference of the bean.
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { (singletonFactory, "Singleton factory must not be null"); synchronized () { /* If the current bean does not exist in singletonObjects (the current bean is still being created and the final singleton object has not been generated, obviously true), then: 1) Put the current singletonFactory into the singletonFactory variable (put the lambda expressions that get early references into the third level cache); 2) Remove the current bean from the earlySingletonObjects variable (clean the secondary cache to ensure that early references are generated only when the circular reference is triggered; actually there is no one here); 3) Put the current beanName into the registeredSingletons variable. */ if (!(beanName)) { (beanName, singletonFactory); (beanName); (beanName); } } }
You may not see any clues here. Keep going. When bean A is initialized, it will automatically assemble the dependent beans B, C, etc., and when B and C are initialized, it will automatically assemble the A they depend on (of course, B and C will be cached first like A after instantiation). However, at this time, A is also being created, and the final object will definitely not be able to get it. At this time, consider generating an early reference of A and providing it to B and C first.
2. When the doGetBean method obtains bean A, the getSingleton method will be called first to find the cache. Some source codes are as follows:
// Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (()) { if (isSingletonCurrentlyInCreation(beanName)) { ("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { ("Returning cached instance of singleton bean '" + beanName + "'"); } } beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); }
Next, let’s look at the getSingleton method:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock //Get bean A from singletonObjects first. I said before that it is obviously not available. Object singletonObject = (beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //Get the bean from earlySingletonObjects, there was no one at the beginning. singletonObject = (beanName); if (singletonObject == null && allowEarlyReference) { synchronized () { // Consistent creation of early reference within full singleton lock singletonObject = (beanName); if (singletonObject == null) { singletonObject = (beanName); if (singletonObject == null) { // Finally get the singletonFactory from singletonFactory ObjectFactory<?> singletonFactory = (beanName); if (singletonFactory != null) { //Call getObject method to get early references, put them in earlySingletonObjects, and clean singletonFactories singletonObject = (); (beanName, singletonObject); (beanName); } } } } } } return singletonObject; }
Here the structure of the Level 3 cache is clearer:
Level 1 cache: singletonObject, where the bean object that is finally initialized is stored.
That is to say, after the bean is initialized, you can directly find it from the cache when you take it again next time, so this is a general cache and is not designed specifically for circular dependencies.
Level 2 cache: earlySingletonObjects, the semi-finished bean object that has been instantiated but has not been initialized yet is actually equivalent to exposing the incomplete object in advance.
For example, when bean A is initialized and injected beans B and C, the initialization of B and C all depends on A. At this time, it is definitely impossible to find A from the first-level cache, so it will go down to the second-level cache. If A is obtained, the doGetBean method will return instead of entering the initialization of A again, which is equivalent to the cycle being cut off. Then B and C can complete the initialization, and finally A can complete the initialization.
Level 3 cache: singletonFactories, object factory, store method implementation of obtaining semi-finished bean objects.
The data in the secondary cache is not generated out of thin air, but because the data cannot be found when accessing the secondary cache at the beginning, then it goes down to the third cache, calls the getObject method, and only puts it in after getting the semi-finished object, and then other beans can be directly retrieved from the secondary cache.
After the secondary cache is stored in the data, the corresponding data of the third cache is no longer needed and will be removed immediately.
The () method is actually the implementation of the lambda expression, and the getEarlyBeanReference method is called:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = (exposedObject, beanName); } } return exposedObject; }
Here, the getEarlyBeanReference method of SmartInstantiationAwareBeanPostProcessor is called to obtain an early reference to the bean instance. In fact, it is the semi-finished bean object itself that has not yet been initialized (or it may have created a proxy object).
Can you not have a level 3 cache? After instantiating a bean, you can directly generate an early reference and put it in the level 2 cache to facilitate circular dependencies?
It is OK but unreasonable. Early references can be used to solve the problem of circular dependency, but in fact, spring is not recommended to use circular dependency by default. If you do not combine whether circular dependencies are needed, directly cache an object, this design is obviously problematic.
Can you avoid the secondary cache? Every time a circular dependency occurs, you can directly call the third-level cache method to obtain early references?
No, first of all, it is repeated calls. It is possible that the work of obtaining early references is completely repeated every time; in fact, if it is a bean object that requires AOP dynamic proxy, a new proxy object will be generated every time, which does not conform to the design principle of singleton.
There is another detail, that is, when a bean is initialized and the singleton object is obtained, the addSingleton method will be called:
protected void addSingleton(String beanName, Object singletonObject) { synchronized () { (beanName, singletonObject); (beanName); (beanName); (beanName); } }
This not only puts the object into the first-level cache singletonObjects, but also cleans up the corresponding second- and third-level caches.
Finally, it should be noted that the way spring solves circular dependencies is after the instantiation of the bean is completed, so do not introduce circular dependencies in the construction method, because the object has not been instantiated at this time and spring cannot solve it.
This is the article about Spring boot startup process - solving circular dependencies. For more related Spring boot circular dependencies, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!