Be careful with the @Async annotation, let us enter the pit of Spring's circular dependency

background

Some time ago, a colleague told me that her project could not start, and asked me to take a look. In the spirit of helping others, I will definitely help.

So, I found the following abnormal information in her console:

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)

Seeing the exception of BeanCurrentlyInCreationException, my first reaction is that there is a problem of circular dependency. But if you think about it carefully, hasn't Spring already solved the problem of circular dependency? Why is this error still reported? So, I asked what was changed, and she said that the @Async annotation was added to the method.

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.

@Component
public class AService {
    @Resource
    private BService bService;

    @Async
    public void save() {

    }
}
@Component
public class BService {

    @Resource
    private AService aService;
}

That is to say, this code will report BeanCurrentlyInCreationException. Could it be that when @Async annotation encounters circular dependency, Spring cannot solve it? In order to verify this conjecture, I removed the @Async annotation and started the project again, and the project succeeded. So it can basically be concluded that when the @Async annotation encounters a circular dependency, Spring really cannot solve it.

Although the cause of the problem has been found, it leads to the following problems:

  • How does the @Async annotation work?

  • Why can't Spring solve the @Async annotation encountering a circular dependency?

  • How to solve the circular dependency exception?

How does the @Async annotation work?

The @Async annotation is implemented by the AsyncAnnotationBeanPostProcessor class, which processes the @Async annotation. The object of this class AsyncAnnotationBeanPostProcessor is injected into the Spring container by the @EnableAsync annotation, which is the fundamental reason why it is necessary to use the @EnableAsync annotation to activate the @Async annotation to work.

AsyncAnnotationBeanPostProcessor

class system

This class implements the BeanPostProcessor interface and the postProcessAfterInitialization method, which is implemented in its parent class AbstractAdvisingBeanPostProcessor, that is to say, after the Bean initialization phase is completed, it will call back the postProcessAfterInitialization method of AsyncAnnotationBeanPostProcessor. The reason for the callback is that in the life cycle of the Bean, when the Bean initialization is completed, all the postProcessAfterInitialization methods of the BeanPostProcessor will be called back. The code is as follows:

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

AsyncAnnotationBeanPostProcessor implements for postProcessAfterInitialization method:

@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 function of this method is to dynamically proxy the object of the method input parameter. When the class of the input parameter object is annotated with @Async, then this method will dynamically proxy the object, and finally return the input object The proxy object goes out. As for how to judge whether the method has @Async annotation, it is judged by isEligible(bean, beanName). Since this code involves the underlying knowledge of the dynamic agent, it will not be expanded here in detail.

The role of AsyncAnnotationBeanPostProcessor

In summary, a conclusion can be drawn, that is, after the initialization phase in the Bean creation process is completed, the postProcessAfterInitialization method of AsyncAnnotationBeanPostProcessor will be called to dynamically proxy the object of the class annotated with @Async, and then return a proxy object go back.

Although here we conclude that the function of the @Async annotation is realized by means of dynamic proxy, another problem arises here, that is, the transaction annotation @Transactional or the custom AOP aspect, they are also implemented through dynamic proxy Yes, why when using these, no circular dependency exceptions are thrown? Are their implementations different from those of the @Async annotation? Yes, it’s really not the same, please read on.

How is AOP implemented?

We all know that AOP is implemented by dynamic proxy, and it works in the life cycle of Bean. Specifically, it is implemented by the class AnnotationAwareAspectJAutoProxyCreator. This class will process aspects and transaction annotations in the life cycle of Bean, and then generate dynamic acting. Objects of this class will be automatically injected into the Spring container when the container starts.

AnnotationAwareAspectJAutoProxyCreator also implements BeanPostProcessor and also implements postProcessAfterInitialization method.

@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)) {
           //生成动态代理,如果需要被代理的话
           return wrapIfNecessary(bean, beanName, cacheKey);
       }
     }
    return bean;
}

Through the wrapIfNecessary method, the Bean will be dynamically proxied, if your Bean needs to be dynamically proxied.

AnnotationAwareAspectJAutoProxyCreator 作用

That is to say, although the bottom layers of AOP and @Async annotations are dynamic proxies, the specific implementation classes are different. The general AOP or transactional dynamic proxy is implemented by AnnotationAwareAspectJAutoProxyCreator, while @Async is implemented by AsyncAnnotationBeanPostProcessor, and it works after the initialization is completed. This is the main difference between the @Async annotation and AOP, that is, the processing The classes are different.

How does Spring resolve circular dependencies?

When Spring resolves circular dependencies, it relies on the third-level cache. I once wrote an article about the third-level cache. If you have any unclear friends, you can read these questions about circular dependencies and third-level cache. Do you know it? (Interview frequently asked) This article, this article can also be regarded as the continuation of this three-level cache article.

To put it simply, by caching the ObjectFactory object corresponding to the object being created, you can get the early referenced object of the object being created. When a circular dependency occurs, because the object has not been created, you can inject it by getting the early referenced object That's it.

And the cache ObjectFactory code is as follows:

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

So the cached ObjectFactory object is actually a lamda expression, and the real access to the early exposed reference object is actually realized through the getEarlyBeanReference method.

getEarlyBeanReference method:

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 implementation of getEarlyBeanReference is to call the getEarlyBeanReference method of all SmartInstantiationAwareBeanPostProcessors.

The aforementioned AnnotationAwareAspectJAutoProxyCreator class implements the SmartInstantiationAwareBeanPostProcessor interface, which is implemented in the parent class:

@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. As mentioned earlier, this method is a method to obtain a dynamic proxy. If necessary, it will be proxied, such as transaction annotations or custom AOP aspects, which will be completed when exposed in the early stage. Dynamic proxy.

Now I finally figured out that what was exposed early may be a proxy object, and it was finally obtained through the getEarlyBeanReference method of the AnnotationAwareAspectJAutoProxyCreator class.

However, AsyncAnnotationBeanPostProcessor does not implement SmartInstantiationAwareBeanPostProcessor, that is, at the stage of obtaining early objects, AsyncAnnotationBeanPostProcessor will not be called to process @Async annotations.

Why can't Spring solve the @Async annotation encountering a circular dependency?

Here we take the previous example as an example, AService is annotated with @Async, AService is created first, and BService is found to be referenced, then BService will be created, when AService is found to be referenced during Service creation, then it will pass AnnotationAwareAspectJAutoProxyCreator This The getEarlyBeanReference method implemented by the class obtains the early reference object of AService. At this time, the early reference object may be proxied, depending on whether AService needs to be proxied, but it must not be a proxy for processing @Async annotations, as mentioned above.

So after BService is created, it is injected into AService, and then AService will continue to process. As mentioned earlier, when the initialization phase is completed, the postProcessAfterInitialization method of all BeanPostProcessor implementations will be called. Then 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.

  • The result processed by AnnotationAwareAspectJAutoProxyCreator will be passed to AsyncAnnotationBeanPostProcessor as an input parameter, which is how the applyBeanPostProcessorsAfterInitialization method is implemented

AnnotationAwareAspectJAutoProxyCreator callback: You will find that the AService object has been referenced early, and nothing will be processed, and the object AService will be returned directly

AsyncAnnotationBeanPostProcessor callback: If the @Async annotation is found in the AService class, it will dynamically proxy the object returned by AnnotationAwareAspectJAutoProxyCreator, and then return the dynamic proxy object.

After this callback, has the problem been found? The object exposed early may be AService itself or the proxy object of AService, and it is realized through the AnnotationAwareAspectJAutoProxyCreator object, but through the callback of AsyncAnnotationBeanPostProcessor, the AService object will be dynamically proxied, which causes the object exposed early by AService to be the same as the last If the completely created objects are not the same, then something must be wrong. How can the same Bean have two different objects in a Spring, so a BeanCurrentlyInCreationException will be thrown. The code for this judgment logic is as follows:

if (earlySingletonExposure) {
  // 获取到早期暴露出去的对象
  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
      // 早期暴露的对象不为null,说明出现了循环依赖
      if (exposedObject == bean) {
          // 这个判断的意思就是指 postProcessAfterInitialization 回调没有进行动态代理,如果没有那么就将早期暴露出去的对象赋值给最终暴露(生成)出去的对象,
          // 这样就实现了早期暴露出去的对象和最终生成的对象是同一个了
          // 但是一旦 postProcessAfterInitialization 回调生成了动态代理 ,那么就不会走这,也就是加了@Aysnc注解,是不会走这的
          exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
               // allowRawInjectionDespiteWrapping 默认是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()) {
                   //抛出异常
                   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.");
               }
      }
   }
}

Therefore, the reason why the @Async annotation encounters a circular dependency and Spring cannot solve it is because the @Aysnc annotation will make the final created Bean not the same object as the earlier exposed Bean, so an error will be reported.

How to solve the circular dependency exception?

There are many ways to solve this problem

1. Adjust the dependencies between objects to fundamentally eliminate circular dependencies. Without circular dependencies, there will be no early exposure. Then there will be no problems

2. Without using the @Async annotation, you can implement asynchrony through the thread pool yourself, so that without the @Async annotation, the proxy object will not be generated at the end, and it will not be different from the object exposed earlier

3. You can add @Lazy annotation to the field of circular dependency injection

@Component
public class AService {
    @Resource
    @Lazy
    private BService bService;

    @Async
    public void save() {

    }
}

4. It can be seen from the above source code comment that the exception is thrown, when allowRawInjectionDespiteWrapping is true, the else if will not go, and the exception will not be thrown, so it can be solved by setting allowRawInjectionDespiteWrapping to true The problem of error, the code is as follows:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
    }
    
}

Although this setting can solve the problem of error reporting, it is not recommended, because this setting allows the early injected object to be different from the final created object, and may cause the final generated object to not be dynamically proxied.

Guess you like

Origin blog.csdn.net/agonie201218/article/details/131686383