关于后端:Async注解的坑小心

48次阅读

共计 10327 个字符,预计需要花费 26 分钟才能阅读完成。

大家好。
背景
前段时间,一个共事小姐姐跟我说她的我的项目起不来了,让我帮忙看一下,本着助人为乐的精力,这个忙必定要去帮。
于是,我在她的控制台发现了如下的异样信息:
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 注解。
@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

类体系
这个类实现了 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 作用
综上所述,能够得出一个论断,那就是当 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 作用
也就说,AOP 和 @Async 注解尽管底层都是动静代理,然而具体实现的类是不一样的。个别的 AOP 或者事务的动静代理是依附 AnnotationAwareAspectJAutoProxyCreator 实现的,而 @Async 是依附 AsyncAnnotationBeanPostProcessor 实现的,并且都是在初始化实现之后起作用,这也就是 @Async 注解和 AOP 之间的次要区别,也就是解决的类不一样。
Spring 是如何解决循环依赖的
Spring 在解决循环依赖的时候,是依附三级缓存来实现的。我已经写过一篇对于三级缓存的文章,如果有不分明的小伙伴能够 关注微信公众号 三友的 java 日记,回复 循环依赖 即可获取原文链接,本文也算是这篇三级缓存文章的续作。
简略来说,通过缓存正在创立的对象对应的 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 在后面。

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

}
}
复制代码
所以,之所以 @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);
}

}
复制代码
尽管这样设置能解决报错的问题,然而并不举荐,因为这样设置就容许晚期注入的对象和最终创立进去的对象是不一样,并且可能会导致最终生成的对象没有被动静代理。
如果感觉这篇文章对你有所帮忙,还请帮忙点赞、在看、转发给更多的人,非常感谢!

正文完
 0