5.0 Spring循环依赖问题
一、什么是循环依赖
就是A对象依赖了B对象,B对象依赖了A对象。
A a = new A();
B b = new B();
a.b = b;
b.a = a;
如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。
为什么在Spring中循环依赖就是一个问题了? 因为在Spring中,一个对象会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题
出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的 场景则需要程序员来解决。
A类Bean创建-->依赖了B属性-->触发B类Bean创建--->B依赖了A属性--->需要A类Bean(但A类Bean还在 创建过程中)
从而导致A类Bean创建不出来,B类Bean也创建不出来。
在spring中三级缓存机制帮助开发者解决部分循环依赖的问题
二、 三级缓存
三级缓存是通用的叫法。
一级缓存为 singletonObjects:经历了完整生命周期的bean对象
二级缓存为 earlySingletonObjects: Bean的生命周期还没走完Bean对象
三级缓存为 singletonFactories: 用来创建早期bean对象的 工厂ObjectFactory
三、解决循环依赖思路
上文分析得到,之所以产生循环依赖的问题,主要是:
A创建时--->需要B---->B去创建--->需要A,从而产生了循环
1. 解决一般的循环依赖(二级缓存)
核心思想就是打破这个循环,加个中间人(缓存)
- A创建时—>A的原始对象放入缓存
- A需要B
- B去创建,B需要A,则从缓存中取
- B创建完成
- 将B赋值给A对象
- A,B对象都创建完成
2. 解决AOP对象的循环依赖(三级缓存)
在上述过程中,只需要一个缓存就能解决循环依赖了,拿为什么Spring中还有SingletonFactory
呢?
因为B依赖的A和最终的A不是同一个对象。
基于上面的场景
- A的原始对象注入给B属性之后,A的原始对象进行了AOP产生一个代理对象,对于A对象而言,它的Bean对象其实是AOP之后的代理对象
- 而B的a属性对应的不是AOP之后的代理对象,这就产生了冲突
AOP的流程:
A类--->生成原始对象-->属性注入-->基于切面生成一个代理对象-->把代理对象放入singletonObject单例池
循环依赖属于IOC的范畴,这个部分和AOP有了交汇,解决方式就是利用第三级缓存singletonFactory。
首先,singletonFactories
中存的是某个beanName
对应的ObjectFactory
,在bean的生命周期中, 生成完原始对象之后,就会构造一个ObjectFactory
存入singletonFactories
中。这个ObjectFactory
是一个函数式接口:() -> getEarlyBeanReference(beanName, mbd, bean)
,存入时不会执行
// 该方法会去执行SmartInstantiationAwareBeanPostProcessor中的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;
}
// 核心调用方法逻辑 AbstractAutoProxyCreator#getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
上面的AOP逻辑为:
- 首先得到一个
cachekey
,cachekey
就是beanName
- 然后把
beanName
和bean(这是原始对象)存入earlyProxyReferences
中 - 调用
wrapIfNecessary
进行AOP
,得到一个代理对象。
解决AOP对象的循环依赖流程:
- 从
singletonFactories
根据beanName得到一个ObjectFactory
, - 然后执行
ObjectFactory
,也就是执行getEarlyBeanReference
方法,此时会得到一个A原始对象经过AOP之后的代理对象, - 然后把该代理对象放入
earlySingletonObjects
中,此时,我们只得到了A原始对象的代理对 象,这个对象还不完整,因为A原始对象还没有进行属性填充,假设现在有其他对象依 赖了A,那么则可以从earlySingletonObjects
中得到A原始对象的代理对象了,并且是A的同一个代 理对象。 - 当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP, 而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了
- 那么怎 么判断一个对象是否经历过了AOP呢?会利用上文提到的
earlyProxyReferences
,在AbstractAutoProxyCreator#postProcessAfterInitialization
方法中,会去判断当前beanName是 否在earlyProxyReferences
,如果在则表示已经提前进行过AOP了,无需再次进行AOP。 对于A而言,进行了AOP的判断后,以及BeanPostProcessor
的执行之后,就需要把A对应的对象放 入singletonObjects
中了,但是我们知道,应该是要把A的代理对象放入singletonObjects
中,所以 此时需要从earlySingletonObjects
中得到代理对象,然后放入singletonObjects
中。
四、总结三级缓存
- singletonObjects:缓存经过了完整生命周期的bean
- earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖, 就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入 earlySingletonObjects。代理对象所代理的原始对象也是没有经过完整生命周期的
- singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean 的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖 (当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行 Lambda表达式得到一个对象,并把得到的对象放入二级缓存((如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象))。
- 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP 了。
五、反向分析singletonFactories
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的生命周期的设计。