Spring 解决单例模式下的 setter 循环依赖问题,主要通过三级缓存机制来确保循环依赖能被正确处理。具体来说,Spring 使用三级缓存来解决在单例模式下两个 Bean 之间相互依赖的问题。通过三级缓存,Spring 可以暴露出正在创建的 Bean,并通过特殊机制注入已经创建但尚未完全初始化的 Bean,从而避免无限递归和 StackOverflowError
。
三级缓存的概念
-
一级缓存(
earlySingletonObjects
):存储已经实例化的 Bean 对象,但还没有经过完整初始化的对象。即它们是一个“半初始化”的 Bean,通常用于循环依赖的处理。 -
二级缓存(
singletonObjects
):存储所有已经完全初始化并且能够提供给外部使用的 Bean 实例。只有在完成了 Bean 的所有初始化(包括属性注入、@PostConstruct
方法等)后,才会放到这里。 -
三级缓存(
singletonFactories
):存储的是 Bean 的工厂(ObjectFactory
),该工厂可以用来创建实例化的 Bean。当循环依赖发生时,Spring 会将 Bean 的实例工厂放入三级缓存中,而不是将一个完整的实例放入缓存,直到该 Bean 完成初始化。
具体步骤与循环依赖解决过程
-
实例化 Bean(空 Bean 对象): Spring 在创建 Bean 的时候,首先实例化一个空的 Bean 对象,然后将这个空的 Bean 放入 一级缓存 中。此时,该 Bean 还没有进行属性赋值,也没有进行任何初始化。
-
属性赋值(依赖注入): 在属性赋值阶段,Spring 会检查该 Bean 的依赖。如果 Bean 存在循环依赖,Spring 会暴露出当前正在创建的 Bean(即处于一级缓存中的“空 Bean”)给需要它的其他 Bean,从而允许这些 Bean 继续进行注入操作。这样,其他 Bean 就可以注入一个“半初始化”的 Bean 来解决依赖。
-
初始化 Bean: 一旦属性赋值完成,Spring 就会开始对 Bean 进行初始化操作(比如执行
@PostConstruct
方法,或者执行配置类中的初始化方法等)。此时,Bean 被认为是完全初始化完成的,并且会被放入 二级缓存 中,表示它已经可以完全使用。 -
注入依赖(循环依赖处理): 如果在属性赋值过程中发现当前 Bean 存在循环依赖,Spring 会通过 三级缓存 来解决这个问题。三级缓存存放的是 Bean 的创建工厂,在这种情况下,Spring 会从三级缓存中获取已完成初始化的 Bean,来注入依赖。
三级缓存如何处理循环依赖
假设有两个 Bean A 和 B,它们之间存在循环依赖:
- Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。
如果没有三级缓存机制,Spring 会陷入递归调用中,无法完成循环依赖的处理。三级缓存的处理方式如下:
-
实例化 Bean A:Spring 开始创建 Bean A,先将其放入 一级缓存。
-
实例化 Bean B:然后 Spring 需要实例化 Bean B,发现 Bean B 依赖于 Bean A,于是从 一级缓存 中取出 Bean A(它是一个“空对象”,即还未初始化完成)。Spring 使用这个“空对象”来继续初始化 Bean B。
-
属性赋值阶段: 在 Bean B 的初始化过程中,Spring 会把 Bean A 暴露给 Bean B 的属性注入,保证循环依赖能够被解决。此时,Bean A 还没有完全初始化,但它已经可以被暴露作为一个临时的依赖注入对象。
-
初始化 Bean B 完成:当 Bean B 的初始化完成后,Spring 会将其放入 二级缓存 中,表示它已经准备好被使用。
-
完成 Bean A 的初始化:在 Bean A 的初始化过程中,它也需要依赖 Bean B。当 Spring 发现这个循环依赖时,它会从 二级缓存 中获取已经初始化完成的 Bean B,从而解决依赖注入。
-
最终 Bean A 完成初始化:当 Bean A 初始化完成后,它也会被放入 二级缓存 中。
三级缓存的作用
- 一级缓存:存放的是处于创建过程中的“空 Bean”,这有助于解决循环依赖,允许 Spring 在尚未完全初始化 Bean 时就暴露其引用。
- 二级缓存:存放已经完全初始化并可以使用的 Bean 实例,这保证了 Bean 在整个生命周期内的唯一性,并且可以用于依赖注入。
- 三级缓存:存放 Bean 的实例工厂,解决了循环依赖的问题。通过延迟 Bean 的初始化,Spring 能够避免在 Bean 完成初始化之前就进行依赖注入。
总结
三级缓存机制是 Spring 用来解决单例模式下循环依赖问题的一种重要方式。通过一级缓存、二级缓存和三级缓存的协同工作,Spring 能够在存在循环依赖时,保证 Bean 的正常创建和依赖注入,避免了无限递归和错误。这个机制不仅保证了应用程序的稳定性,也提高了框架的灵活性和可扩展性。