Contents

Analysis of Spring Source Code (Part 2) — How to resolve circular dependencies

Contents

In the last Spring source code analysis, we skipped part of the code on Spring to solve the circular dependency part of the code, in order to fill the hole, I here another article to discuss this issue.

First of all, explain what is circular dependency, in fact, very simple, is that there are two classes they are dependent on each other, as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Component
public class AService {

    @Autowired
    private BService bService;
}
@Component
public class BService {
  
    @Autowired
    private AService aService;
}

AService and BService are obviously both internally dependent on each other, singled out as if to see the common deadlock code in the multi-threaded, but obviously Spring solves this problem, otherwise we would not be able to use it properly.

The so-called creation of the bean is actually a call getBean () method, this method can be found in the AbstractBeanFactory this class inside, this method will start by calling getSingleton () method.

1
2
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

The implementation of this method is interestingly long, with a bunch of if statements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized(this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            // 从三级缓存里取出放到二级缓存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}

But it’s a good idea to get the bean one level at a time, first from singletonObjects, which holds the fully created singleton bean; if it’s not there, then go down to earlySingletonObjects, which is early exposed objects; if it’s still not there, then go to the third level of caching, singletonFactories, which is early exposed objects**; and if it’s still not there, then go to the third level of caching, singletonFactories, which is early exposed objects**. exposure of the object **; if still not, then go to the third level of cache singletonFactories to get it, it is the early exposure of the object factory, here will be taken out of the third level of cache into the second level of cache. Then in short, Spring to get a bean, in fact, not directly from the container inside the take, but first from the cache to find, and the cache has a total of **three **. Then the return from this method is not necessarily the bean we need, the back will call getObjectForBeanInstance () method to get the instantiated bean, here will not say more.

But what if the bean is indeed not available in the cache? Then the bean has not been created, you need to create a bean, so we will go to the previous life cycle in the creation of the bean method. Recall the process: instantiation — property injection — initialisation — destruction. So let’s go back to the example at the beginning of the article with two classes ServiceA and ServiceB. Generally speaking, Spring is in accordance with the natural order to create beans, then the first to be created is ServiceA. Obviously at first there is no cache, we will come to the method of creating beans. We will start with the instantiation phase, and we will come to the first code related to resolving circular dependencies, which can be found in the code of the instantiation phase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

Firstly, look at the first line, what will be the value of the variable earlySingletonExposure?

It is returned with a conditional expression, one by one, first, mbd.isSingleton(). We know that Spring’s default Bean’s scope are singleton, so the normal here is to return true no problem. The second, this.allowCircularReference, this variable is to mark whether to allow circular references, the default is also true. third, called a method, isSingletonCurrentlyInCreation(beanName), into the code can be seen it is return to the current bean is not normally created, obviously also true. so this **earlySingletonExposure ** return is true.

The next step is to enter the implementation of the if statement inside, that is, addSingletonFactory () this method. Is it familiar to see the variable singletonFactories in the code? Turn to the above getSingleton() know, in fact, is the third level of cache, so the role of this method is ** through the third level of cache in advance to expose a factory object **.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/**
 * Add the given singleton factory for building the specified singleton
 * if necessary.
 * <p>To be called for eager registration of singletons, e.g. to be able to
 * resolve circular references.
 * @param beanName the name of the bean
 * @param singletonFactory the factory for the singleton object
 */
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);
      }
   }
}

Next, recalling the step after instantiation as stated in the previous section, is property injection. This means that ServiceA needs to inject ServiceB into it, so obviously the getBean() method has to be called again to get ServiceB. serviceB hasn’t been created yet, then it will also go to this createBean() method, and again it will come to this step of dependency injection. serviceB has a dependency in ServiceB on ServiceB is dependent on ServiceA, then it will call getBean() to get ServiceA. At this time to get ServiceA is not to create the bean again, but from the cache to get. This cache is the above getSingleton() method we see inside the singletonFactory. So where this singletonFactory comes from is the second parameter of the addSingletonFactory() method, the getEarlyBeanReference() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * Obtain a reference for early access to the specified bean,
 * typically for the purpose of resolving a circular reference.
 * @param beanName the name of the bean (for error handling purposes)
 * @param mbd the merged bean definition for the bean
 * @param bean the raw bean instance
 * @return the object to expose as bean reference
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
         exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
      }
   }
   return exposedObject;
}

Looking at the implementations of bp.getEarlyBeanReference(exposedObject, beanName), I found two, one SmartInstantiationAwareBeanPostProcessor under spring-beans, and one AbstractAutoProxyCreator** under spring-aop. We take the first implementation without using AOP.

1
2
3
4
5
public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}

As you can see, if AOP is used, what this method returns is actually a proxy for the bean, not itself. So with this part we can assume that without using AOP, the three levels of caching are not useful, and the so-called three levels of caching are actually just related to Spring’s AOP.

Well we are now in the process of creating B, but because B depends on A, so the call to get A method, then A from the third level cache into the second level cache, to get A proxy object. Of course we don’t need to worry about injecting B as a proxy for A, ** because the generated proxy class internally holds a reference to the target class, and when calling the proxy’s methods, it actually calls the target object’s methods ** so the proxy is unaffected. Of course this also reflects the fact that the object we are actually getting from the container is actually the proxy object and not itself.

So let’s go back to the logic of creating A. We can see that the getSingleton() method is actually called once more. The allowEarlyReference passed in is false.

1
2
3
4
5
6
7
8
9
if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
      ...
   }
}

Looking over the getSingleton() code above you can see that allowEarlyReference to false is the equivalent of disabling the third level of caching and the code will only execute until it gets through the second level of caching.

1
singletonObject = this.earlySingletonObjects.get(beanName);

Since we already took A out of the tertiary cache and put it in the secondary cache earlier when we created and injected it into B, A can be fetched from the secondary cache here. Further down is the code behind the lifecycle, so I won’t continue.

So now there will be a question, ** why do we have to three levels of cache, direct use of the second level of cache seems to be enough? **

Look at the above getEarlyBeanReference () this method is located in the class, it is SpringAOP autoproxy key class, it implements the SmartInstantiationAwareBeanPostProcessor, that is to say, it is also a post-processor BeanPostProcessor which has custom post-initialisation methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

Obviously it’s earlyProxyReferences that will create the proxy if the current bean is not found in the cache. That means SpringAOP expects the proxy to be created after the bean is initialised. If we were to use only the second level of the cache, that would be in this place

1
 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Call getEarlyBeanReference() directly and put the resulting early reference into the secondary cache. This means that you have to create proxies whenever you get this far in creating beans, regardless of whether there are interdependencies between the beans or not. However, Spring does not want to do this, do not believe that you can do a debug, if there is no dependency between ServiceA and ServiceB, getEarlyBeanReference () this method will not be executed at all. In summary, ** if you don’t use the third level of cache directly use the second level of cache, it will lead to all the beans in the instantiation to complete the AOP proxy, which is not necessary. **

Finally, let’s reorganise the process, remember that Spring creates beans in their natural order, so A comes before B comes after:

https://leafw-blog-pic.oss-cn-hangzhou.aliyuncs.com/1613740800708.png

We start with the creation of A, but because of the dependency on B, we start creating B. Similarly, the property injection of B will have to use A, then it will go through the getBean() to get A. A will go ahead and put the object into the tertiary cache during the instantiation phase, and if it is not using AOP, then it is essentially the bean itself, otherwise it is the AOP proxy after the proxy object. The tertiary cache singletonFactories will store it in. So when you get A via the getBean() method, the core of the getSingleton() method will take it out of the tertiary cache and put it into the secondary cache. The final end of the creation of B back to the initialisation of A, will once again call the getSingleton () method, at this time into the **allowEarlyReference ** for false, so it is to go to the second level of the cache to take, to get the real need for the bean or proxy, and finally the end of the A creation, the end of the process.

So Spring’s principle of solving circular dependencies is roughly finished, but based on the above conclusion, we can think of a question, what is the case of circular dependencies can not be solved?

According to the flow chart above, we know that to solve circular dependencies first a major premise is **bean must be a single case **, based on this premise we are worth continuing to discuss the issue. Then according to the above summary, we can know that each bean is to be instantiated, that is, to execute the constructor. So the ability to solve the circular dependency problem is actually related to the way of dependency injection.

Dependency injection methods are setter injection, constructor injection and Field method.

The Filed method is the one we usually use the most, adding an @Autowired or @Resource annotation on the property, which has no effect on solving circular dependency;

If both A and B are injected via setter, obviously it has no effect on the execution of the constructor, so it has no effect on solving circular dependency;

If A and B are injected into each other via a constructor, then when the constructor is executed, that is, when it is instantiated, A goes to create B before it is put into the cache, then B can’t get A either, so it will be wrong;

If A is injected into B as a setter, and B is injected into A as a constructor, there is no problem with instantiating A, executing the constructor, and creating the cache, continuing the attribute injection, relying on B, and then going through the process of creating B, and obtaining A from the cache, the process is smooth all the way through.

If A in the way of injecting B for the constructor, B in the injection of A for the setter, then at this time A first into the instantiation method, found that the need for B, then will go to create B, and A has not been put into the third level of the cache, B and then create the time to get the A will fail to get.

Well, the above is all about Spring to solve the circular dependency problem, the answer to this question I know a long time ago, but really just know the answer, this time is to look at the source code plus debug a little bit of look to know why this answer, although it can not be done to learn thoroughly, but it does understand the problem a little bit more profoundly, and then work hard on it.

https://leafw-blog-pic.oss-cn-hangzhou.aliyuncs.com/bmc-button.png