In the last Spring source analysis, we skipped some of Spring’s code on solving loop dependencies, so to fill this hole, I’m going to write another article on this issue.

Circular dependencies are simply two classes that depend on each other, as follows:

@Component
public class AService {

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

AService and BService are obviously both internally dependent on each other. If you look at them individually, you might see a common deadlock code in multithreading, but Spring obviously solves this problem, otherwise we wouldn’t be able to use it properly.

Creating a Bean actually calls the getBean() method, found in the AbstractBeanFactory class, which initially calls the **getSingleton()** method.

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
Copy the code

The implementation of this method looks interesting, with a bunch of if statements.

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();
                           	// Remove from level 3 cache and place in level 2 cache
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}
Copy the code

However, this if is easy to understand, which is to fetch the bean layer by layer, first from singletonObjects, which holds the fully created singleton bean; If you can’t get it, then go down and get it in earlySingletonObjects, this is the early exposure; If it doesn’t, then go to the third level cache singletonFactories, which are pre-exposed object factories that are fetched from the third level cache and put into the second level cache. In general, Spring does not fetch a bean directly from the container, but from the cache first, and there are three levels of caching. The bean returned from this method is not necessarily the bean we want. We will call the **getObjectForBeanInstance()** method to get the instantiated bean, which we won’t go into here.

But what if the bean does not exist in the cache? This means that the bean has not been created yet and needs to be created, which will take us back to the method of creating the bean in the previous lifecycle. Review the process: instantiation — Property injection — initialization — Destruction. So back to the example at the beginning of this article, there are ServiceA and ServiceB classes. In general, Spring creates beans in a natural order, so the ServiceA is created first. Obviously not in the cache at first, we’ll come to the method to create the bean. Starting with the instantiation phase, we’ll come to the first code related to resolving loop dependencies, which can be found in the instantiation phase code.

// 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));
}
Copy the code

So let’s look at the first line, the earlySingletonExposure variable what would it be?

It is returned with a conditional expression, one by one, first, mbd.issingleton (). We know that Spring’s default beans are singleton scoped, so we normally return true here. Second, the enclosing allowCircularReference, this variable is whether the tag allows the circular reference, the default is true. Third, calling a method, isSingletonCurrentlyInCreation (beanName), enter the code you can see it is to return the current bean is not normal to create, is true. So earlySingletonExposure returns true.

The next step is to get into the implementation of the if statement, which is the addSingletonFactory() method. Is it familiar to see the singletonFactories variable in the code? GetSingleton () is a level 3 cache, so the purpose of this method is to expose a factory object in advance through the level 3 cache.

/**
 * 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); }}}Copy the code

Next, recall the step after instantiation described in the previous chapter, which is property injection. This means that The ServiceA needs to inject the ServiceB into it, which obviously calls the getBean() method to get the ServiceB. If the ServiceB has not yet been created, the createBean() method will also be entered, as will the dependency injection step. If ServiceB depends on ServiceA, getBean() is called to get ServiceA. Instead of creating the Bean, the fetch ServiceA will fetch it from the cache. This cache is the singletonFactory we saw in the getSingleton() method above. The getEarlyBeanReference() method is the second argument to the addSingletonFactory() method.

/**
 * 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;
}
Copy the code

Look at bp. GetEarlyBeanReference (exposedObject, beanName) implementation, found that there are two, one is spring – SmartInstantiationAwareBeanPostProcessor under the beans, One is Abstractobproxy Creator under Spring-AOP. We chose the first implementation without using AOP.

default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   return bean;
}
Copy the code

Surprisingly, then, this method returns beans directly, which means that without AOP, this method does nothing but return instantiated objects directly. Another implementation is invoked if AOP is considered:

public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}
Copy the code

As you can see, this method actually returns a proxy for the bean, not itself, if AOP is used. So from this section we can conclude that level 3 caching is useless without AOP, and that level 3 caching is really just related to Spring AOP.

Ok, now we are in the process of creating B, but since B depends on A, we call the method of getting A, so A goes from level 3 cache to level 2 cache, and gets the proxy object of A. Of course, we do not need to worry about injecting B into A proxy object, because the generated proxy class is holding A reference to the target class, when calling the proxy object method, actually will call the target object method, so the proxy object is not affected. Of course, this also reflects that the object we are actually fetching from the container is actually a proxy object rather than itself.

So let’s go back to the logic that created A and see that the getSingleton() method is actually called again. The allowEarlyReference passed in is false.

if (earlySingletonExposure) {
   Object earlySingletonReference = getSingleton(beanName, false);
   if(earlySingletonReference ! =null) {
      if(exposedObject == bean) { exposedObject = earlySingletonReference; }... }}Copy the code

If you look at the **getSingleton()** code above, you can see that allowEarlyReference is false, which disables level 3 caching, and the code only executes until it passes level 2 cache GET.

singletonObject = this.earlySingletonObjects.get(beanName);
Copy the code

A can be fetched from the level 2 cache because it was already removed from the level 3 cache when we created and injected A into B. Further down is the code at the end of the life cycle.

So now the question is, why do we need level 3 cache, when level 2 cache seems to be enough?

See above getEarlyBeanReference () this method’s class, it is the key to SpringAOP automatic proxy class, it implements the SmartInstantiationAwareBeanPostProcessor, That is, it is also a post-processor, BeanPostProcessor, which has its own custom post-initialization methods.

/**
 * 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) {returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
Copy the code

It will obviously create the proxy if the current bean is not found in the earlyProxyReferences cache. That is, SpringAOP wants to create the proxy after the Bean is initialized. If we only use level 2 cache, it’s right here

 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
Copy the code

Call **getEarlyBeanReference()** and place the resulting earlier reference into the secondary cache. This means that regardless of whether there is a dependency between beans, creating beans at this point requires creating proxy objects. However, Spring doesn’t want to do this, so you can debug it yourself. If there is no dependency between ServiceA and ServiceB, the **getEarlyBeanReference()** method will not execute at all. In summary, using a second-level cache instead of a third-level cache will result in all beans completing AOP proxying after instantiation, which is unnecessary.

Remember that Spring creates beans in the natural order, so A comes first and B comes after:

We make A first created, but by relying on B, so begin to create B, the same, the time would want to use attributes into A, B will be through the getBean () to obtain A, A, put objects in early tertiary in the instantiation phase in the cache, without using AOP, so in essence is the bean itself, Otherwise, it is the proxy object after the AOP proxy. SingletonFactories, the level-three cache, will put it there. So the core of getting A through the getBean() method is the getSingleton() method, which takes it out of the level 3 cache and puts it into the level 2 cache. The getSingleton() method will be called again after the final creation of B and the initialization of A. At this time, the allowEarlyReference of the input parameter is false, so it is to fetch the bean or proxy object really needed from the second-level cache. Finally, the creation of A ends and the process ends.

So that’s all we have to say about Spring’s solution to circular dependencies, but with this conclusion we can ask ourselves: what kind of circular dependencies can’t be solved?

As we know from the above flowchart, the first big premise for solving loop dependencies is that beans must be singletons, and it is worth continuing with this premise. Then, based on the above summary, you can see that each bean is instantiated, that is, the constructor is executed. So the solution to the problem of circular dependencies really depends on the way dependency injection is done.

The methods of dependency injection include setter injection, constructor injection, and Field injection.

Filed mode is what we usually use most. The attribute is annotated with @autowired or @Resource, which has no impact on solving the circular dependence.

If both A and B are injected through setters, there is obviously no impact on the execution constructor, so there is no impact on resolving loop dependencies;

If A and B are injected into each other through A constructor, then when the constructor is executed (instantiation), A creates B before it is put into the cache, and B can’t get A, so it will fail;

If you inject B into A as setter, and inject A into B as constructor, since A instantiates first, executes the constructor, and creates the cache, there’s no problem, so you go ahead and inject property, depending on B and then go through the process of creating B, you can also fetch A from the cache, and the process goes smoothly.

If A injects B as A constructor and B injects A as A setter, then A enters the instantiation method first and finds that B is needed, then B will create B, but A has not been put into the three-level cache, and B will fail to obtain A when creating A again.

Ok, that is about the Spring all content to solve the problem of circular dependencies, the answer to the question “I knew that is a long time ago, but really just know the answer, this is their source code and debug a little look at just know is what is the answer, though still can’t completely learn the connect fully, but does a bit more profound understanding of the problem, Keep up the good work.