原文来自于:zha-ge.cn/java/113
三级缓存揭秘:Spring 如何优雅地处理循环依赖问题
你有没有遇到过那种"你中有我、我中有你"的 Bean 场面?有一次,我一不小心写了俩服务,A 里注入了 B,B 里又非要注入回 A。结果启动直接爆炸,简直比宫斗剧还乱。后来一琢磨,这年头连内存都三层缓存了,Spring 它到底靠啥能优雅地扛过循环依赖?今天咱就唠唠:Spring 三级缓存的江湖传说。
Spring 的 BeanFactory,其实就是一套好用的"仓库管理小精灵"。循环依赖一来,普通搞法直接凉凉,但 Spring 居然通过三级缓存,像搞"排兵布阵"一样把问题拆开来看:
- 一级缓存(singletonObjects):成品区,搞定的 Bean 都放这儿。
- 二级缓存(earlySingletonObjects):半成品区,可能还没初始化好,但需要提前透剧的货。
- 三级缓存(singletonFactories):工厂区,万一再来一波操作,灵活应急!
三层缓存到底怎么救命?
说实话,第一次点进 Spring 源码看的时候,我全程黑人问号脸。终于在 AbstractBeanFactory 里,抓住了工资负责人的身影。尤其是下面这几行伪代码,让我脑补出现场画面:
java
// Bean 创建大致流程,遇到循环依赖的时刻
if (!singletonObjects.contains(beanName)) {
// 本地没成品,看看工厂区有没有货
Object singleton = getSingletonFromFactory(beanName);
if (singleton == null) {
singleton = createBean(beanName);
addSingleton(beanName, singleton);
}
// ......
}
咱们打断点调了又调,才发现每当 A、B 互相依赖时,Spring 就提前把工厂对象注册进三级缓存。A 注入 B 时(一脸无助),发现自己不在成品区,索性先暴露个"毛坯版"A。同时把它的 ObjectFactory 藏进三级缓存里,B 要用就"勉强先来一口试试"。这样能保证最终的 Bean 能在 Spring 容器安全出生。
- 真正的对象方法注入在后面才补全,像拼图一样,总算把所有 Bean 填好。
踩坑瞬间
来,说几个让人吐血的循环依赖小故事:
- 曾经一个朋友,手贱搞了个@Configuration,偏偏又自己注入自己,一脸懵逼连爆三次错。
- 看源码时,误以为三级缓存等于三级缓存,全局搜 earlySingletonObjects,还以为这是个 Bug,原地自闭一个小时。
- 曾用原生 Java 写了一次类似的"临时缓存",结果死循环到怀疑人生,还没 Spring 靠谱。
如果非要描述踩坑瞬间,就是各种"BeanCurrentlyInCreationException"的红色大字,佛系劝退新手。
经验启示
唠到这里,总算有点经验了,送你几个:
- 千万别轻易设计循环依赖,代码能隔离就别钻牛角尖。
- @Lazy、setter注入可以偶尔救命,但设计时还是要优雅些。
- 别把三级缓存当魔法,单例Bean有限制,不支持构造器依赖、@Scope("prototype")直接爆炸。
- 对自己温柔点,踩坑是成长的必经之路。毕竟三层缓存,也不是天生就会用的嘛!
最后,"收个尾巴":你要跟循环依赖过不去,Spring就跟你和解;你跟它和解,它也教会你点底层的智慧。行了,先写到这,你有没有被三级缓存救过命?欢迎留言唠嗑!