这里知识点避开不@Aysnc注解标注的Bean的创建代理的时机。
@EnableAsync开启时它会向容器内注入AsyncAnnotationBeanPostProcessor,它是一个BeanPostProcessor,实现了postProcessAfterInitialization方法。此处我们看代码,创建代理的动作在抽象父类AbstractAdvisingBeanPostProcessor上:
// @since 3.2 注意:@EnableAsync在Spring3.1后出现 // 继承自ProxyProcessorSupport,所以具有动态代理相关属性~ 方便创建代理对象 public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor { // 这里会缓存所有被处理的Bean~~~ eligible:合适的 private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256); //postProcessBeforeInitialization方法什么不做~ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } // 关键是这里。当Bean初始化完成后这里会执行,这里会决策看看要不要对此Bean创建代理对象再返回~~~ @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (this.advisor == null || bean instanceof AopInfrastructureBean) { // Ignore AOP infrastructure such as scoped proxies. return bean; } // 如果此Bean已经被代理了(比如已经被事务那边给代理了~~) if (bean instanceof Advised) { Advised advised = (Advised) bean; // 此处拿的是AopUtils.getTargetClass(bean)目标对象,做最终的判断 // isEligible()是否合适的判断方法 是本文最重要的一个方法,下文解释~ // 此处还有个小细节:isFrozen为false也就是还没被冻结的时候,就只向里面添加一个切面接口 并不要自己再创建代理对象了 省事 if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) { // Add our local Advisor to the existing proxy's Advisor chain... // beforeExistingAdvisors决定这该advisor最先执行还是最后执行 // 此处的advisor为:AsyncAnnotationAdvisor 它切入Class和Method标注有@Aysnc注解的地方~~~ if (this.beforeExistingAdvisors) { advised.addAdvisor(0, this.advisor); } else { advised.addAdvisor(this.advisor); } return bean; } } // 若不是代理对象,此处就要下手了~~~~isEligible() 这个方法特别重要 if (isEligible(bean, beanName)) { // copy属性 proxyFactory.copyFrom(this); 生成一个新的ProxyFactory ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName); // 如果没有强制采用CGLIB 去探测它的接口~ if (!proxyFactory.isProxyTargetClass()) { evaluateProxyInterfaces(bean.getClass(), proxyFactory); } // 添加进此切面~~ 最终为它创建一个getProxy 代理对象 proxyFactory.addAdvisor(this.advisor); //customize交给子类复写(实际子类目前都没有复写~) customizeProxyFactory(proxyFactory); return proxyFactory.getProxy(getProxyClassLoader()); } // No proxy needed. return bean; } // 我们发现BeanName最终其实是没有用到的~~~ // 但是子类AbstractBeanFactoryAwareAdvisingPostProcessor是用到了的 没有做什么 可以忽略~~~ protected boolean isEligible(Object bean, String beanName) { return isEligible(bean.getClass()); } protected boolean isEligible(Class<?> targetClass) { // 首次进来eligible的值肯定为null~~~ Boolean eligible = this.eligibleBeans.get(targetClass); if (eligible != null) { return eligible; } // 如果根本就没有配置advisor 也就不用看了~ if (this.advisor == null) { return false; } // 最关键的就是canApply这个方法,如果AsyncAnnotationAdvisor 能切进它 那这里就是true // 本例中方法标注有@Aysnc注解,所以铁定是能被切入的 返回true继续上面方法体的内容 eligible = AopUtils.canApply(this.advisor, targetClass); this.eligibleBeans.put(targetClass, eligible); return eligible; } ... }
经此一役,根本原理是只要能被切面AsyncAnnotationAdvisor切入(即只需要类/方法有标注@Async注解即可)的Bean最终都会生成一个代理对象(若已经是代理对象里,只需要加入该切面即可了~)赋值给上面的exposedObject作为返回最终add进Spring容器内~
针对上面的步骤,为了辅助理解,我尝试总结文字描述如下:
- context.getBean(A)开始创建A,A实例化完成后给A的依赖属性b开始赋值~
- context.getBean(B)开始创建B,B实例化完成后给B的依赖属性a开始赋值~
- 重点:此时因为A支持循环依赖,所以会执行A的getEarlyBeanReference方法得到它的早期引用。而执行getEarlyBeanReference()的时候因为@Async根本还没执行,所以最终返回的仍旧是原始对象的地址
- B完成初始化、完成属性的赋值,此时属性field持有的是Bean A原始类型的引用~
- 完成了A的属性的赋值(此时已持有B的实例的引用),继续执行初始化方法initializeBean(...),在此处会解析@Aysnc注解,从而生成一个代理对象,所以最终exposedObject是一个代理对象(而非原始对象)最终加入到容器里~
- 尴尬场面出现了:B引用的属性A是个原始对象,而此处准备return的实例A竟然是个代理对象,也就是说B引用的并非是最终对象(不是最终放进容器里的对象)
- 执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,so最终就抛错了~
此步骤是由我个人即兴总结,希望能帮助到小伙伴们理解。若有不对的地方,还请指出让帮忙我斧正
解决方案
通过上面分析,知道了问题的根本原因,现总结出解决上述新问题的解决方案,可分为下面三种方案:
- 把allowRawInjectionDespiteWrapping设置为true
- 使用@Lazy或者@ComponentScan(lazyInit = true)解决
- 不要让@Async的Bean参与循环依赖
1、把allowRawInjectionDespiteWrapping设置为true:
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true); } }
这样配置后,容器启动将不再报错了,但是但是但是:Bean A的@Aysnc方法将不起作用了,因为Bean B里面依赖的a是个原始对象,所以它最终没法执行异步操作(即使容器内的a是个代理对象):
需要注意的是:但此时候Spring容器里面的Bean A是Proxy代理对象的~~~
但是此种情况若是正常依赖(非循环依赖)的a,注入的是代理对象,@Async异步依旧是会生效的哦~
这种解决方式一方面没有达到真正的目的(毕竟Bean A上的@Aysnc没有生效)。
由于它只对循环依赖内的Bean受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案,所以我个人对此方案的态度是不建议,也不反对。
2、使用@Lazy或者@ComponentScan(lazyInit = true)解决
本处以使用@Lazy为例:(强烈不建议使用@ComponentScan(lazyInit = true)作用范围太广了,容易产生误伤)
@Service public class B implements BInterface { @Lazy @Autowired private AInterface a; @Override public void funB() { System.out.println("线程名称:" + Thread.currentThread().getName()); a.funA(); } }
注意此@Lazy注解加的位置,因为a最终会是@Async的代理对象,所以在@Autowired它的地方加
另外,若不存在循环依赖而是直接引用a,是不用加@Lazy的
只需要在Bean b的依赖属性上加上@Lazy即可。(因为是B希望依赖进来的是最终的代理对象进来,所以B加上即可,A上并不需要加)
最终的结果让人满意:启动正常,并且@Async异步效果也生效了,因此本方案我是推荐的
但是需要稍微注意的是:此种情况下B里持有A的引用和Spring容器里的A并不是同一个,如下图:
两处实例a的地址值是不一样的,容器内的是$Proxy@6914,B持有的是$Proxy@5899。
关于@Autowired和@Lazy的联合使用为何是此现象,其实@Lazy的代理对象是由ContextAnnotationAutowireCandidateResolver生成的,具体参考博文:【小家Spring】Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理


