以下以 Bean A 和 Bean B 互相依赖为例,结合源码和流程图,详细说明 Bean 的创建过程与三级缓存的交互。
1. Bean 的完整生命周期(简化版)
2. 三级缓存的作用
缓存名称 | 存储内容 | 目的 |
---|---|---|
singletonObjects | 完全初始化好的 Bean(成品) | 直接对外提供可用的 Bean |
earlySingletonObjects | 未初始化完成的 Bean(半成品,早期引用) | 临时存储,供其他 Bean 提前引用,避免循环依赖卡死 |
singletonFactories | 创建 Bean 的 ObjectFactory (工厂对象) | 延迟生成早期引用(支持 AOP 代理等动态扩展) |
3. 详细流程(以 Bean A → Bean B → Bean A 循环依赖为例)
步骤 1:开始创建 Bean A
- 入口:调用
getBean("a")
。 - 阶段:实例化(
createBeanInstance()
)。// 反射调用无参构造函数创建原始对象 A a = new A();
- 此时状态:
a
是原始对象,属性b
为null
。- 未存入任何缓存。
步骤 2:提前暴露 Bean A 的工厂
- 操作:将 Bean A 的
ObjectFactory
存入 三级缓存(singletonFactories
)。// AbstractAutowireCapableBeanFactory.doCreateBean() addSingletonFactory("a", () -> getEarlyBeanReference("a", mbd, a));
- 目的:允许其他 Bean(如 Bean B)在依赖注入时,通过工厂获取 Bean A 的早期引用(可能是代理对象)。
步骤 3:填充 Bean A 的属性(发现依赖 Bean B)
- 操作:调用
populateBean("a")
,检测到a
依赖b
。 - 触发:调用
getBean("b")
创建 Bean B。
步骤 4:开始创建 Bean B
- 入口:调用
getBean("b")
。 - 阶段:实例化(
createBeanInstance()
)。B b = new B(); // 反射创建原始对象
- 此时状态:
b
是原始对象,属性a
为null
。- 未存入任何缓存。
步骤 5:提前暴露 Bean B 的工厂
- 操作:将 Bean B 的
ObjectFactory
存入 三级缓存。addSingletonFactory("b", () -> getEarlyBeanReference("b", mbd, b));
步骤 6:填充 Bean B 的属性(发现依赖 Bean A)
- 操作:调用
populateBean("b")
,检测到b
依赖a
。 - 触发:再次调用
getBean("a")
。
步骤 7:再次获取 Bean A(解决循环依赖)
- 检查一级缓存:
singletonObjects
中没有 Bean A。 - 检查二级缓存:
earlySingletonObjects
中没有 Bean A。 - 检查三级缓存:找到 Bean A 的
ObjectFactory
。 - 操作:调用
ObjectFactory.getObject()
,实际执行getEarlyBeanReference()
。// 生成早期引用(可能是代理对象) Object earlyA = getEarlyBeanReference("a", mbd, a);
- 若 Bean A 需要 AOP 代理:此时生成代理对象(如
A$$EnhancerBySpringCGLIB
)。 - 若无需代理:直接返回原始对象
a
。
- 若 Bean A 需要 AOP 代理:此时生成代理对象(如
- 升级缓存:将早期引用
earlyA
存入 二级缓存(earlySingletonObjects
),并清除三级缓存中的工厂。this.earlySingletonObjects.put("a", earlyA); this.singletonFactories.remove("a");
- 结果:Bean B 获得 Bean A 的早期引用(
earlyA
),完成属性注入b.setA(earlyA)
。
步骤 8:完成 Bean B 的初始化
- 初始化:执行
initializeBean("b")
(包括@PostConstruct
、AOP 代理等)。 - 存入一级缓存:将完全初始化的 Bean B 存入
singletonObjects
。registerSingleton("b", b);
步骤 9:回到 Bean A 的属性填充
- 操作:Bean A 获得完全初始化的 Bean B(
b
),完成属性注入a.setB(b)
。
步骤 10:完成 Bean A 的初始化
- 初始化:执行
initializeBean("a")
。- 若 Bean A 需要代理:此时生成代理对象
proxyA
(覆盖原始对象a
)。 - 若无需代理:直接使用原始对象
a
。
- 若 Bean A 需要代理:此时生成代理对象
- 存入一级缓存:将最终 Bean A(可能是代理对象)存入
singletonObjects
,并清除二、三级缓存。registerSingleton("a", a); // 或 proxyA
4. 三级缓存的交互总结
5. 关键设计思想
- 提前暴露半成品:允许未初始化的 Bean 被其他 Bean 引用,打破循环依赖的死锁。
- 动态代理兼容:通过
ObjectFactory
延迟生成早期引用,确保 AOP 代理逻辑正确执行。 - 缓存层级隔离:
- 一级缓存:存放完全可用的 Bean(安全)。
- 二级缓存:临时存放早期引用(加速依赖查找)。
- 三级缓存:存放工厂,支持动态扩展(如代理)。
6. Bean的创建是否都需要经历三级缓存
1. 必须经历三级缓存的场景
条件:当 Bean 是单例(Singleton)且 存在循环依赖(通过属性注入)时,Spring 会通过三级缓存机制解决依赖问题。此时 Bean 的创建流程如下:
graph LR
A[实例化 Bean] --> B[注册 ObjectFactory 到三级缓存]
B --> C[填充属性(触发循环依赖)]
C --> D[从三级缓存升级到二级缓存]
D --> E[完成初始化后存入一级缓存]
示例:Bean A 和 Bean B 互相依赖
- 步骤:
- 创建 Bean A 时,实例化后注册
ObjectFactory
到三级缓存。 - 注入 Bean B 时触发 B 的创建。
- 创建 Bean B 时,实例化后注册
ObjectFactory
到三级缓存。 - 注入 Bean A 时,通过三级缓存获取 A 的早期引用(升级到二级缓存)。
- Bean B 完成初始化后存入一级缓存。
- Bean A 完成初始化后存入一级缓存。
- 创建 Bean A 时,实例化后注册
2. 不经历三级缓存的场景
场景 1:无循环依赖的普通 Bean
条件:Bean 是单例,且 没有循环依赖(如 Bean C 无依赖或依赖已存在的 Bean)。
流程:
关键点:
- 不需要提前暴露早期引用,直接跳过三级缓存。
- 例如:Bean C 依赖的 Bean D 已经在一级缓存中,则直接注入 D,无需触发缓存升级。
场景 2:构造器注入的循环依赖
条件:Bean 使用 构造器注入 导致循环依赖。
结果:
Spring 无法解决构造器注入的循环依赖,直接抛出 BeanCurrentlyInCreationException
。
原因:
- 构造器注入需在实例化阶段完成依赖注入,此时 Bean 尚未创建完成,无法提前暴露到三级缓存。
场景 3:原型(Prototype)作用域的 Bean
条件:Bean 的作用域为 prototype
。
结果:
Spring 不缓存原型 Bean,每次请求都创建新实例,因此:
- 不涉及三级缓存。
- 循环依赖直接报错(原型 Bean 不支持循环依赖)。
场景 4:无需代理的 Bean
条件:Bean 不需要 AOP 代理,且无循环依赖。
流程:
直接通过反射创建原始对象,完成初始化后存入一级缓存,全程不涉及三级缓存。
3. 三级缓存的触发条件总结
条件 | 是否触发三级缓存 | 示例场景 |
---|---|---|
单例 + 属性注入 + 循环依赖 | ✔️ | Bean A → Bean B → Bean A |
单例 + 无循环依赖 | ❌ | 普通 Service 类 |
单例 + 构造器注入循环依赖 | ❌(直接报错) | 构造器注入导致循环依赖 |
原型作用域 Bean | ❌ | 每次请求新实例 |
需要代理但无循环依赖 | ❌ | 独立 Bean 使用 @Transactional |
4. 源码验证
(1) 三级缓存的注册逻辑
在 AbstractAutowireCapableBeanFactory.doCreateBean()
中,只有满足以下条件时才会注册 ObjectFactory
:
// 条件:单例 + 允许循环引用 + Bean 正在创建中
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
(2) 无循环依赖时的跳过逻辑
若 Bean 无循环依赖,则不会触发从三级缓存获取早期引用的代码:
// DefaultSingletonBeanRegistry.getSingleton()
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 无循环依赖时,不会进入此分支
if (isSingletonCurrentlyInCreation(beanName)) {
// 从三级缓存获取早期引用的逻辑...
}
}
总结
- 必须经历三级缓存:仅当单例 Bean 存在属性注入的循环依赖时。
- 不经历三级缓存:
- 无循环依赖的单例 Bean。
- 构造器注入的循环依赖(直接报错)。
- 原型作用域 Bean。
- 不需要代理的普通 Bean。
7. 常见问题解答
Q1:为什么需要三级缓存?二级缓存不够吗?
- 三级缓存的核心价值:解耦 Bean 的创建 和 代理的生成。
- 如果只有二级缓存:
代理逻辑需在实例化后立即执行(违反 Spring 的设计原则,代理应在初始化阶段完成)。 - 三级缓存通过
ObjectFactory
延迟代理生成,确保代理逻辑在正确的时机执行。
- 如果只有二级缓存:
Q2:构造器注入为何无法解决循环依赖?
- 根本原因:构造器注入需在实例化阶段完成依赖注入,而实例化尚未完成时无法提前暴露对象(三级缓存机制无法介入)。
Q3:为什么二级缓存叫 earlySingletonObjects
?
- 它存储的是“早期单例对象”(尚未完成初始化),与一级缓存的“完全体单例”区分。
通过以上流程,可以清晰理解 Spring 如何通过三级缓存协作,在保证单例完整性的前提下,优雅解决循环依赖问题。