Spring 为何需要三级缓存解决循环依赖,而不是二级缓存

Spring 为何需要三级缓存解决循环依赖,而不是二级缓存

问题

都知道 Spring 通过三级缓存来解决循环依赖的问题。但是是不是必须三级缓存才能解决,二级缓存不能解决吗?
要分析是不是可以去掉其中一级缓存,就先过一遍 Spring 是如何通过三级缓存来解决循环依赖的。

对于这部分内容,需要了解 Bean 的生命周期。

解决

在 Spring 生命周期的 populateBean 部分,如果发现该属性(成员变量)还未在 spring 中生成,则会跑去生成属性对象实例

image-20231008150928305

我们可以看到填充属性的时候,spring 会提前将已经实例化的 bean 通过 ObjectFactory 半成品暴露出去,为什么称为半成品是因为这时候的 bean 对象实例化,但是未进行属性填充,是一个不完整的 bean 实例对象

image-20231008151216188

spring 利用 singletonObjects, earlySingletonObjects, singletonFactories 三级缓存去解决的,所说的缓存其实也就是三个 Map

image

可以看到三级缓存各自保存的对象,这里重点关注二级缓存 earlySingletonObjects 和三级缓存 singletonFactory,一级缓存可以进行忽略。前面我们讲过先实例化的 bean 会通过 ObjectFactory 半成品提前暴露在三级缓存中

image-20231008151721379

singletonFactory 是传入的一个匿名内部类,调用 ObjectFactory.getObject() 最终会调用 getEarlyBeanReference 方法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。

我们假设现在有这样的场景 AService 依赖 BService,BService 依赖 AService

  • AService 首先实例化,实例化通过 ObjectFactory 半成品暴露在三级缓存中
  • 填充属性 BService,发现 BService 还未进行过加载,就会先去加载 BService
  • 再加载 BService 的过程中,实例化,也通过 ObjectFactory 半成品暴露在三级缓存
  • BService 填充属性 AService 的时候,这时候能够从三级缓存中拿到半成品的 ObjectFactory (AService)

image-20231008190716923

拿到 ObjectFactory 对象后,调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法,getEarlyBeanReference 这个方法主要逻辑大概描述下,如果 bean 被 AOP 切面代理则返回的是 beanProxy 对象,如果未被代理则返回的是原 bean 实例,这时我们会发现能够拿到 bean 实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects 中,🔔(补充内容见下部分),然后 B 创建完毕,放入一级缓存中,再回到 A 的创建过程,去一级缓存拿到了B,然后 A 也创建完成,A 最后也放入到一级缓存,最终两个都创建完成;

🔔:而此时 B 注入的是一个半成品的实例A对象,不过随着 B 初始化完成后,A 会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。

过程如下:

循环依赖.drawio

原因

可以看到,循环依赖下,有没有代理情况下的区别就在:

1
singletonObject = singletonFactory.getObject();

在循环依赖发生的情况下 B 中的 A 赋值时:

  1. 无代理:getObject 直接返回原来的 Bean
  2. 有代理:getObject 返回的是代理对象

然后都放到二级缓存

为什么要三级缓存?

  1. 假设去掉三级缓存

    去掉三级缓存之后,Bean 直接创建 earlySingletonObjects, 看着好像也可以。

    如果有代理的时候,在 earlySingletonObjects 直接放代理对象就行了。

    但是会导致一个问题:意味着 Bean 在构造完后就创建代理对象,这样违背了 Spring 设计原则。Spring 结合 AOP 跟 Bean 的生命周期,是在 Bean 创建完全之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,在这个后置处理的 postProcessAfterInitialization 方法中对初始化后的 Bean 完成 AOP 代理。如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理,但是没有出现循环依赖的情况下,设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

    这么一想,是不是会对 Bean 的生命周期有影响。

    同样,先创建 singletonFactory 的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。

  2. 假设去掉二级缓存

    如果去掉了二级缓存,则需要直接在 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)

https://www.bilibili.com/video/BV1rt4y1u7q5

Spring循环依赖三级缓存是否可以减少为二级缓存?-CSDN博客


Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
https://wangtao.site/posts/95a885e4.html
作者
wt
发布于
2023年10月8日
许可协议