Spring 三级缓存:为什么非得是三级,二级不行吗?

📅 发布时间:2026/10/11 4:15:15
Spring 三级缓存:为什么非得是三级,二级不行吗?
周一早上小刘抱着电脑冲进办公室一脸委屈“杨工我面试被问住了。面试官问 Spring 为什么用三级缓存解决循环依赖而不是二级。我愣了半天说‘因为要三级’他笑了。”我笑了“这问题不能硬背得从‘工厂流水线’讲起。”第一幕只有一级缓存时工厂死锁了我们把 Spring 容器想象成一个Bean 工厂。工厂里有一条流水线实例化先把对象new出来这时候它还是个空壳属性填充把依赖注入进去初始化执行回调、AOP 代理等成品入库放进一级缓存singletonObjects对外提供服务。现在有两个 BeanAService依赖BServiceBService也依赖AService。如果只有一级缓存流程是这样的工厂开始生产 AA 实例化后需要注入 B工厂去生产 BB 实例化后需要注入 A工厂回头去找 A但 A 还没生产完不能入库于是又去生产 A……最后工厂卡死A 等 BB 等 A流水线无限递归。所以一级缓存解决不了循环依赖。第二幕二级缓存能救急但会埋雷小刘说“那我把半成品提前放进二级缓存不行吗”我说“可以但只能救普通场景。”二级缓存earlySingletonObjects里放的是已经实例化、但还没完成初始化的半成品 Bean。现在流程变成工厂生产 A实例化后把 A 的半成品放进二级缓存A 需要 B工厂转去生产 BB 实例化后需要 A从二级缓存拿到 A 的半成品B 完成生产入库A 拿到 B继续完成生产最终入库。看起来完美。但问题来了如果 A 需要 AOP 代理呢Spring 的代理对象通常是在初始化后期也就是BeanPostProcessor后置处理阶段才生成的。如果二级缓存直接暴露原始对象就会出现B 注入的是A 的原始对象但容器最终保存的是A 的代理对象同一个 Bean出现了两个身份。这就破坏了单例一致性事务、切面都可能失效。所以二级缓存的问题是它能打破循环但不能保证代理对象一致。第三幕三级缓存为什么是“延迟决策”三级缓存singletonFactories里存的不是对象而是一个ObjectFactory 工厂。它的意思是我不现在给你半成品也不现在给你代理。我先给你一个“生产早期引用的工厂”。等你真的需要时我再决定是返回原始对象还是提前生成代理对象。流程变成这样A 实例化后把一个ObjectFactory放进三级缓存A 填充属性时需要 B工厂转去生产 BB 填充属性时需要 A一级、二级缓存都没找到 A于是从三级缓存拿到 A 的工厂调用getObject()工厂通过getEarlyBeanReference决定如果 A 需要代理就提前生成代理如果不需要就返回原始对象生成的早期引用放进二级缓存三级缓存清除B 拿到 A 的早期引用完成生产A 继续完成初始化最终进入一级缓存。关键就在这里代理对象不是无脑提前创建而是在确实发生循环依赖时才延迟生成。如果没有循环依赖三级缓存的工厂根本不会被调用AOP 仍然按正常流程在初始化后生成性能不受影响。第四幕为什么不能去掉二级缓存小刘又问“那二级缓存是不是多余三级缓存直接返回不就行了吗”我说“二级缓存的作用是缓存已经生成的早期引用。”如果没有二级缓存每次有人来取 A 的早期引用都会重新调用工厂。万一工厂被调用多次就可能生成多个代理对象破坏单例。所以三级缓存负责“延迟决定怎么生成早期引用”二级缓存负责“保证生成的早期引用只有一份”。一个管“怎么造”一个管“别重复造”。尾声面试可以这样讲Spring 的三级缓存用来解决单例 Bean 的 Setter 或字段注入循环依赖。一级缓存存成品二级缓存存提前暴露的半成品三级缓存存 ObjectFactory 工厂。只用一级缓存会死循环只用二级缓存能解决普通循环依赖但在 AOP 场景下会暴露原始对象导致注入对象和最终容器中的代理对象不一致。三级缓存通过 ObjectFactory 延迟生成早期引用只有在真正发生循环依赖时才提前生成代理既保证单例一致性又避免无循环依赖时提前代理带来的性能损耗。二级缓存则用于缓存已经生成的早期引用防止重复生成。这套机制不适用于构造器注入和原型 Bean。最后我补了一句“所以三级缓存不是‘为了凑三级’而是把三个问题拆开处理打破循环、延迟代理、保证单例。”小刘点点头“那我下次不说‘因为要三级’了我说‘因为要延迟决策’。”