When the circular dependency encounters the @Async annotation, the project cannot be started

The following content is transferred from Shishan’s architecture notes

background

Some time ago, a colleague, a young lady, told me that her project could not be started, 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 being reported. So, I asked the young lady what she 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 the @Async annotation encounters a circular dependency, Spring cannot solve it? In order to verify this conjecture, after I removed the @Async annotation, I 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:

  1. How does the @Async annotation work?
  2. Why does @Async annotation encounter circular dependency, and Spring can't solve it?
  3. How to solve the circular dependency exception?

How does the @Async annotation work?

The @Async annotation is implemented by the AsyncAnnotationBeanPostProcessor class, which handles the @Async annotation. The object of the AsyncAnnotationBeanPostProcessor class 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, when 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 parameter 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

To sum up, 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 @Async annotation is realized by dynamic proxy, but another problem is caused here, that is, transaction annotation @Transactional or custom AOP aspect, they are also realized 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 the Bean. Specifically, it is implemented by the class AnnotationAwareAspectJAutoProxyCreator. This class will process aspects, transaction annotations, and generate dynamics in the life cycle of the Bean. acting. Objects of this class are 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 @Async annotation and AOP, that is, processing The classes are different.

How does Spring resolve circular dependencies?

When Spring resolves circular dependencies, it relies on the third-level cache.

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

Therefore, the cached ObjectFactory object is actually a lamda expression, and the actual acquisition of the reference object exposed early 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, it does not call AsyncAnnotationBeanPostProcessor to process @Async annotations.

Why does @Async annotation encounter circular dependency, and Spring can't solve it?

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, it will pass AnnotationAwareAspectJAutoProxyCreator. 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 the BService is created, it is injected into the AService, and then the 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:

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

  2. 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, then the object returned by AnnotationAwareAspectJAutoProxyCreator will be dynamically proxied, and then the dynamic proxy object will be returned.

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 one 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. The @Lazy annotation can be added to the field of circular dependency injection

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

    @Async
    public void save() {
    
    

    }
}
  1. As can be seen from the above source code comments for judging and throwing exceptions, when allowRawInjectionDespiteWrapping is true, the else if will not go, and no exceptions will be thrown, so you can solve the error by setting allowRawInjectionDespiteWrapping to true problem, 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.

おすすめ

転載: blog.csdn.net/qq_16607641/article/details/127265897