@Async注解的坑

背景

前段时间,一个同事小姐姐跟我说她的项目起不来了,让我帮忙看一下,本着助人为乐的精神,这个忙肯定要去帮。

于是,我在她的控制台发现了如下的异常信息:

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)
 ```
看到`BeanCurrentlyInCreationException`这个异常,我的第一反应是出现了循环依赖的问题。但是仔细一想,Spring不是已经解决了循环依赖的问题么,怎么还报这个错。于是,我就询问小姐姐改了什么东西,她说在方法上加了`@Async`注解。

这里我模拟一下当时的代码,`AService` 和 `BService` 相互引用,`AService`的 `save()` 方法加了 `@Async` 注解。
```java
@Component
public class AService {
    @Resource
    private BService bService;

    @Async
    public void save() {

    }
}

@Component
public class BService {

    @Resource
    private AService aService;

}

也就是这段代码会报`BeanCurrentlyInCreationException`异常,难道是`@Async`注解遇上循环依赖的时候,Spring无法解决?为了验证这个猜想,我将`@Async`注解去掉之后,再次启动项目,项目成功起来了。于是基本可以得出结论,那就是`@Async`注解遇上循环依赖的时候,Spring的确无法解决。

虽然问题的原因已经找到了,但是又引出以下几个问题:

  • `@Async`注解是如何起作用的?
  • 为什么`@Async`注解遇上循环依赖,Spring无法解决?
  • 出现循环依赖异常之后如何解决?

@Async注解是如何起作用的?

`@Async`注解起作用是靠`AsyncAnnotationBeanPostProcessor`这个类实现的,这个类会处理`@Async`注解。`AsyncAnnotationBeanPostProcessor`这个类的对象是由`@EnableAsync`注解放入到Spring容器的,这也是为什么需要使用`@EnableAsync`注解来激活让`@Async`注解起作用的根本原因。

AsyncAnnotationBeanPostProcessor类体系
AsyncAnnotationBeanPostProcessor类体系

这个类实现了 `BeanPostProcessor` 接口,实现了 `postProcessAfterInitialization` 方法,是在其父类`AbstractAdvisingBeanPostProcessor` 中实现的,也就是说当Bean的初始化阶段完成之后会回调 `AsyncAnnotationBeanPostProcessor` 的 `postProcessAfterInitialization` 方法。之所以会回调,是因为在Bean的生命周期中,当Bean初始化完成之后,会回调所有的 `BeanPostProcessor` 的 `postProcessAfterInitialization` 方法,代码如下:

@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` 对于 `postProcessAfterInitialization` 方法实现:

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

该方法的主要作用是用来对方法入参的对象进行动态代理的,当入参的对象的类加了`@Async`注解,那么这个方法就会对这个对象进行动态代理,最后会返回入参对象的代理对象出去。至于如何判断方法有没有加`@Async`注解,是靠 `isEligible(bean, beanName)` 来判断的。由于这段代码牵扯到动态代理底层的知识,这里就不详细展开了。

AsyncAnnotationBeanPostProcessor作用
AsyncAnnotationBeanPostProcessor作用

综上所述,可以得出一个结论,那就是当Bean创建过程中初始化阶段完成之后,会调用 `AsyncAnnotationBeanPostProcessor` 的 `postProcessAfterInitialization` 的方法,对加了`@Async`注解的类的对象进行动态代理,然后返回一个代理对象回去。

虽然这里我们得出`@Async`注解的作用是依靠动态代理实现的,但是这里其实又引发了另一个问题,那就是事务注解`@Transactional`又或者是自定义的AOP切面,他们也都是通过动态代理实现的,为什么使用这些的时候,没见抛出循环依赖的异常?难道他们的实现跟`@Async`注解的实现不一样?不错,还真的不太一样,请继续往下看。

AOP是如何实现的?

我们都知道AOP是依靠动态代理实现的,而且是在Bean的生命周期中起作用,具体是靠 `AnnotationAwareAspectJAutoProxyCreator` 这个类实现的,这个类会在Bean的生命周期中去处理切面,事务注解,然后生成动态代理。这个类的对象在容器启动的时候,就会被自动注入到Spring容器中。

`AnnotationAwareAspectJAutoProxyCreator` 也实现了`BeanPostProcessor`,也实现了 `postProcessAfterInitialization` 方法。

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

通过 `wrapIfNecessary` 方法就会对Bean进行动态代理,如果你的Bean需要被动态代理的话。

AnnotationAwareAspectJAutoProxyCreator作用
AnnotationAwareAspectJAutoProxyCreator作用

也就说,AOP和`@Async`注解虽然底层都是动态代理,但是具体实现的类是不一样的。一般的AOP或者事务的动态代理是依靠 `AnnotationAwareAspectJAutoProxyCreator` 实现的,而`@Async`是依靠 `AsyncAnnotationBeanPostProcessor` 实现的,并且都是在初始化完成之后起作用,这也就是`@Async`注解和AOP之间的主要区别,也就是处理的类不一样。

Spring是如何解决循环依赖的

Spring在解决循环依赖的时候,是依靠三级缓存来实现的。

简单来说,通过缓存正在创建的对象对应的`ObjectFactory`对象,可以获取到正在创建的对象的早期引用的对象,当出现循环依赖的时候,由于对象没创建完,就可以通过获取早期引用的对象注入就行了。

而缓存`ObjectFactory`代码如下:

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

所以缓存的`ObjectFactory`对象其实是一个lamda表达式,真正获取早期暴露的引用对象其实就是通过 `getEarlyBeanReference` 方法来实现的。

`getEarlyBeanReference` 方法:

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

`getEarlyBeanReference` 实现是调用所有的 `SmartInstantiationAwareBeanPostProcessor` 的 `getEarlyBeanReference` 方法。

而前面提到的 `AnnotationAwareAspectJAutoProxyCreator` 这个类就实现了 `SmartInstantiationAwareBeanPostProcessor` 接口,是在父类中实现的:

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

这个方法最后会调用 `wrapIfNecessary` 方法,前面也说过,这个方法是获取动态代理的方法,如果需要的话就会代理,比如事务注解又或者是自定义的AOP切面,在早期暴露的时候,就会完成动态代理。

这下终于弄清楚了,早期暴露出去的原来可能是个代理对象,而且最终是通过`AnnotationAwareAspectJAutoProxyCreator`这个类的`getEarlyBeanReference`方法获取的。

但是`AsyncAnnotationBeanPostProcessor`并没有实现`SmartInstantiationAwareBeanPostProcessor`,也就是在获取早期对象这一阶段,并不会调`AsyncAnnotationBeanPostProcessor`处理`@Async`注解。

为什么@Async注解遇上循环依赖,Spring无法解决?

这里我们就拿前面的例子来说,`AService`加了`@Async`注解,`AService`先创建,发现引用了`BService`,那么`BService`就会去创建,当Service创建的过程中发现引用了`AService`,那么就会通过`AnnotationAwareAspectJAutoProxyCreator` 这个类实现的 `getEarlyBeanReference` 方法获取`AService`的早期引用对象,此时这个早期引用对象可能会被代理,取决于`AService`是否需要被代理,但是一定不是处理`@Async`注解的代理,原因前面也说过。

于是`BService`创建好之后,注入给了`AService`,那么`AService`会继续往下处理,前面说过,当初始化阶段完成之后,会调用所有的`BeanPostProcessor`的实现的 `postProcessAfterInitialization` 方法。于是就会回调依次回调 `AnnotationAwareAspectJAutoProxyCreator` 和 `AsyncAnnotationBeanPostProcessor` 的 `postProcessAfterInitialization` 方法实现。

这段回调有两个细节:

  • `AnnotationAwareAspectJAutoProxyCreator` 先执行,`AsyncAnnotationBeanPostProcessor` 后执行,因为 `AnnotationAwareAspectJAutoProxyCreator` 在前面。

image

  • `AnnotationAwareAspectJAutoProxyCreator`处理的结果会当入参传递给 `AsyncAnnotationBeanPostProcessor`,`applyBeanPostProcessorsAfterInitialization`方法就是这么实现的

`AnnotationAwareAspectJAutoProxyCreator`回调:会发现`AService`对象已经被早期引用了,什么都不处理,直接把对象`AService`给返回

`AsyncAnnotationBeanPostProcessor`回调:发现`AService`类中加了`@Async`注解,那么就会对`AnnotationAwareAspectJAutoProxyCreator`返回的对象进行动态代理,然后返回了动态代理对象。

这段回调完,是不是已经发现了问题。早期暴露出去的对象,可能是`AService`本身或者是`AService`的代理对象,而且是通过`AnnotationAwareAspectJAutoProxyCreator`对象实现的,但是通过`AsyncAnnotationBeanPostProcessor`的回调,会对`AService`对象进行动态代理,这就导致`AService`早期暴露出去的对象跟最后完全创造出来的对象不是同一个,那么肯定就不对了。同一个Bean在一个Spring中怎么能存在两个不同的对象呢,于是就会抛出`BeanCurrentlyInCreationException`异常,这段判断逻辑的代码如下:

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

所以,之所以`@Async`注解遇上循环依赖,Spring无法解决,是因为`@Aysnc`注解会使得最终创建出来的Bean,跟早期暴露出去的Bean不是同一个对象,所以就会报错。

出现循环依赖异常之后如何解决?

解决这个问题的方法很多

1、调整对象间的依赖关系,从根本上杜绝循环依赖,没有循环依赖,就没有早期暴露这么一说,那么就不会出现问题

2、不使用`@Async`注解,可以自己通过线程池实现异步,这样没有`@Async`注解,就不会在最后生成代理对象,导致早期暴露的出去的对象不一样

3、可以在循环依赖注入的字段上加`@Lazy`注解

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

    @Async
    public void save() {

    }
}

4、从上面的那段判断抛异常的源码注释可以看出,当`allowRawInjectionDespiteWrapping`为`true`的时候,就不会走那个`else if`,也就不会抛出异常,所以可以通过将`allowRawInjectionDespiteWrapping`设置成`true`来解决报错的问题,代码如下:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

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

}

虽然这样设置能解决报错的问题,但是并不推荐,因为这样设置就允许早期注入的对象和最终创建出来的对象是不一样,并且可能会导致最终生成的对象没有被动态代理。

除非注明,否则均为哦豁原创文章,转载必须以链接形式标明本文链接
guest

0 评论
最多投票
最新 最旧
内联反馈
查看所有评论