摘要: 面试官一问“Spring 怎么解决循环依赖”,脑子里就只剩下“三个 Map”?一问“为什么不能只用两级缓存”,当场就哑口无言?今天,我们彻底抛弃那些生涩的源码变量,把 Spring 容器想象成一个巨大的“房地产开发商”。看懂了这场“造房子大戏”,你不仅能手撕三级缓存,还能直接反杀面试官!

在 Spring 的世界里,所谓的“循环依赖”,就是A 房子需要装 B 牌智能锁,而 B 牌智能锁的系统又必须绑定 A 房子的确切地址。
大家都等对方先完工,这不就死锁了吗?

为了打破这个死锁,Spring 董事长设计了三个大仓库(也就是传说中的三级缓存):

🏢 认识这三大仓库(缓存)

  1. 一级缓存(成品交付区 - singletonObjects): 这里停放的,是精装修完毕、家电齐全、直接能交房的绝对成品。大家平时 getBean() 拿到的都是这里的货。
  2. 二级缓存(半成品展示区 - earlySingletonObjects):
    这里放的是刚封顶、毛坯状态的半成品。虽然没装修,但房子的地址已经确定了,别人可以先拿这个地址去填表登记。
  3. 三级缓存(图纸与代工厂 - singletonFactories):
    高能预警!这里放的根本不是房子,而是一份“造房说明书(ObjectFactory)”。它相当于一个随时待命的代工厂,只要别人拿着说明书大喊一声,它就能瞬间给你变出一个半成品房子(还可以顺便给你套个 AOP 的精装外壳)。

🎬 循环依赖破局大戏(A 与 B 的极限拉扯)

现在,项目正式开工!请紧跟剧情:

第一幕:A 动工,留好后路
A 房子刚打好地基(实例化),连砖都没砌。此时 Spring 非常聪明,它没有直接把 A 放进二级缓存,而是把 A 的造房说明书 扔进了三级缓存
潜台词:“我先去拉电线了,万一有人急着找我,你们去三级缓存按说明书找我的半成品!”

第二幕:A 找 B,B 动工
A 准备装锁了,发现需要 B。去一级缓存找 B,没找到。于是 A 暂停施工,呼叫 B 开工。
B 刚打好地基(实例化),也把 B 的说明书 扔进了三级缓存

第三幕:B 找 A,三级缓存发威(破局点!)
B 准备绑定系统,发现需要 A 的地址。B 去一级缓存找 A(没成品),去二级缓存找 A(没半成品),最终在三级缓存找到了 A 的说明书!
此时,B 兴奋地按下了说明书的执行按钮。
三级缓存的代工厂立刻启动,把 A 的半成品引用(早期引用)造了出来,并且把它从三级缓存挪到了二级缓存
B 拿到 A 的地址,心满意足地完成了自己的系统绑定(属性赋值),接着做完所有初始化,风风光光地住进了一级缓存(成品区)

第四幕:A 满血复活
B 完工了,A 终于拿到了成品的 B,装上了智能锁。A 也顺理成章地完成了所有装修,最终从二级缓存中删掉,骄傲地住进了一级缓存

死锁,完美破解!


💣 终极拷问:为什么非要三级?二级不行吗?

这时候面试官一定会冷笑一声:“既然说明书一执行,产生的就是半成品,那为什么 A 打好地基的时候,不直接生成半成品放进二级缓存?非要多搞一个三级缓存存说明书?

神级反杀话术:

“如果只是为了解决循环依赖,两级缓存确实够了。三级缓存存在的唯一意义,是为了兼容 Spring 的 AOP 设计原则!

在 Spring 的原则里,AOP 代理对象应该在 Bean 彻底装修完之后(初始化后置阶段)再生成
如果我们只有两级缓存,A 刚打好地基,为了防范“可能存在”的循环依赖,我们就必须在此时立刻判断 A 要不要 AOP,如果要,就得强行生成代理对象放进二级缓存。这会导致所有需要 AOP 的 Bean 都被迫提前代理,彻底破坏了 Spring 的生命周期设计!

有了三级缓存(说明书),Spring 就可以优雅地实现**“延迟加载”**:
我先把说明书放着,如果没人跟我循环依赖,我就按正常流程,等彻底装修完再做 AOP;
只有当真正的循环依赖发生时(比如 B 找上门了),说明书才会被迫提前执行 AOP,生成代理对象进入二级缓存救急。

一句话总结:三级缓存的本质,是用空间换时间,在不破坏正常 AOP 生命周期的情况下,保留了提前生成代理对象的“后手”!


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐