Spring 是如何解决循环依赖的
1. 场景设定 —— “A和B互相日”
- A依赖B,B也依赖A,两个SB在XML或注解里勾搭上了。
- 问题:Spring先造A还是先造B?不管先造谁,对方都他妈没出生,死循环!
2. 三级缓存到底是哪三级?
Spring内部有三个Map(缓存池),专门处理这种死锁:
- singletonFactories(一级缓存,工厂池):存的是半成品对象工厂(对象刚new出来,还没填属性)。
- earlySingletonObjects(二级缓存,半成品池):存从工厂池里拿到的半成品对象(可能被代理过)。
- singletonObjects(三级缓存,成品池):存完全初始化好的成品单例对象。
口诀:
“一厂二半三成品,对象升级靠缓存”
3. 解决流程 —— “套娃式创建”
以A和B互相依赖为例(假设都是单例,用属性注入):
-
Step1: 开始创建A
- 用反射无脑
new A()
→ 此时A是个空壳(属性全null)。 - 把A的对象工厂(一个能返回A的lambda)扔进一级缓存(singletonFactories)。
- 此时A的状态:半残次品(没属性,但工厂已注册)。
- 用反射无脑
-
Step2: 给A填充属性B
- Spring发现A依赖B → 去成品池(三级缓存)找B → 没有!
- 开始创建B:同样反射
new B()
→ B也是个空壳。 - 把B的工厂扔进一级缓存。
-
Step3: 给B填充属性A
- B需要A → 先去成品池找A → 没有!
- 去二级缓存找 → 也没有!
- 关键骚操作:从一级缓存(工厂池)找到A的工厂 → 调用
getObject()
,拿到A的半成品对象(此时A还是空壳)。 - 把A的半成品扔进二级缓存(防止重复创建),同时从一级缓存移除A的工厂。
- 把A的半成品塞给B → B完成属性填充,变成成品B。
- 把B扔进三级缓存(成品池),清空B在一级、二级的缓存。
-
Step4: 回头完善A
- 此时B已经是成品,A拿到B的成品,填充自己的属性 → A变成成品。
- 把A从二级缓存升级到三级缓存,清空一级缓存。
最终结果:
A和B都进了三级缓存(成品池),互相持有对方的成品引用,套娃成功!
4. 为什么构造函数循环依赖无解?
- 关键区别:属性注入(setter/字段注入)是先创建空对象,再填属性;构造函数注入是创建对象时必须传参。
- 死锁场景:
- 创建A → 必须传B(但B还没创建)→ 去创建B。
- 创建B → 必须传A(A还没创建完)→ 死循环。
- Spring直接摆烂:抛出
BeanCurrentlyInCreationException
,并骂你代码写得像屎。
5. 动态代理的坑 —— “AOP代理搅局”
- 如果A被AOP代理(比如用了@Transactional),半成品A和最终A可能不是同一个对象!
- 解决方案:
- 一级缓存存的是对象工厂(
ObjectFactory
),而不是对象本身。 - 工厂的
getObject()
会判断是否需要代理 → 确保半成品和成品对象类型一致(要么都是原生对象,要么都是代理对象)。
- 一级缓存存的是对象工厂(
- 核心骚操作:工厂生产对象时动态套娃,保证AOP代理只被创建一次。
总结:
三级缓存的本质是“先交差,再补锅”——允许半成品对象临时被引用,后续再完善。
就像你老板催你交PPT,你先给个草稿(半成品),等同事给你数据(依赖)后再补全终稿(成品)!