What is circular dependency
The creation of class a depends on class B, and the creation of class B depends on class A
There are four simple cases
Dependency | Dependency injection mode | Are circular dependencies resolved |
---|---|---|
AB interdependence (cyclic dependency) | setter method is used for injection | yes |
AB interdependence (cyclic dependency) | All are injected by constructor | no |
AB interdependence (cyclic dependency) | The method of injecting B into A is setter method, and the method of injecting A into B is constructor | yes |
AB interdependence (cyclic dependency) | The method of injecting A into B is setter method, and the method of injecting B into A is constructor | no |
The above will be discussed later
How does Spring solve circular dependency
L3 cache
Before introducing circular dependencies, let's introduce a few sets
Map | effect |
---|---|
L3 cache singletonFactories | Save the Factory where you want to create the Bean |
L2 cache earlySingletonObjects | Save the Bean proxy object created from the Bean's Factory |
First level cache singletonObjects | Save the created Bean |
registeredSingletons | Indicates that the beans in the collection are registered |
singletonsCurrentlyInCreation | Indicates that the Bean of the collection species is being created |
The cyclic dependency of a Bean is inseparable from the creation of a Bean. Please understand the life cycle of a Bean first
- doGetBean
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean Object sharedInstance = this.getSingleton(beanName); ----- sharedInstance = this.getSingleton(beanName, () -> { try { return this.createBean(beanName, mbd, args); } catch (BeansException var5) { this.destroySingleton(beanName); throw var5; } });
In doGetBean, these two points are closely related to circular dependency
Let's first look at the source code of getsingletonstring beanname, Boolean allowrearlyreference). In the above circular dependency process of A and B, this method will be executed three times; When A is created for the first time, because A is also created for the first time, the return is empty and the specific process will not be executed. When the life cycle of A comes to attribute injection, that is, org springframework. beans. factory. support. Abstractautowirecapablebeanfactory#populatebean, and to inject B, it will create B, and then execute it again when creating B. because B is also created for the first time, the return is null. When B performs attribute injection and wants to inject A, it will call this method again, and because A is already being created, Moreover, A's factory has been added to singletonFactories, so after the execution of this method, A's proxy object will be returned to create B's Bean. When was A's factory added to singletonFactories? Next, see org springframework. beans. factory. support. DefaultSingletonBeanRegistry#addSingletonFactory
protected Object getSingleton(String beanName, boolean allowEarlyReference) { //Judge whether there are created beans in the singleton pool Object singletonObject = this.singletonObjects.get(beanName); //Does not exist and the Bean is being created if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { //Judge whether the proxy of the Bean exists in the L2 cache singletonObject = this.earlySingletonObjects.get(beanName); //Does not exist and the Bean allows circular dependencies if (singletonObject == null && allowEarlyReference) { //Lock the L1 cache to prevent duplicate Bean creation synchronized(this.singletonObjects) { //Again, judge whether there are created beans in the L1 cache singletonObject = this.singletonObjects.get(beanName); //non-existent if (singletonObject == null) { //Again, judge whether the proxy object of Bena exists in the L2 cache singletonObject = this.earlySingletonObjects.get(beanName); //non-existent if (singletonObject == null) { //Judge again whether there is Bena's factory in the L3 cache ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); //existence if (singletonFactory != null) { //Get the corresponding factory singletonObject = singletonFactory.getObject(); //Use the Bean factory to create the proxy object of the Bean and put it into the L2 cache this.earlySingletonObjects.put(beanName, singletonObject); //Remove Bean factory from L3 cache this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
This method is executed before attribute injection
Source code
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); //Lock L1 cache synchronized(this.singletonObjects) { //If the L1 cache does not exist, the Bean if (!this.singletonObjects.containsKey(beanName)) { //Add the factoru of the Bean to the L3 cache this.singletonFactories.put(beanName, singletonFactory); //Move the proxy corresponding to the Bean out of the L2 cache this.earlySingletonObjects.remove(beanName); //Register the Bean this.registeredSingletons.add(beanName); } } }
Then take a look at another getsingleton method, org springframework. beans. factory. support. DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
This method is triggered when doGetBean is used to obtain a singleton Bean. You can think about how to deal with it if you can't obtain the Bean? If you can't get it, it will be created. In this method, createbean will be called
if (mbd.isSingleton()) { sharedInstance = this.getSingleton(beanName, () -> { try { //Call createbean to create a Bean return this.createBean(beanName, mbd, args); } catch (BeansException var5) { this.destroySingleton(beanName); throw var5; } }); bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
In creatBean, the Bean to be created is checked first. After completion of verification, doCreatBean is called to create Bean.
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ...... try { beanInstance = this.doCreateBean(beanName, mbdToUse, args); if (this.logger.isTraceEnabled()) { this.logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } ...... }
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
That is, in this method, the instantiation, attribute injection and initialization of the Bean are completed
Circular dependency is triggered when the attribute is filled. If circular dependency is triggered, an object that needs circular dependency will be created. If the object is being created and added to the L3 cache (mentioned above), A proxy object will be created through the L3 cache to create the object that needs to be created. When the object is created, it will be added to the L1 cache and then handed over to the object to be created. (when AB has circular dependency, A is created first. When injecting attributes, A creates B and then starts to create B. when injecting attributes into B, it is found that A is being created and already exists in the level 3 cache, it is found that A is being created, and the proxy object of A is handed over to B for creation. After creation, it is put into the level 1 cache, and then it returns to create A, and A goes to the level 1 cache Save and get B, and then create A... that's it).
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ...... if (instanceWrapper == null) { //instantiation instanceWrapper = this.createBeanInstance(beanName, mbd, args); } ...... try { //Attribute filling this.populateBean(beanName, mbd, instanceWrapper); //initialization exposedObject = this.initializeBean(beanName, exposedObject, mbd); } ...... }
As mentioned above, the created Bena should be added to the L1 cache. Where did you add it? Do you remember where we called createbean? Yes
org. springframework. beans. factory. support. Defaultsingletonbeanregistry #getsingleton (java.lang.string, org. Springframework. Beans. Factory. Objectfactory <? >) here, right! The creation here must also be added in this method! Looking at the source code of this method, you will find that there is such a method org. In this method springframework. beans. factory. support. DefaultSingletonBeanRegistry#addSingleton
Take a look at the source code. You can see that it has been added to the L1 cache, that is, the singleton pool we often say!
protected void addSingleton(String beanName, Object singletonObject) { synchronized(this.singletonObjects) { //Add to L1 cache this.singletonObjects.put(beanName, singletonObject); //Remove L3 cache this.singletonFactories.remove(beanName); //Remove L3 cache this.earlySingletonObjects.remove(beanName); //Register this.registeredSingletons.add(beanName); } }
The circular dependency is thus solved
I don't know if you feel it. Spring's circular dependency is triggered to be solved only when property injection is carried out. That is, there is no circular dependency processing when using construction methods when setting setter s. When I try, I find that the circular dependency injected according to construction methods cannot be solved, that is, the table for solving circular dependency at the beginning, I copied it, but the third one can succeed, but the fourth one can't. let's talk about my own understanding. If it's wrong, please let me know in time!
Dependency | Dependency injection mode | Are circular dependencies resolved |
---|---|---|
AB interdependence (cyclic dependency) | setter method is used for injection | yes |
AB interdependence (cyclic dependency) | All are injected by constructor | no |
AB interdependence (cyclic dependency) | The method of injecting B into A is setter method, and the method of injecting A into B is constructor | yes |
AB interdependence (cyclic dependency) | The method of injecting A into B is setter method, and the method of injecting B into A is constructor | no |
Before we talk about it, we need to know that Spring creates beans in order, which will be sorted according to the initial letter or A certain way. You can check it. Here you can get that A will be created before B.
In the third case, the life cycle of A will go to attribute injection. If it is found that B needs to be injected, it will create B. in the process of creating B, it is found that A needs to be injected. Because A has been instantiated, B can use the object instantiated by A to create, which solves the problem of circular dependency.
In the fourth case, B is needed when A is instantiated, so B will be created directly. At this time, A is not instantiated and added to the cache, so that when B is created, it has to come back and start creating A again, and when A is created, it has to create B, so it goes back and forth... It can't be solved...
Then explain several questions often asked in the interview
- Can L2 cache solve circular dependency??
The answer is yes. Let's take a look at the impact of removing the L3 cache and L2 cache on circular dependency
First remove the L3 cache. The factory used to create Bena is stored in the L3 cache. Once removed, each time the directly created proxy object is placed in the L2 cache, it can also deal with circular dependency, but what impact will it have? Comparing before and after removal, it is found that after removal, the post processor will be executed in the instantiation stage to proxy the object, which is contrary to the design of Spring and will have an impact on subsequent extensions. With the three-level cache, you can proxy when you really need it, which is equivalent to delaying instantiation.
Let's take A look at removing the L2 cache. The L2 cache is used to store proxy objects. After removing the L2 cache, where will the proxy objects go? The probability will be in the first level cache. In this way, the second level cache may store Bean forms, proxies and complete beans in different periods. If both A and B depend on C, they will rely on C's proxy to create A when creating A, and then C in the first level cache will become A complete Bean after the execution life cycle, which is re created by B, B will create beans based on C's beans in the L1 cache, which will lead to A and B relying on different forms of C. - Will L3 caching improve efficiency?
Yes, it has little impact
If L3 cache is used and there is cache dependency, the impact on efficiency mainly lies in the processing of AOP
If the L3 cache is used, the timing of AOP proxy is different for the presence or absence of AOP. In case of circular dependency, proxy will be performed when processing the L2 cache, and in case of nonexistence, proxy will be performed during initialization;
If L2 cache is used, proxy is also required, and the timing of proxy is different
For the above cases, the impact on efficiency can be said to be No.