Spring 中引入第三级缓存singletonFactories的目的主要是为了解决循环依赖问题。
当 bean A 依赖 bean B,而 bean B 同样依赖 bean A 时,就产生了循环依赖。此时,如果简单地将二者的实例相互注入,就会陷入无限递归。
为解决这个问题,Spring 引入了三级缓存:
- singletonObjects:用于普通的依赖查找,存放完全初始化好的 bean 实例。
- earlySingletonObjects:用于解决循环依赖,将未完全初始化的 bean 实例提前暴露,以满足循环依赖的依赖查找。
- singletonFactories:用于解决循环依赖,当发现依赖循环时,会通过 ObjectFactory 进行懒加载,创建一个临时的 bean 实例至 earlySingletonObjects,继续完成后续的依赖注入。
当发现循环依赖时:
- 从 singletonFactories 获取对应的 ObjectFactory。
- 通过 ObjectFactory 进行懒加载,创建一个临时 bean 实例至 earlySingletonObjects。
- 将 earlySingletonObjects 中的临时实例注入至依赖它的 bean。
- 完成所有 bean 的初始化后,从 earlySingletonObjects 中清除临时实例,将完全初始化后的实例存入 singletonObjects。
- 后续的依赖查找直接从 singletonObjects 中获取实例。
二级缓存的引入为 Spring 容器带来了解决循环依赖问题的能力,使依赖注入的过程更加灵活可靠。但同时也增加了容器对 bean 生命周期管理的难度,它需要在适当的时机将 bean 的实例添加至二级缓存与清除二级缓存。
- 使依赖注入的目标 bean 可以在完全初始化之前被依赖。
在创建 bean 的过程中,如果发现存在循环依赖,即其他 bean 依赖当前正在创建的 bean,此时就需要将当前 bean 提前暴露出来,让依赖它的 bean 可以使用。 这就是 earlySingletonObjects 存在的首要原因。它存储了还未完全初始化的 bean,但已经可以被依赖使用。如果不使用二级缓存,依赖当前 bean 的其他 bean 在初始化时无法获得它的实例,这会导致依赖注入失败,产生循环依赖问题。
- 在目标 bean 完全初始化后,需要从二级缓存中清除它的实例。
earlySingletonObjects 中存储的实例还未完全初始化,只是为了解决循环依赖问题暴露出来的临时实例。 一旦 bean 的全部初始化逻辑执行完成,需要将其从 earlySingletonObjects 移除,然后放入 singletonObjects。由于 earlySingletonObjects 中的实例未完全初始化,它们不应该被当做 bean 的最终实例使用。所以在初始化完成后,必须将其清除,这是使用二级缓存的第二个原因。如果不在初始化完成后清除 earlySingletonObjects 中的实例,其中的实例仍然可能在系统中流转,这会产生难以预料的问题。
@Component
public class A {
@Autowired
private B b;
}
@Component
@Aspect
public class B { // B 会被代理
@Autowired
private A a;
}
- 创建A
- A 依赖 B,开始创建 B
- 此时由于 @Aspect 注解,B 会被代理,所以获取到的不是 B 的真实对象
- 代理对象也依赖 A,但是 A 此时还未完全初始化
- 又开始创建 A,产生循环。
而引入三级缓存 singletonFactories 后,问题就解决了:
- 创建 A,注册到 earlySingletonObjects(二级缓存)
- A 依赖 B,开始创建 B
- B 会被代理,代理对象也依赖 A
- 此时代理对象从 singletonFactories(三级缓存) 获取一个可以创建 A 的工厂
- 代理对象使用工厂创建 A,完成初始化
- A 从 singletonFactories(三级缓存 ) 获取 B 的工厂,创建 B,完成初始化
所以,singletonFactories (三级缓存 )不仅封装了对其他 bean 的依赖,也隐藏了 bean 是否被代理的状态。 无论 B 是否被代理,A 都可以通过 singletonFactories (三级缓存 )获取 B 的工厂,然后再获取 B 的代理对象或目标对象。这也体现了高内聚和解耦的思想,通过 singletonFactories 隔离了 bean 与代理对象之间的依赖关系。所以,总结来说,三级缓存不仅可以解决循环依赖的问题,也可以很好的解决代理造成的依赖问题。这也是 Spring 框架采用三级缓存的另一个目的。