Spring如何解决循环依赖问题?
文章目录
- Spring如何解决循环依赖问题?
- 代码模拟
- 图示
- 结合Bean的生命周期分析
- 问题解决的关键
- 解决方案【三级缓存】
- 详细步骤
- 如下图所示:
- 为什么需要三级缓存?
- 一个场景:
- 现在我们再来走一遍这个场景:
- 总结:
代码模拟
@Component
class A{
@Autowired
private B b;
}
@Component
class B{
@Autowired
private A a;
}
图示
结合Bean的生命周期分析
- Spring扫描class得到BeanDefinition
- 根据bean的definition生成bean
- 推断构造方法(优先选无参)
- 根据构造方法通过反射创建对象(放入三级缓存lamda表达式后面详细讨论)
- 填充对象属性(依赖注入)
- 如果对象的某个方法被AOP了,那么则需要根据原对象生成代理对象
- 把最终生成的对象放进单例池中
在Spring中,bean的生命周期导致循环依赖问题
问题解决的关键
在Spring中,如果通过Set方式注入属性,【有参构造无法提前暴露对象,
不能解决循环依赖问题】那么对象的实例化和初始化就是分开的,实例化
完成的对象是不完整的对象,但是却可以之间给其他对象引用的,所以可
以在此增加一层缓存,把完成实例化但未初始化的对象提前暴露出去,让
其他对象能够进行引用,就解开了上图中的闭环问题。
解决方案【三级缓存】
- 一级缓存:singletonObjects 拥有完整生命周期的bean对象
- 二级缓存:earlySingletonObjects 半成品对象,如果提前进行了AOP,存放的就是半成品代理对象
- 三级缓存:singletonFactories 对象工厂,一个lamda表达式,用于回调是否生成代理对象还是原始对象
详细步骤
第1步,反射构造A对象的时候将对象放入三级缓存,存入的是一个未执行的lambda表达式,
之后填充属性需要B,就会从三级缓存找(没有找到)------>二级缓存(没有找到)------>一级缓存(没有找到)
最终都没有找到就去创建B,开始了B的生命周期,同样B填充属性需要依赖A,就会找三级缓存(找到)
执行三级缓存中A的lambda表达式获取一个对象。
并检测如果需要进行AOP就返回一个代理对象【不完整】,反之就返回一个普通对象。
之后lambda表达式执行完三级缓存删除,放入二级缓存【没有经过完整生命周期的对象A(或代理对象A)的引用。
最后当B的生命周期结束之后返回A的生命周期,A填充属性B,判断是否已经提前进行AOP并决定是否
需要进行AOP,发现二级缓存中含有A对象(或代理对象A)就将二级缓存删除,放入一级缓存中。
如下图所示:
为什么需要三级缓存?
只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean被AOP进行了切面代理的场景
三级缓存实现aop(代理对象实例化的时候,实例化对象是原始对象,若没有三级缓存,此时若根据
类名直接获取对象的话,获取的是原始对象,而我们想要的肯定是通过类名直接获取代理对象,所以
Spring在类加载过程中,直接将实例化的对象放入三级缓存中,从三级缓存中获取类对象的时候,判
断类是否被代理,若被代理则返回代理对象)
一个场景:
a对象依赖b,c对象,同时b,c依赖a对象;那么我们想一下在b对象注入a对象时没有在一级缓存里找到我
们的a的bean对象也没有在二级缓存找到我们的代理对象a,此时spring会在三级缓存找到存放着我们
的原始对象a的lambda表达式,spring执行该lambda表达式生成的代理对象a,此时b注入a完成其他步
骤正常执行,这里注意我们并没有一个完整的a的bean对象在我们的SingletonObjects里面,所以我们
的c对象在注入a对象时又会生成一个代理对象a,这个时候问题来了,代理对象a出现不一致的情况,
那么如何解决呢?这时候二级缓存的重要性体现出来了,我们将这个代理对象放earlySingletonObjects
二级缓存,key存放我们的beanName,value存放我们的代理对象。
现在我们再来走一遍这个场景:
a对象实例化好,在进行注入时发现依赖a,c两个对象,此时spring会去先创建b,c对象,b对象会去注入
a对象,会先去SingletonObjects一级缓存和earlySingletonObjects二级缓存找,发现没找到就去
SingletonFactories三级缓存,找到带有原始对象a的lambda表达式并执行该lambda表达式获得一个代
理对象a并放入二级缓存将该lambda在三级缓存删除,b对象的流程正常执行,此时c对象也需要注入
一个a对象,先去一级缓存找,没找到,再去二级缓存找找到了代理对象a,此时c对象也正常执行现
在a对象也能成功完成注入,spring的循环依赖已经解决。
总结:
- 只有一级三级缓存,加入二级缓存可避免lambda表达式重复生成代理对象,保证单例的Spring的bean。
- 如果只有一级二级缓存,三级缓存通过lambda表达式可灵活决定是否是需要提前进行aop并生成代理对象。
- 如果没有循环依赖,三级缓存的lambda表达式就不会执行,二级缓存也不会使用,不影响Spring的正常bean生命周期。
- 循环依赖两个核心
- 单例bean
- Set方式注入属性
- 具体实现细节可参考Spring源码