别再被源码绕晕了!用“造房子大戏”秒懂 Spring 三级缓存与循环依赖
摘要: 面试官一问“Spring 怎么解决循环依赖”,脑子里就只剩下“三个 Map”?一问“为什么不能只用两级缓存”,当场就哑口无言?今天,我们彻底抛弃那些生涩的源码变量,把 Spring 容器想象成一个巨大的“房地产开发商”。看懂了这场“造房子大戏”,你不仅能手撕三级缓存,还能直接反杀面试官!
在 Spring 的世界里,所谓的“循环依赖”,就是A 房子需要装 B 牌智能锁,而 B 牌智能锁的系统又必须绑定 A 房子的确切地址。
大家都等对方先完工,这不就死锁了吗?
为了打破这个死锁,Spring 董事长设计了三个大仓库(也就是传说中的三级缓存):
🏢 认识这三大仓库(缓存)
- 一级缓存(成品交付区 -
singletonObjects): 这里停放的,是精装修完毕、家电齐全、直接能交房的绝对成品。大家平时getBean()拿到的都是这里的货。 - 二级缓存(半成品展示区 -
earlySingletonObjects):
这里放的是刚封顶、毛坯状态的半成品。虽然没装修,但房子的地址已经确定了,别人可以先拿这个地址去填表登记。 - 三级缓存(图纸与代工厂 -
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 生命周期的情况下,保留了提前生成代理对象的“后手”!
更多推荐



所有评论(0)