前言
用了Spring这么多年,以为我对循环依赖的问题已经了如指掌,但是现实还是给我了一巴掌。
最近线上服务报了错误:
Bean with name xx has been injected into other beans [ x ] 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.
Exception是BeanCurrentlyInCreationException,看起来是个循环依赖的问题,但是日志的报错是我从来没见过的。
正常的循环依赖
1 |
|
如上,我们创建了两个Service,并且通过构造函数的方式注入。
启动时,Spring会报错:
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?
并且还会贴心的会你画个图:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| a defined in file [A.class]
↑ ↓
| b defined in file [B.class]
└─────┘
这种也是我理解中的循环依赖问题,解决方案也是比较简单,我们换成set方法注入,或者成员变量注入就行。
复现上文的异常
上文的异常,我一开始以为是AOP导致的问题,于是在A上加了个Aop的方法,发现Aop能够很好的解决这种循环依赖。
解决方案是三级缓存:
参考文章: https://segmentfault.com/a/1190000039134606
那我就疑惑了,到底是什么情况导致的呢?
后来搜到了文章:https://segmentfault.com/a/1190000018835760
发现@Async可以复现,于是我修改了代码:
1 |
|
发现确实可以复现了,这就暴露出我的知识盲区了,难道@Async不是通过Aop去解决的吗?
为什么@Aspect注解切的方法,不会报循环依赖,但是@Async的方法会呢?
带着疑惑,我又尝试了@Transectional会不会导致循环依赖问题,发现并不会。
所以,为啥@Async如此特殊呢?
对比
于是我得对比,@Async和@Transectional在实现上的区别
为此,我们把A的方法声明称这样:
1 |
|
同时配上一个手写的Aspect:
1 |
|
在DefaultAdvisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法中
我们可以查到这个方法所有的Advisor。
可以看到一共有三个
- ExposeInvocationInterceptor:先不管,和我们的业务无关
- BeanFactoryTransactionAttributeSourceAdvisor:就是我们@Transactional的相关Aop
- AspectA:自定义的Aop
可以发现,并没有@Async相关的Aop代码。
而通过对DefaultAdvisorChainFactory的调用链,可以分析到,这里的执行,对原来类的Aop织入,其实在
1 | doCreateBean# |
的逻辑中已经生成
所以也就导致
1 | if (earlySingletonExposure) { |
那为什么加了@Async就会不一样呢?
通过上文,我们知道了@Async的逻辑和传统的Aop逻辑不太一样。
@Async是通过AsyncAnnotationBeanPostProcessor去织入自己的逻辑。
这个BeanPostProcessor在applyBeanPostProcessorsAfterInitialization
方法中被调用
导致生成的是一个新的代理Bean,和原来的Bean就会不一样,也就是走到了抛出异常的逻辑。
总结
还是不能想当然,Aop是一个广泛的概念,但是在Spring中,其实还是冗余了不同的实现。
并不是所有加了Annotation的实现,都是一样的。
有可能是Aspect的实现,也有可能是类似BeanPostProcessor的实现。
需要看源码具体原因具体分析。