Background

Today I found that a colleague’s spring boot project failed to start, and the exception log output from the console is as follows.

1
2
3
4
5
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)

When I saw the BeanCurrentlyInCreationException exception, my first thought was that there was a cyclic dependency problem. But on second thought, hasn’t Spring already solved the cyclic dependency problem? How can this exception still be thrown.

Upon closer inquiry, I learned that he annotated the @Async annotation on the method of the circular dependency bean.

Here I simulate the code at that time, AService and BService refer to each other, and the save() method of AService is annotated with @Async.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Component
public class AService {
    @Resource
    private BService bService;

    @Async
    public void save() {

    }
}

@Component
public class BService {

    @Resource
    private AService aService;

}

That is, this code causes a BeanCurrentlyInCreationException exception to be thrown when the application starts. Could it be that Spring is unable to solve the circular dependency problem when the @Async annotation meets it? To verify this suspicion, I removed the @Async annotation and started the project again and it worked. So basically we can conclude that Spring can’t solve the cyclic dependency problem when the @Async annotation meets the cyclic dependency.

Although the cause of the problem has been found, it leads to the following questions.

  • How does the @Async annotation work?
  • Why is Spring unable to resolve circular dependencies when the @Async annotation meets a circular dependency?
  • How do I resolve a cyclic dependency exception after it occurs?

How does the @Async annotation work?

The @Async annotation works with the AsyncAnnotationBeanPostProcessor class, which handles the @Async annotation. The objects of the AsyncAnnotationBeanPostProcessor class are put into the Spring container by the @EnableAsync annotation, which is the fundamental reason why the @EnableAsync annotation is needed to activate the @Async annotation to work.

AsyncAnnotationBeanPostProcessor class system

AsyncAnnotationBeanPostProcessor class system

This class implements the BeanPostProcessor interface and implements the postProcessAfterInitialization method, which is implemented in its parent class AbstractAdvisingBeanPostProcessor, which means that when the initialization phase of the bean is completed will call back the postProcessAfterInitialization method of the AsyncAnnotationBeanPostProcessor. The reason for the callback is that all the postProcessAfterInitialization methods of the BeanPostProcessor will be called back after the initialization of the bean is completed in the bean’s lifecycle, with the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
         Object current = processor.postProcessAfterInitialization(result, beanName);
         if (current == null) {
            return result;
         }
         result = current;
     }
    return result;
}

The implementation of AsyncAnnotationBeanPostProcessor for the postProcessAfterInitialization method is as follows.

 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
26
27
28
29
30
31
32
33
34
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (this.advisor == null || bean instanceof AopInfrastructureBean) {
   // Ignore AOP infrastructure such as scoped proxies.
        return bean;
    }

    if (bean instanceof Advised) {
       Advised advised = (Advised) bean;
       if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
           // Add our local Advisor to the existing proxy's Advisor chain...
           if (this.beforeExistingAdvisors) {
              advised.addAdvisor(0, this.advisor);
           }
           else {
              advised.addAdvisor(this.advisor);
           }
           return bean;
        }
     }

     if (isEligible(bean, beanName)) {
        ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
        if (!proxyFactory.isProxyTargetClass()) {
           evaluateProxyInterfaces(bean.getClass(), proxyFactory);
        }
        proxyFactory.addAdvisor(this.advisor);
        customizeProxyFactory(proxyFactory);
        return proxyFactory.getProxy(getProxyClassLoader());
     }

     // No proxy needed.
     return bean;
}

The main role of this method is used for the method input parameter object dynamic proxy, when the input parameter object class annotated @Async annotation, then the method will generate a dynamic proxy object for this object, and finally will return the input parameter object proxy object. As for how to determine whether the method is annotated with @Async annotation, it relies on isEligible(bean, beanName) to determine. Since this code involves the knowledge of the underlying dynamic proxy, it will not be expanded in detail here.

AsyncAnnotationBeanPostProcessor

AsyncAnnotationBeanPostProcessor role

In summary, we can conclude that when the initialization phase of the bean creation process is completed, the postProcessAfterInitialization method of the AsyncAnnotationBeanPostProcessor is called, and the object of the class annotated with the @Async annotation is dynamically proxied. and then returns a proxy object back.

Although we conclude that the role of the @Async annotation relies on dynamic proxying, but here actually raises another question, that is, the transaction annotation @Transactional or the custom AOP facet, they are also implemented through dynamic proxying, why when using these, no circular dependency exception is thrown? Is their implementation different from that of the @Async annotation? Well, it’s not really the same, so read on.

How is AOP implemented?

We all know that AOP relies on dynamic proxies and works in the bean’s lifecycle, specifically by AnnotationAwareAspectJAutoProxyCreator class, which goes through the bean’s lifecycle to handle the tangents, transaction annotations, and then generates dynamic proxies. The objects of this class are automatically injected into the Spring container when the container is started.

AnnotationAwareAspectJAutoProxyCreator also implements BeanPostProcessor and also implements postProcessAfterInitialization method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
    if (bean != null) {
       Object cacheKey = getCacheKey(bean.getClass(), beanName);
       if (!this.earlyProxyReferences.contains(cacheKey)) {
           //generate dynamic proxies, if they need to be proxied
           return wrapIfNecessary(bean, beanName, cacheKey);
       }
     }
    return bean;
}

The wrapIfNecessary method will then dynamically proxy the bean, if your bean needs to be dynamically proxied.

AnnotationAwareAspectJAutoProxyCreator role

That is, although both AOP and @Async annotations are dynamic proxies at the bottom, the specific classes that implement them are different. The general AOP or transaction dynamic proxy relies on the AnnotationAwareAspectJAutoProxyCreator implementation, while the @Async relies on the AsyncAnnotationBeanPostProcessor implementation, and both work after the initialization is completed, which is also This is the main difference between the @Async annotation and AOP, that is, the classes handled are different.

How Spring resolves cyclic dependencies

Spring relies on the three-level cache to solve the circular dependency.

Simply put, by caching the object being created corresponding to the ObjectFactory, you can get the object being created by the early reference to the object, when there is a circular dependency, because the object is not created, you can just inject by getting the early reference to the object.

The ObjectFactory code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

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);
       }
    }
}

As you can see, the cached ObjectFactory is actually a lamda expression, and the real way to get the earlier exposed reference objects is actually through the getEarlyBeanReference method.

getEarlyBeanReference

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
               SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
               exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
       }
    }
    return exposedObject;
}

The getEarlyBeanReference implementation calls all the getEarlyBeanReference methods of the SmartInstantiationAwareBeanPostProcessor.

The AnnotationAwareAspectJAutoProxyCreator class mentioned earlier implements the SmartInstantiationAwareBeanPostProcessor interface, and the getEarlyBeanReference method is implemented in the parent class in the parent class.

1
2
3
4
5
6
7
8
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (!this.earlyProxyReferences.contains(cacheKey)) {
        this.earlyProxyReferences.add(cacheKey);
    }
    return wrapIfNecessary(bean, beanName, cacheKey);
}

This method will finally call the wrapIfNecessary method, which, as mentioned earlier, is a way to get a dynamic proxy that will be proxied if needed, such as a transaction annotation or a custom AOP that will be done when exposed early on.

This finally made it clear that what was exposed early on was probably a proxy object, and was eventually obtained through the getEarlyBeanReference method of the AnnotationAwareAspectJAutoProxyCreator class.

But AsyncAnnotationBeanPostProcessor does not implement SmartInstantiationAwareBeanPostProcessor, that is, it does not call AsyncAnnotationBeanPostProcessor to process the @Async annotation at the stage of getting the early object.

Why can’t Spring solve the problem of circular dependency when the @Async annotation is annotated?

Let’s take the previous example here, AService adds @Async annotation, AService is created first, BService is found to be referenced, then BService goes to create it, and when Service is created, AService is found to be referenced, then it will be created by AnnotationAwareAspectJAutoProxyCreator class implements the getEarlyBeanReference method to get the early reference object of AService, at this time this early reference object may be proxied, depending on whether AService needs to be proxied, but It must not be a proxy that handles the @Async annotation, for the reasons mentioned above.

So after BService is created and injected into AService, then AService will continue to process, and as said before, when the initialization phase is completed, all the BeanPostProcessor implementations will call the postProcessAfterInitialization method. So it will call back the postProcessAfterInitialization method implementation of AnnotationAwareAspectJAutoProxyCreator and AsyncAnnotationBeanPostProcessor in turn.

This callback has two details.

  • AnnotationAwareAspectJAutoProxyCreator is executed first and AsyncAnnotationBeanPostProcessor is executed later, because AnnotationAwareAspectJAutoProxyCreator is in front.

    AnnotationAwareAspectJAutoProxyCreator

  • The results of the AnnotationAwareAspectJAutoProxyCreator processing are passed as input parameters to the AsyncAnnotationBeanPostProcessor, which is how the applyBeanPostProcessorsAfterInitialization method is implemented

AnnotationAwareAspectJAutoProxyCreator callback: it will find that the AService object has been referenced earlier, nothing is processed, and the AService object is returned directly .

AsyncAnnotationBeanPostProcessor callback: it finds that AService annotated with the @Async annotation, then it creates a dynamic proxy for the object returned by AnnotationAwareAspectJAutoProxyCreator and returns it.

After this callback, have you found the problem. The object exposed earlier may be AService itself or a proxy object of AService, and it is implemented through the AnnotationAwareAspectJAutoProxyCreator object. But with the AsyncAnnotationBeanPostProcessor callback, a proxy object for the AService object is created, which results in the object exposed earlier by AService not being the same as the object created completely in the end, so it’s definitely not right. How can the same bean exist in a Spring with two different objects, so it will throw BeanCurrentlyInCreationException exception, the code of this judgment logic is as follows.

 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
26
27
28
29
30
31
32
33
if (earlySingletonExposure) {
  // Get the object exposed earlier
  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
      // An early exposed object is not null, indicating a circular dependency
      if (exposedObject == bean) {
          // This judgment means that the postProcessAfterInitialization callback is not dynamically proxied, if not then the early exposed object is assigned to the final exposed (generated) object.
          // so that the early exposed object and the final generated object are the same
          // But once the postProcessAfterInitialization callback generates a dynamic proxy, then it will not go here, that is, with the @Aysnc annotation, it will not go here
          exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
               // allowRawInjectionDespiteWrapping Default is false
               String[] dependentBeans = getDependentBeans(beanName);
               Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
               for (String dependentBean : dependentBeans) {
                   if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                       actualDependentBeans.add(dependentBean);
                  }
               }
               if (!actualDependentBeans.isEmpty()) {
                   //Throwing Exceptions
                   throw new BeanCurrentlyInCreationException(beanName,
                           "Bean with name '" + beanName + "' has been injected into other beans [" +
                           StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                           "] in its raw version as part of a circular reference, but has eventually been " +
                           "wrapped. This means that said other beans do not use the final version of the " +
                           "bean. This is often the result of over-eager type matching - consider using " +
                           "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
               }
      }
   }
}

So, the reason why the @Async annotation encounters a circular dependency that Spring cannot resolve is because the @Aysnc annotation makes the bean that is eventually created not the same object as the bean that was exposed earlier, so it throws an exception.

Spring can’t solve this problem because the @Aysnc annotation makes the final bean created not the same object as the earlier exposed bean, so it throws an exception.

How do I resolve a circular dependency exception after it occurs?

There are many ways to solve this problem

  1. adjust the dependency relationship between objects, fundamentally eliminate the circular dependency, there is no circular dependency, there is no such thing as early exposure, then there will be no problem.

  2. do not use the @Async annotation, you can achieve their own asynchronous through the thread pool, so there is no @Async annotation, you will not generate a proxy object at the end, resulting in early exposure out of the object is not the same.

  3. You can annotate the @Lazy annotation on the fields of cyclic dependency injection.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    @Component
    public class AService {
        @Resource
        @Lazy
        private BService bService;
    
        @Async
        public void save() {
    
        }
    }
    
  4. From the source code comment above, we can see that when allowRawInjectionDespiteWrapping is true, it won’t take that else if and won’t throw an exception, so we can solve the error problem by setting allowRawInjectionDespiteWrapping to true, the code is as follows.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
        }
    
    }
    

    Although this setup solves the problem, it is not recommended because it allows the early injected objects to be different from the final created objects and may result in the final generated objects not being dynamically proxied.

Reference https://www.lifengdi.com/archives/transport/technology/3953