Spring 源码系列(9): 循环依赖终极拷问:为什么用三级缓存而不是二级
引子A 依赖 BB 又依赖 A——这么写Spring 为什么没崩很多人的答案止步于因为三级缓存。但面试官要的是三级缓存各自存什么为什么二级不够、非得三级本篇把循环依赖的前因后果 源码证据 反证一次性讲透。这是整个 IoC 阶段最值得收藏的一篇。一句话结论三级缓存解决的是在还没初始化完时如何把一个可被他人引用的早期对象可能是 AOP 代理安全暴露出去。二级缓存只能存已决定的早期对象无法延迟决定要不要生成代理三级缓存用ObjectFactory把是否生成代理延迟到真正被依赖引用时才发生。这就是二级不够、必须三级的根本原因。前置知识Spring只解决单例 字段/setter 注入的循环依赖构造器注入、原型prototype作用域不解决。提前暴露动作发生在doCreateBean实例化后addSingletonFactory把() - getEarlyBeanReference放进三级缓存见第 6 篇。循环依赖的产生源于 DI 阶段的getBean(依赖)递归见第 8 篇。一、三级缓存到底存什么都在DefaultSingletonBeanRegistry里三个 Map缓存字段存的内容写入时机读取时机一级singletonObjects成品 Bean完全初始化完成addSingleton创建完成getSingleton先查它二级earlySingletonObjects早期 Bean已实例化、未初始化可能是代理getSingleton从三级取对象后转入有人提前引用时三级singletonFactoriesObjectFactory能产出早期引用addSingletonFactory实例化后一级、二级都未命中时// DefaultSingletonBeanRegistryprivatefinalMapString,ObjectsingletonObjectsnewConcurrentHashMap(256);// 一级privatefinalMapString,ObjectearlySingletonObjectsnewHashMap(16);// 二级privatefinalMapString,ObjectFactory?singletonFactoriesnewHashMap(16);// 三级二、getSingleton查缓存的顺序protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ObjectsingletonObjectthis.singletonObjects.get(beanName);// ① 一级if(singletonObjectnullisSingletonCurrentlyInCreation(beanName)){singletonObjectthis.earlySingletonObjects.get(beanName);// ② 二级if(singletonObjectnullallowEarlyReference){ObjectFactory?singletonFactorythis.singletonFactories.get(beanName);// ③ 三级if(singletonFactory!null){singletonObjectsingletonFactory.getObject();// 调 getEarlyBeanReferencethis.earlySingletonObjects.put(beanName,singletonObject);// 转存二级this.singletonFactories.remove(beanName);// 清三级}}}returnsingletonObject;}三、为什么必须三级核心论证假设只有一 二级缓存即没有三级的ObjectFactory会发生什么实例化后必须立刻把一个早期对象放进二级缓存earlySingletonObjects。但此时 Bean还没走initializeBeanAOP 代理还没生成。如果这里直接放原始对象而后面initializeBean又生成了代理——那么循环依赖方B手里拿的是原始对象最终容器里却是代理二者不是同一个引用Spring 会抛BeanCurrentlyInCreationException。那实例化后就直接生成代理放进二级行不行不行——绝大多数 Bean 没有 AOP凭空给每个 Bean 都生成代理既浪费又在语义上错误而且代理本应在initializeBean第 ⑦ 步postProcessAfterInitialization才确定。三级缓存的价值 延迟决策三级缓存存的是() - getEarlyBeanReference(beanName, mbd, bean)是一个工厂/lambda不是具体对象。只有真正发生循环依赖B 来取 A时才调用这个工厂 →getEarlyBeanReference→ 此时才决定返回原始对象还是代理。若不发生循环依赖这个工厂永远不被调用A 正常走到initializeBean生成代理没有任何副作用。// 发生循环依赖、被引用时才调用protectedObjectgetEarlyBeanReference(StringbeanName,RootBeanDefinitionmbd,Objectbean){ObjectexposedObjectbean;for(SmartInstantiationAwareBeanPostProcessorbp:getBeanPostProcessors()){exposedObjectbp.getEarlyBeanReference(exposedObject,beanName);// AOP 在此可能生成代理}returnexposedObject;}结论三级缓存不是为了性能而是为了延迟生成代理 保证引用一致性。二级缓存是三级的结果缓存——getEarlyBeanReference拿到代理后存进二级避免同一早期引用被重复生成代理不能 new 多次。四、构造器循环依赖为何无解构造器注入在createBeanInstance实例化阶段就要求依赖就绪而addSingletonFactory注册三级缓存是在实例化之后才执行的。所以A 构造器要 B → getBean(B) → B 构造器要 A → getBean(A) → A 此时既不在一级没建完也不在二/三级还没到 addSingletonFactory → 只能再走创建 A → isSingletonCurrentlyInCreation(A) 已标记 → 抛 BeanCurrentlyInCreationException字段/setter 注入能解正是因为实例化先于注入——实例化后立刻把工厂放进三级缓存等populateBean时 B 来取能从三级拿到 A 的早期引用。五、循环依赖时序图六、常见误区误区正解三级缓存是为了性能否是为了延迟生成代理 引用一致二级缓存就够了不够无法延迟决定要不要代理构造器循环依赖能解不能实例化早于三级缓存注册原型 Bean 的循环依赖能解不能原型不缓存、不提前暴露七、面试题自测三级缓存分别存什么读取优先级是什么为什么不能只用一 二级缓存循环依赖场景中AOP 代理是在哪一步生成的为什么构造器注入的循环依赖无法解决哪些场景 Spring 明确不解决循环依赖八、Debug 小技巧构造 A↔B 字段循环依赖在getEarlyBeanReference打断电观察 B 注入 A 时这里返回的是 A 的代理若 A 有 AOP还是原始对象。在DefaultSingletonBeanRegistry.getSingleton三个get处分别打断电看 A 创建时三个缓存的先后命中顺序。把 A、B 改成构造器互相注入启动观察异常栈顶端BeanCurrentlyInCreationException确认失败发生在createBeanInstance。下篇预告第 10 篇IoC 收尾FactoryBean与 Spring 事件机制。讲清 MyBatis 的 Mapper 为何是FactoryBean以及事件如何实现组件解耦。如果这篇对你有帮助欢迎点赞 · 收藏 · 关注三连支持。Spring 源码系列共 30 篇由浅入深持续更新中。有疑问或想深挖的源码点评论区告诉我下篇见。