文章目录
🔄 现实生活类比:开餐厅的过程
假设你是一个餐厅老板,你有两个大厨 A 和 B,他们互相配合做菜:
- A 需要 B 提供酱料
- B 需要 A 提供食材
- 但 A 和 B 都必须先开工,才能完成各自的任务。
如果他们都等对方的食材/酱料准备好,餐厅就会卡死,无法开张!(就像 Bean 创建时循环依赖)
于是,你(Spring 容器)设计了一个聪明的解决方案:
- 先让 A 和 B 都开始工作,即使他们还没完全准备好。
- 让他们先把“半成品”存放在一个共享架子上,这样可以让对方提前拿到需要的东西。
- 等到他们各自的任务完成后,再正式交付最终产品。
这个 共享架子,就是 Spring 三级缓存的核心机制!🔍
💡 结合到 Spring 三级缓存
现实餐厅 | Spring 三级缓存 |
---|---|
A 和 B 互相依赖,但必须同时开始工作 | A 依赖 B,B 依赖 A,必须同时创建 |
共享架子,存放半成品,方便对方使用 | 二级缓存(earlySingletonObjects),存放半成品 Bean |
如果需要特别处理(如食材加工),可以先放入工厂区 | 三级缓存(singletonFactories),存放 Bean 工厂 |
🛠️ Spring 解决循环依赖的步骤
假设 A 和 B 互相依赖(Setter 方式),Spring 解决循环依赖的过程如下:
1️⃣ Spring 开始创建 A
- 发现 A 依赖 B,于是暂停 A 的创建。
- 先把 A 的“工厂”(可以用来创建 A 的工具)放入
singletonFactories
(三级缓存)。
2️⃣ Spring 开始创建 B
- 发现 B 依赖 A,Spring 先去缓存里找 A。
- 此时 A 还没完全创建,但 Spring 可以从三级缓存里找到 A 的工厂,并提前创建 A 的半成品,然后存入
earlySingletonObjects
(二级缓存)。 - B 现在可以正常获取 A 了,继续完成 B 的创建。
3️⃣ B 创建完成后,回过头来继续创建 A
✅ 最终,A 和 B 都成功创建,没有死锁!
📌 三级缓存的作用
缓存 | 作用 | 何时使用 |
---|---|---|
一级缓存(singletonObjects) | 存放完全初始化的 Bean | Bean 创建完成后 |
二级缓存(earlySingletonObjects) | 存放提前暴露的半成品 Bean | 解决循环依赖时(但不支持代理) |
三级缓存(singletonFactories) | 存放Bean 创建工厂 | 用于创建代理对象(如 AOP) |
如果有 AOP 代理,Spring 会先从三级缓存拿出工厂,创建代理对象,再放入二级缓存,保证最终拿到的 Bean 是代理对象,而不是普通对象。
❓ 为什么不用两级缓存,而要三级缓存?
-
无法支持 AOP 代理
。
🔚 结论
- Spring 通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)来解决 Setter 方式的循环依赖。
- 一级缓存存放完全创建好的 Bean,二级缓存存放提前暴露的半成品,三级缓存存放 Bean 工厂(支持 AOP 代理)。
- 如果 Bean 需要 AOP 代理,Spring 先从三级缓存获取工厂,创建代理对象,然后存入二级缓存,最终返回代理对象。
- 构造器循环依赖无法通过三级缓存解决,因为 Spring 不能提前暴露半成品。
📌 总结一句话: Spring 通过共享“半成品”机制(三级缓存),让 A 和 B 在创建过程中互相提前看到对方,避免循环等待,从而成功创建!🎯