Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
问题
都知道 Spring 通过三级缓存来解决循环依赖的问题。但是是不是必须三级缓存才能解决,二级缓存不能解决吗?
要分析是不是可以去掉其中一级缓存,就先过一遍 Spring 是如何通过三级缓存来解决循环依赖的。
对于这部分内容,需要了解 Bean 的生命周期。
解决
在 Spring 生命周期的 populateBean 部分,如果发现该属性(成员变量)还未在 spring 中生成,则会跑去生成属性对象实例
我们可以看到填充属性的时候,spring 会提前将已经实例化的 bean 通过 ObjectFactory 半成品暴露出去,为什么称为半成品是因为这时候的 bean 对象实例化,但是未进行属性填充,是一个不完整的 bean 实例对象
spring 利用 singletonObjects, earlySingletonObjects, singletonFactories 三级缓存去解决的,所说的缓存其实也就是三个 Map
可以看到三级缓存各自保存的对象,这里重点关注二级缓存 earlySingletonObjects 和三级缓存 singletonFactory,一级缓存可以进行忽略。前面我们讲过先实例化的 bean 会通过 ObjectFactory 半成品提前暴露在三级缓存中
singletonFactory 是传入的一个匿名内部类,调用 ObjectFactory.getObject() 最终会调用 getEarlyBeanReference 方法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。
我们假设现在有这样的场景 AService 依赖 BService,BService 依赖 AService
- AService 首先实例化,实例化通过 ObjectFactory 半成品暴露在三级缓存中
- 填充属性 BService,发现 BService 还未进行过加载,就会先去加载 BService
- 再加载 BService 的过程中,实例化,也通过 ObjectFactory 半成品暴露在三级缓存
- BService 填充属性 AService 的时候,这时候能够从三级缓存中拿到半成品的 ObjectFactory (AService)
拿到 ObjectFactory 对象后,调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法,getEarlyBeanReference 这个方法主要逻辑大概描述下,如果 bean 被 AOP 切面代理则返回的是 beanProxy 对象,如果未被代理则返回的是原 bean 实例,这时我们会发现能够拿到 bean 实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects 中,🔔(补充内容见下部分),然后 B 创建完毕,放入一级缓存中,再回到 A 的创建过程,去一级缓存拿到了B,然后 A 也创建完成,A 最后也放入到一级缓存,最终两个都创建完成;
🔔:而此时 B 注入的是一个半成品的实例A对象,不过随着 B 初始化完成后,A 会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。
过程如下:
原因
可以看到,循环依赖下,有没有代理情况下的区别就在:
1 |
|
在循环依赖发生的情况下 B 中的 A 赋值时:
- 无代理:getObject 直接返回原来的 Bean
- 有代理:getObject 返回的是代理对象
然后都放到二级缓存。
为什么要三级缓存?
假设去掉三级缓存
去掉三级缓存之后,Bean 直接创建
earlySingletonObjects
, 看着好像也可以。如果有代理的时候,在
earlySingletonObjects
直接放代理对象就行了。但是会导致一个问题:意味着 Bean 在构造完后就创建代理对象,这样违背了 Spring 设计原则。Spring 结合 AOP 跟 Bean 的生命周期,是在 Bean 创建完全之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,在这个后置处理的 postProcessAfterInitialization 方法中对初始化后的 Bean 完成 AOP 代理。如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理,但是没有出现循环依赖的情况下,设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
这么一想,是不是会对 Bean 的生命周期有影响。
同样,先创建
singletonFactory
的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。假设去掉二级缓存
如果去掉了二级缓存,则需要直接在
singletonFactory.getObject()
阶段初始化完毕,并放到一级缓存中。
B 和 C 都依赖 A那有这么一种场景,B 和 C 都依赖了 A。
要知道在有代理的情况下
singletonFactory.getObject()
获取的是代理对象。多次获取代理对象不同
而多次调用
singletonFactory.getObject()
返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。那如果获取 B 到之后直接放到一级缓存,然后 C 再获取呢?
…
一级缓存放的是已经初始化完毕的 Bean,要知道 A 依赖了 B 和 C ,A 这时候还没有初始化完毕。
参考文章
Spring 为何需要三级缓存解决循环依赖,而不是二级缓存 - 半分、 - 博客园 (cnblogs.com)
彻底搞懂Spring之三级缓存解决循环依赖问题 - 知乎 (zhihu.com)
【彻底搞懂】Spring之三级缓存解决循环依赖问题-腾讯云开发者社区-腾讯云 (tencent.com)