一、Spring为什么需要三级缓存源码剖析Spring采用三级缓存机制来处理单例Bean的循环依赖主要是为了解决一个核心难题当循环依赖遇上AOP面向切面编程时如何保证最终注入到其他Bean的是且仅是唯一的代理对象。简单来说如果没有AOP两级缓存12或13就足以解决循环依赖引入第三级缓存是为了保证代码设计的单一职责原则和高扩展性让创建原始Bean与生成代理对象的职责分离代码更清晰、灵活。为了理解这一点我们先看看Spring的三级缓存各自是什么。️ 三级缓存一览缓存名称变量名存储内容解决问题阶段生命周期一级缓存singletonObjects完全初始化好的成品BeanBean初始化完成最终存放全局唯一二级缓存earlySingletonObjects提前暴露的原始Bean引用 (半成品)循环依赖发生且无AOP时临时使用用完即删三级缓存singletonFactoriesObjectFactory对象工厂生成代理对象应对AOP场景临时工厂用完即删以下是它们的详细定义一级缓存 (singletonObjects)存放的是已经经历了完整生命周期实例化、属性填充、初始化的“成品Bean”。这是业务代码最终使用的单例对象保证了全局唯一性。二级缓存 (earlySingletonObjects)存放的是“半成品Bean”或“早期Bean”。这些对象已经完成了内存分配实例化但尚未进行属性填充和初始化。它主要解决了AOP代理对象的唯一性问题。三级缓存 (singletonFactories)这是整个机制的关键存放的是ObjectFactory?类型的对象工厂。ObjectFactory是一个函数式接口调用其getObject()方法才能真正创建对象。它的核心作用是将Bean的创建实例化与代理的生成AOP逻辑分离实现了代码的单一职责和延迟初始化。️ 源码剖析Spring的相关逻辑主要在AbstractAutowireCapableBeanFactory和DefaultSingletonBeanRegistry类中。核心查找方法getSingleton的定义如下// 在 DefaultSingletonBeanRegistry 中定义NullableprotectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){// 1. 从一级缓存成品缓存获取ObjectsingletonObjectthis.singletonObjects.get(beanName);// 目标bean不存在且当前正在创建中- 可能发生了循环依赖if(singletonObjectnullisSingletonCurrentlyInCreation(beanName)){// 2. 从二级缓存早期引用缓存获取singletonObjectthis.earlySingletonObjects.get(beanName);// 二级缓存没有且允许提前引用if(singletonObjectnullallowEarlyReference){// 加锁确保线程安全synchronized(this.singletonObjects){// 双重检查防止并发问题singletonObjectthis.singletonObjects.get(beanName);if(singletonObjectnull){singletonObjectthis.earlySingletonObjects.get(beanName);if(singletonObjectnull){// 3. 从三级缓存获取 ObjectFactoryObjectFactory?singletonFactorythis.singletonFactories.get(beanName);if(singletonFactory!null){// 调用工厂的 getObject() 方法创建对象可能是AOP代理singletonObjectsingletonFactory.getObject();// 将创建好的对象放入二级缓存this.earlySingletonObjects.put(beanName,singletonObject);// 从三级缓存中移除该工厂this.singletonFactories.remove(beanName);}}}}}}returnsingletonObject;}第1步优先从一级缓存singletonObjects获取这是为了直接拿到最完整的成品Bean。第2步当发生循环依赖时目标Bean的创建流程被阻塞此时该Bean会处于“正在创建”的状态。isSingletonCurrentlyInCreation(beanName)的校验触发Spring便会向下检查二级缓存。第3步二级缓存未命中表示这是该Bean第一次被循环依赖引用此时Spring会从三级缓存获取对应的ObjectFactory并调用getObject()。这一步是AOP的“钩子”getObject()方法内会执行getEarlyBeanReference其中包含了AOP代理的创建逻辑。存入二级缓存为了确保对象唯一性它会将生成的singletonObject存入二级缓存并移除三级缓存。这意味着即使有多个Bean如C、D在循环依赖中依赖ASpring也只会首次通过三级缓存创建一次代理对象后续都直接从二级缓存获取同一个实例。最终生成当A完成初始化后会被存入一级缓存并移除二级缓存。 工作流程从依赖到解决以A→B→A为例开始创建ASpring实例化A得到一个原始对象A_raw并将其包装成ObjectFactory存入三级缓存singletonFactories。A发现依赖B开始对A进行属性填充时发现需要B于是转去创建B。开始创建BSpring创建B的原始对象B_raw同样将其包装成ObjectFactory存入三级缓存。B发现依赖A对B进行属性填充时发现需要A于是调用getSingleton去获取A。三级缓存触发getSingleton方法在一级、二级缓存找不到A但在三级缓存中找到了A的ObjectFactory。Spring调用factory.getObject()该方法内部逻辑会判断A是否需要AOP若需要则创建A_proxy若不需要则返回A_raw。缓存升级getSingleton方法将上一步得到的对象A_proxy或A_raw存入二级缓存earlySingletonObjects并移出三级缓存。这个对象随后被注入到B中。B完成剩余初始化后成为一个成品Bean存入一级缓存。A继续完成初始化Spring拿到注入的成品Bean B继续完成A的初始化。A的初始化完成后从二级缓存中取出其对象存入一级缓存并将二级缓存中A的记录移除。 核心解惑为什么二级缓存不够结合上面的流程二级缓存足以在无AOP时解决循环依赖。但既然可能有AOP就必须引入三级缓存原因在于从设计角度看AOP代理的生成时机通常是在Bean生命周期的最后阶段初始化后。如果强制要求在一个Bean被循环依赖引用时属性填充阶段就必须生成代理对象就破坏了Spring原有的生命周期和单一职责原则也就是**“调用时机”**的问题。从扩展性角度看ObjectFactory是一个通用的函数式接口它不仅仅能用于AOP还可以为其他BeanPostProcessor提供扩展点来处理“早期引用”。这为Spring未来的功能扩展留下了巨大的空间体现了框架设计的高瞻远瞩。从对象唯一性看如果只用三级缓存不用二级缓存每次调用factory.getObject()都会产生一个新对象无法保证单例。因此需要二级缓存来暂存唯一的实例这正是引入**“第二级缓存”**的核心作用。从技术实现看即使将代理对象的创建时机强行提前到实例化之后确实可行但这会牺牲设计的优雅和清晰度让代码变得难以维护和扩展。Spring选择三级缓存是在技术可行性与代码优雅性之间做出的最佳权衡。Spring为什么“非要”三级缓存不可了。它本质上是对代码设计原则的极致追求与现实场景解决方案的精妙平衡的产物。二、结合Spring为什么“非要”三级缓存不可再详细解释问题核心为什么二级缓存解决不了AOP循环依赖先明确一个事实如果没有AOP即不需要代理两级缓存一级二级就足够了。但是有了AOP一个Bean最终会变成一个代理对象这个代理对象是在Bean初始化initializeBean的最后一步通过BeanPostProcessor生成的。现在考虑循环依赖A依赖BB依赖AA有AOP。A实例化 - 原始对象A_raw存入二级缓存如果只有两级会存A_raw。A填充属性发现需要B - 去创建B。B实例化 - B原始对象B_raw。B填充属性发现需要A - 从缓存中获取A。关键是此时A还没有初始化完成还没执行AOP后置处理所以二级缓存中只有A_raw。B拿到了A_raw注入给自己。B完成初始化成为成品B没有AOP问题。回到A继续初始化。最后一步执行AOP生成A_proxy。问题来了B里面持有的A是A_raw而最终容器中真正的A是A_proxy。这就产生两个不同的A对象B调用A的方法时不会有AOP增强比如事务不生效。所以二级缓存会导致对象不一致。三级缓存如何解决三级缓存中存的是ObjectFactory它的getObject()方法会调用getEarlyBeanReference。这个getEarlyBeanReference方法允许BeanPostProcessor提前返回一个代理对象的引用如果需要AOP。流程变化A实例化 - 创建ObjectFactory里面封装了getEarlyBeanReference逻辑存入三级缓存注意此时没有生成代理只是工厂。A填充属性发现需要B - 创建B。B实例化填充属性时需要A - 调用getSingleton。一级缓存没有A二级缓存没有A因为A还没放进二级三级缓存有A的工厂 - 调用工厂的getObject()- 触发getEarlyBeanReference- 判断A需要AOP直接生成代理对象A_proxy提前生成。将A_proxy放入二级缓存earlySingletonObjects删除三级缓存。返回A_proxy给B。B拿到A_proxy注入完成初始化成为成品B。回到A继续初始化。最后一步initializeBean中也会生成代理对象但此时SmartInstantiationAwareBeanPostProcessor发现这个Bean已经在二级缓存中有代理了就不会重复生成直接返回二级缓存中的A_proxy。A完成初始化将A_proxy移入一级缓存删除二级缓存。最终B中持有的和容器中的是同一个A_proxy完美解决。一句话硬核总结三级缓存允许在循环依赖暴露早期引用时通过getEarlyBeanReference提前生成最终的代理对象如果必要从而保证所有地方引用的是同一个对象。二级缓存如果直接存原始对象就无法在早期阶段得到代理对象导致最终对象不一致。我们只看源码中的关键一行在AbstractAutowireCapableBeanFactory.doCreateBean中实例化Bean后会执行addSingletonFactory(beanName,()-getEarlyBeanReference(beanName,mbd,bean));这个getEarlyBeanReference就是生成代理或返回原对象的地方。而getSingleton中从三级缓存取出工厂并调用getObject()就触发了上面的逻辑。没有三级缓存就没有地方存放这个“能提前生成代理的工厂”。三、疑惑点 ?疑惑1为什么代理必须延迟到初始化最后一步疑惑2为什么早期暴露必须用工厂而不能直接暴露原始对象疑惑3二级缓存的作用和时机到底是什么疑惑1为什么代理AOP必须延迟到Bean初始化的最后一步才生成核心答案因为AOP代理需要依赖Bean的完整状态包括属性填充、初始化方法等来决定如何增强。详细解释在Spring中一个Bean的生命周期是严格的实例化new一个对象调用构造函数—— 此时对象刚出生所有属性都是null。属性填充通过反射或setter注入依赖的其他Bean如Autowired字段。初始化调用PostConstruct、InitializingBean.afterPropertiesSet()、init-method等自定义初始化逻辑。AOP代理生成通过BeanPostProcessor具体是AbstractAutoProxyCreator在初始化之后包装成代理对象。为什么不能提前生成代理因为AOP增强可能需要用到刚刚初始化的状态比如你要在一个方法上做Transactional事务增强这个方法的执行结果可能依赖于PostConstruct里准备的数据。如果代理在实例化后就生成那代理对象里包裹的还是原始对象但原始对象的初始化还没执行代理就无法感知最终状态。更根本的设计原则生命周期统一。Spring所有的BeanPostProcessor都约定在初始化阶段前后工作。如果AOP可以任意提前就会破坏整个扩展点的执行顺序导致其他BeanPostProcessor如属性注入、自动代理等的不可预知行为。代码证据在AbstractAutowireCapableBeanFactory.initializeBean中protectedObjectinitializeBean(StringbeanName,Objectbean,NullableRootBeanDefinitionmbd){// ... 执行Aware方法、各种BeanPostProcessor的前置处理 ...invokeInitMethods(beanName,bean,mbd);// 执行初始化// ... 执行BeanPostProcessor的后置处理这里才会生成AOP代理...returnwrappedBean;}AOP代理生成是在applyBeanPostProcessorsAfterInitialization中而该方法位于invokeInitMethods之后。这是硬编码的顺序意味着Spring强制要求所有Bean必须完成初始化之后才能被代理。疑惑2为什么早期暴露必须用工厂ObjectFactory而不能直接暴露原始对象核心答案为了满足“需要代理但代理尚未生成”这种矛盾场景。工厂提供了一种“按需提前生成代理”的机制而不破坏正常的生命周期。详细解释假设循环依赖发生时A依赖BB依赖AA处在“实例化完成但属性填充和初始化尚未完成”的阶段。此时如果有另一个BeanB需要A按正常流程B只能得到一个原始对象因为A的代理还没生成。但Spring希望如果A最终需要被代理那么B也应该拿到那个代理对象而不是原始对象。然而此时A的代理还没生成因为还没到初始化完成那一时刻怎么办→用一个工厂对象延迟决定工厂里封装了一个方法getEarlyBeanReference这个方法内部会去检查“这个Bean是否需要代理如果需要在现在循环依赖暴露时就生成代理”。这样一来Spring不用破坏生命周期代理生成时机仍然在初始化之后只是对循环依赖做了特例同时也能让B拿到正确的对象代理。为什么不直接暴露原始对象并标记“以后替换”如果直接暴露原始对象B已经引用了原始对象后续Spring无法把B里持有的引用偷偷换成代理对象Java是值传递换不了。所以必须在暴露的那一刻就让B拿到最终的、正确的对象——即使这个对象是通过提前生成的。为什么不直接在实例化后生成代理并一直使用如果AOP代理在实例化后立即生成那就不需要工厂了。但这样会破坏前面说的“初始化后才代理”的约定导致代理对象可能在未初始化时就暴露给外部调用方法时会出问题因为依赖的属性还是null。工厂模式的精妙之处通常情况下没有循环依赖工厂不会被调用代理会在初始化结束后正常生成。有循环依赖时工厂被调用提前生成代理但此时只是“提前生成了代理”而非“提前完成初始化”。代理对象内部的原始对象的初始化仍会按正常流程执行只是代理对象已经暴露了。这样既满足了循环依赖又最大限度地保持了生命周期的完整性。疑惑3二级缓存的作用和时机是什么核心答案二级缓存用于保证“同一个Bean在循环依赖暴露期间只通过三级工厂生成一次对象”避免重复创建代理或重复调用工厂。时机代码回顾DefaultSingletonBeanRegistry.getSingleton// 从三级缓存拿到工厂ObjectFactory?singletonFactorythis.singletonFactories.get(beanName);if(singletonFactory!null){// 生成对象可能是原始也可能是代理singletonObjectsingletonFactory.getObject();// 放入二级缓存this.earlySingletonObjects.put(beanName,singletonObject);// 删除三级缓存this.singletonFactories.remove(beanName);}作用分析如果没有二级缓存只有一级和三级那么当有两个Bean同时依赖于A时比如C依赖AD也依赖A流程会变成C请求A → 发现三级缓存有工厂 → 调用工厂生成对象比如代理对象A1→ 返回给C。D请求A → 发现三级缓存还有工厂因为C用完后没有删除如果没删除就会重复生成→ 再次调用工厂生成对象A2 → 返回给D。问题C和D拿到的不是同一个对象A破坏了单例。如果三级缓存用完后删除那D再次请求时三级缓存已空就会直接返回null导致D无法拿到A从而报错。所以需要一个地方存放“已经通过工厂生成过一次的对象”并且这个对象在A完全初始化之前对所有其他依赖者可见。这个存放的地方就是二级缓存。一旦生成就将对象移到二级缓存之后所有其他依赖A的Bean直接从二级缓存拿同一个实例。等到A完全初始化完成再将这个对象从二级缓存移到一级缓存成品区。为什么需要二级不能用一级直接存半成品因为一级缓存是专门存放“完全初始化好的成品”的如果半成品也放在一级会导致getSingleton无法区分是否已初始化完毕可能错误地获取到未初始化的对象。保持缓存的语义清晰非常重要一级成品二级早期暴露出的半成品可能已代理但未初始化三级工厂。把它们串起来——核心逻辑链代理生成时机正常情况下在初始化之后这是Spring的硬设计。循环依赖冲突在初始化之前就需要暴露Bean但暴露的应该是代理如果需要而不是原始对象。解决方案不直接暴露对象而是暴露一个工厂三级缓存在需要时由工厂决定生成原始对象还是提前生成代理。避免重复生成工厂生成的对象暂存于二级缓存确保多个依赖者拿到同一个实例。最终转移Bean完全初始化后从二级缓存移到一级缓存成品区。