@Async情况下的循环依赖解析
问题场景演示
上一节分析过,就算发生循环依赖且提前AOP,最终也可以通过二级缓存把最终的代理对象放入二级缓存:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
exposedObject:初始化后得到的对象
bean:一开始实例化的原始对象
大部分情况下exposedObject == bean,什么情况下exposedObject != bean呢?
先看初始化后方法:
aop其实只是其中一个BeanPostProcessor的功能,并且它的实现是:
-
发生了循环依赖(提前AOP,二级缓存中有值),返回普通对象,此时exposedObject == bean
-
没有发生循环依赖(没有提前AOP,二级缓存中无值),则进行AOP返回代理对象
此时二级缓存中无值,且getSingleton方法的入参allowEarlyReference=false,返回是null
根本不会进入这段逻辑:
除开aop的BeanPostProcessor会处理传进来Bean之外,其他BeanPostProcessor也会处理,其他BeanPostProcessor有可能会直接替换传进来的Bean。
比如:
@ComponentScan(value = "com.yth.application")
@EnableAspectJAutoProxy
@EnableAsync//开启异步执行功能
public class AppConfig {}
@Component
public class AService {
@Autowired
private BService bService;
@Async//开启异步
public void test() {
System.out.println(bService);
}
}
@Component
public class BService {
@Autowired
private AService aService;
public void test() {
System.out.println(aService);
}
}
启动,抛异常了,就是刚刚讲的这段代码的else:
此时exposedObject != bean,因为初始化后的时候把exposedObject改了
按照之前分析,有循环依赖,所以AOP的实现中返回的还是原始对象
但是走到AsyncAnnotationBeanPostProcessor,要支持异步就会生成另外一个代理对象(代理对象中,通过单独开一个线程执行相应方法达到异步执行效果)。
所以导致exposedObject != bean
本质原因其实是
- AsyncAnnotationBeanPostProcessor,没有实现SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference方法,不支持提前获取引用,即不支持处理循环依赖的情况
- 普通BeanPostProcessor正常执行了初始化后增强替换了原始对象,与提前初始化getEarlyBeanReference生成的对象发生冲突
那为什么要抛异常呢?
之前的案例,发生循环依赖以后:
B注入的A,是二级缓存中的值,getEarlyBeanReference得到的对象,当前场景下就是aop的代理对象(AsyncAnnotationBeanPostProcessor没有提供这个功能)
而A经过初始化后,拿到的是AsyncAnnotationBeanPostProcessor生成的代理对象,如果不做处理,放入单例池的就是这个代理对象
放入单例池的和赋值给B的(二级缓存中的对象)不一样,所以要抛异常。
解决方案
那怎么办呢?
1. @Lazy懒注入,打破循环依赖
加了@Lazy就不报错了,为啥?
因为之前出现一系列问题,是因为发了循环依赖,创建A的过程需要B,创建B的过程又需要A
但是加了@Lazy注解以后,创建A,填充属性B的时候,发现是@Lazy懒注入,则生成一个代理对象直接赋值了(懒注入原理见前面依赖注入章节),不会再去找B、创建B了,A直接能正常走完后续生命周期流程,最终放入单例池。
而到单例B创建的时候,填充属性A时直接能从单例池拿到A,B也能正常走完后续生命周期流程。
当A真正用到属性B,执行其方法的时候,才会去找真正的Bean,此时A早已经在单例池中了。
即使用 @Lazy 直接打破了循环依赖,根本不会发生循环依赖!!!!从根本解决了问题。
2. 根据顺序判断到底谁发生了循环依赖
之前给A加了@Async注解,A本身是发生循环依赖的,所以会报错
而对于B本身来说,其实并没有发生循环依赖,所以@Async放到B上不会报错
如果把@Async从AService去掉,放到BService上:
@Component
public class AService {
@Autowired
private BService bService;
public void test() {
System.out.println(bService);
}
}
@Component
public class BService {
@Autowired
private AService aService;
@Async
public void test() {
System.out.println(aService);
}
}
不会报错为啥?
主要原因是顺序,AService是先创建的
先创建A,先给A填充属性B,发现B没有,会去创建B,B再去填充属性A
当B去填充A的时候,对于A来说,A发生了循环依赖,所以这里A提前AOP拿到代理对象给B注入
而B本身并没有发生循环依赖,后续会正常的走完流程,在步骤2.5中,先根据aop的到一个代理对象,在根据Async给AOP的代理对象再生成一个代理对象…最终放到单例池中,是OK的
B成为完全Bean给A赋值后,A继续走后续流程,由于A没有被@Async标记,所以初始化后不会生成新的代理对象(不会与AOP提前生成的代理对象冲突),返回的是原始Bean,按照之前分析最终会用二级缓存的值替换放入单例池中,最终A、B放入单例池的,与他们相互引用的都是同一个对象,正常的。
3. 改造AsyncAnnotationBeanPostProcessor支持提前引用
其实本质原因是AsyncAnnotationBeanPostProcessor不支持循环依赖的情况,所以直接从根本解决。
改造AsyncAnnotationBeanPostProcessor,让他实现SmartInstantiationAwareBeanPostProcessor接口,实现getEarlyBeanReference方法,支持提前生成代理对象,并重写postProcessAfterInitialization方法,模仿AOP的实现,没有发生循环依赖,再生成代理对象,否则返回原来的Bean,即原始对象。(注意AsyncAnnotationBeanPostProcessor的顺序应该是放在AOP实现之后的)
为什么可行?感兴趣参考:参考链接
原型Bean情况下的循环依赖解析
A、B都是原型情况,其实解决不了(也不是解决不了,可能没什么意义或者应用场景):
- 创建A的时候,给A的属性B填充值,B没有要去创建
- B创建的过程中,给B的属性A填充值,但是A是原型的…拿不到刚刚创建的原型A,又回new一个A…
- 会一直循环下去
所以 A -> B -> A1 -> B1 -> A2 -> B2 …
一个是原型另一个是单例没关系:
- 原型A -> 单例B -> 原型A1 -> 单例B结束(这种情况不会发生,因为容器启动的时候就会把所有单例先实例化)
- 单例A -> 原型B -> 单例A结束
源码分析
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
这里会检查多例Bean是不是已经在创建中了,如果是,说明发生了循环依赖,会抛异常
org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
//返回指定的原型bean当前是否正在创建中(在当前线程内)。
//prototypesCurrentlyInCreation是ThreadLocal
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
使用@Lazy懒注入其实可以解决,启动的时候不会报错,但每次调用代理对象方法的时候,都会重新创建一个Bean,可能没什么意义。
构造方法导致的循环依赖解析
@Component
public class AService {
public AService(BService bService) {
this.bService = bService;
}
private BService bService;
public void test() {
System.out.println(bService);
}
}
@Component
public class BService {
public BService(AService aService) {
this.aService = aService;
}
private AService aService;
public void test() {
System.out.println(aService);
}
}
构造方法中A依赖B,B依赖A
直接报错,因为此时连对象都实例化不了,半成品都生成不了
实例化A 需要B -> 实例化B 需要A -> …
怎么解决?用@Lazy!!!打破循环依赖!!
@Transaction情况下的循环依赖解析
之前@Async给A会报错,换成@Transaction呢?
@ComponentScan(value = "com.yth.application")
@EnableAspectJAutoProxy
@EnableTransactionManagement//开启事务管理
public class AppConfig {}
不会报错,具体不演示了。
因为@EnableTransactionManagement注解,并没有向容器里添加BeanPostProcessor,而是Advisor,通过AOP实现的事务管理(AOP的通知,AOP原理以后讲)
自己注入自己的情况下的循环依赖解析
@Component
public class AService {
@Autowired
private AService aService;
}
创建A -> 注入A -> 单例池没有 -> 发现循环依赖 -> 二级缓存也没有 -> 利用三级缓存提前获取引用,存入二级缓存,并返回 -> 赋值给属性A -> A继续走后续生命周期流程…一样的
总结
至此,循环依赖告一段落,总结一下三级缓存:
- singletonObjects:缓存经过了完整生命周期的bean
- earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入earlySingletonObjects,但是不管怎么样,就是是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects我们就可以统一认为是未经过完整生命周期的bean。
- singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖(当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行Lambda表达式得到一个对象,并把得到的对象放入二级缓存(如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象)。
- 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。
反向分析一下singletonFactories
为什么需要singletonFactories?假设没有singletonFactories,只有earlySingletonObjects,earlySingletonObjects是二级缓存,它内部存储的是未经过完整生命周期的bean对象,Spring原有的流程是出现了循环依赖的情况下:
- 先从singletonFactories中拿到lambda表达式,这里肯定是能拿到的,因为每个bean实例化之后,依赖注入之前,就会生成一个lambda表达式放入singletonFactories中
- 执行lambda表达式,得到结果,将结果放入earlySingletonObjects中
那如果没有singletonFactories,该如何把原始对象或AOP之后的代理对象放入earlySingletonObjects中呢?何时放入呢?
首先,将原始对象或AOP之后的代理对象放入earlySingletonObjects中的有两种:
- 实例化之后,依赖注入之前:如果是这样,那么对于每个bean而言,都是在依赖注入之前会去进行AOP,这是不符合bean生命周期步骤的设计的。
- 真正发现某个bean出现了循环依赖时:按现在Spring源码的流程来说,就是getSingleton(String beanName, boolean allowEarlyReference)中,是在这个方法中判断出来了当前获取的这个bean在创建中,就表示获取的这个bean出现了循环依赖,那在这个方法中该如何拿到原始对象呢?更加重要的是,该如何拿到AOP之后的代理对象呢?难道在这个方法中去循环调用BeanPostProcessor的初始化后的方法吗?不是做不到,不太合适,代码太丑。最关键的是在这个方法中该如何拿到原始对象呢?还是得需要一个Map,预先把这个Bean实例化后的对象存在这个Map中,那这样的话还不如直接用第一种方案,但是第一种又直接打破了Bean生命周期的设计。
所以,我们可以发现,现在Spring所用的singletonFactories,为了调和不同的情况,在singletonFactories中存的是lambda表达式,这样的话,只有在出现了循环依赖的情况,才会执行lambda表达式,才会进行AOP,也就说只有在出现了循环依赖的情况下才会打破Bean生命周期的设计,如果一个Bean没有出现循环依赖,那么它还是遵守了Bean的生命周期的设计的。