Spring 处理循环依赖的机制笔记
1. Spring Bean 生命周期概述
在 Spring 框架中,一个 bean 的生命周期大致包括以下几个步骤:
- 实例化:通过构造函数创建 bean 实例。
- 属性填充:注入 bean 的依赖。
- 初始化前的处理:调用
BeanPostProcessor
的postProcessBeforeInitialization
方法。 - 初始化:调用
init-method
或者InitializingBean
接口的afterPropertiesSet
方法。 - 初始化后的处理:调用
BeanPostProcessor
的postProcessAfterInitialization
方法。 - 最终放入 singletonObjects:完全初始化后的 bean 放入
singletonObjects
缓存。
2. 循环依赖的概念
当存在循环依赖时,意味着两个或多个 bean 相互依赖对方。例如,bean A 依赖 bean B,而 bean B 也依赖 bean A。
3. Spring 如何处理循环依赖
在处理循环依赖时,Spring 通过提前暴露部分初始化的 bean 实例,使得每个 bean 都能在初始化过程中获得所需的依赖。
具体步骤:
-
实例化第一个 bean:
- 假设有两个 bean A 和 B,它们相互依赖。
- Spring 首先实例化 bean A。
-
注入依赖:
- 在注入 bean B 到 bean A 时,发现 bean B 尚未初始化。
- 此时 Spring 会尝试从缓存中获取 bean B。
-
创建 bean B 的实例:
- 由于 bean B 也依赖 bean A,Spring 会在
singletonFactories
中创建 bean B 的工厂,然后创建 bean B 的实例。
- 由于 bean B 也依赖 bean A,Spring 会在
-
注入依赖:
- 此时 bean B 的实例需要 bean A,但由于 bean A 已经在
earlySingletonObjects
中存在,所以 Spring 可以从earlySingletonObjects
中获取 bean A 的部分初始化版本,并注入到 bean B 中。
- 此时 bean B 的实例需要 bean A,但由于 bean A 已经在
-
完成 bean B 的初始化:
- 完成 bean B 的所有初始化步骤,并将其放入
singletonObjects
。
- 完成 bean B 的所有初始化步骤,并将其放入
-
完成 bean A 的初始化:
- 现在 bean A 有了完全初始化的 bean B,可以继续完成自己的初始化,并最终放入
singletonObjects
。
- 现在 bean A 有了完全初始化的 bean B,可以继续完成自己的初始化,并最终放入
4. 三级缓存的角色
- singletonObjects:存放完全初始化的 bean。
- earlySingletonObjects:存放部分初始化的 bean,即已经实例化并注入了依赖,但还没有调用
postProcessBeforeInitialization
和init-method
。 - singletonFactories:存放创建部分初始化 bean 的工厂。
5. 为什么没有再出现循环依赖的问题
-
部分初始化的 bean:
- 在注入依赖时,Spring 会使用部分初始化的 bean,这意味着 bean 在注入时已经完成了实例化和依赖注入,但还没有调用
init-method
或者BeanPostProcessor
的postProcessBeforeInitialization
方法。
- 在注入依赖时,Spring 会使用部分初始化的 bean,这意味着 bean 在注入时已经完成了实例化和依赖注入,但还没有调用
-
逐步推进:
- 通过这种方式,Spring 保证了每个 bean 都能逐步推进其初始化过程。一旦某个 bean 完成了初始化,它就会被放入
singletonObjects
,这样其他 bean 在后续的初始化过程中就可以使用已经完全初始化的 bean。
- 通过这种方式,Spring 保证了每个 bean 都能逐步推进其初始化过程。一旦某个 bean 完成了初始化,它就会被放入
-
避免无限循环:
- 通过在部分初始化阶段就注入 bean,Spring 避免了无限循环的发生。每个 bean 都有机会在初始化过程中获得所需的依赖,即使这些依赖一开始只是部分初始化的。
6. 示例
假设我们有两个 bean A 和 B,它们之间存在循环依赖关系:
public class A {
private B b;
public A(B b) {
this.b = b;
}
public void init() {
// 进行一些初始化操作
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
public void init() {
// 进行一些初始化操作
}
}
在这个例子中:
- 实例化 A:Spring 首先实例化 bean A。
- 注入 B 到 A:在注入 bean B 时,发现 B 尚未初始化。此时 Spring 会尝试初始化 B。
- 实例化 B:Spring 开始实例化 bean B。
- 注入 A 到 B:在注入 bean A 时,发现 A 已经部分初始化了(即已经被实例化,但尚未完成其他初始化步骤)。此时 Spring 会从
earlySingletonObjects
中获取 A 的部分初始化版本,并注入到 B 中。 - 完成 B 的初始化:B 现在有了部分初始化的 A,可以继续自己的初始化过程,直到完全初始化,并放入
singletonObjects
。 - 完成 A 的初始化:A 现在有了完全初始化的 B,可以继续自己的初始化过程,直到完全初始化,并放入
singletonObjects
。
通过这种方式,即使存在循环依赖,Spring 也能够确保每个 bean 都能够完成其初始化过程,而不会陷入无限循环。