ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring IoC 循环依赖源码解析:三级缓存如何提前暴露半成品 Bean(source-code-hunter)

Spring IoC 循环依赖源码解析:三级缓存如何提前暴露半成品 Bean(source-code-hunter) Spring IoC 循环依赖源码解析三级缓存如何提前暴露半成品 Beansource-code-hunter【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunterSpring 容器在创建单例 Bean 时如果出现 A 依赖 B、B 又依赖 A 的闭环依赖默认情况下并不会直接抛异常而是通过DefaultSingletonBeanRegistry中的三级缓存 “提前暴露半成品对象”机制平滑解决。本文以 Spring 5.3.18 源码为蓝本从可运行的 XML 工程出发逐段剖析doCreateBean → populateBean → getSingleton的完整调用链让你在面试与日常排障中能讲清楚“为什么三级缓存能解决 setter 注入的循环依赖”以及这套机制适用的边界。什么是循环依赖循环依赖Circular Reference指一个对象经过依赖链后闭环回到自身A - B - ... - A本文讨论的范围是属性注入setter / 字段注入场景下的循环依赖默认不涉及代理对象问题即普通 Bean 直接提前暴露原始实例即可关于代理对象与getEarlyBeanReference的关系后文会展开。其解决思路可以浓缩为一句话当一个对象已经实例化完毕、还未初始化的时候将它提前暴露注入给它所依赖的、已经实例化好的对象使对方拿到的是一个“半成品引用”待对方完成完整初始化后再把这个完整对象注入回当前对象。翻译成源码语言就是实例化完成后立即把该 Bean 的“获取引用工厂”放入三级缓存属性填充阶段遇到依赖时先从各级缓存中取“半成品”从而打破“必须等对方完全创建好才能引用”的死锁。简单工程复现循环依赖多个类相互依赖的原理与两个类完全一致这里用最小工程cn.demo1复现。A 类依赖 Bpackage cn.demo1; import lombok.Getter; import lombok.Setter; Setter Getter public class A { private B b; }B 类依赖 Apackage cn.demo1; import lombok.Getter; import lombok.Setter; Setter Getter public class B { private A a; }配置文件 test1.xml?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd bean ida classcn.demo1.A property nameb refb/ /bean bean idb classcn.demo1.B property namea refa/ /bean /beansproperty nameb refb/在 BeanDefinition 解析阶段会被封装为RuntimeBeanReference这正是后面属性填充时触发递归创建的关键标记详见 Spring-beanFactory.md 与 将 bean 解析封装成 BeanDefinition。三级缓存三个“特别重要的属性”循环依赖的解法落在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry的三个 Map 上完整属性清单见 Spring-DefaultSingletonBeanRegistry.md// 一级缓存存放完整 Bean 对象实例化 初始化 private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 三级缓存存放一个 lambda 表达式ObjectFactory private final MapString, ObjectFactory? singletonFactories new HashMap(16); // 二级缓存存放一个半成品 Bean 对象只是实例化还未初始化提前暴露 private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16);缓存容器类型存放内容作用一级缓存singletonObjectsConcurrentHashMap(256)完整 Bean实例化 初始化完成最终对外提供单例的入口二级缓存earlySingletonObjectsConcurrentHashMap(16)提前暴露的半成品 Bean仅实例化缓存已通过三级缓存工厂产出的引用避免重复执行工厂三级缓存singletonFactoriesHashMap(16)ObjectFactorylambda 表达式延迟到“真正发生循环依赖”时才生成提前引用此外还有一个容易被忽略的状态集合singletonsCurrentlyInCreationCollections.newSetFromMap(new ConcurrentHashMap(16))它记录当前正在创建中的单例名称是getSingleton判断“要不要查二、三级缓存”的前提。循环依赖问题出现在**属性填充populateBean**阶段而不是实例化阶段。提前暴露的动作发生在实例化之后、填充属性之前。doCreateBean在填充属性之前埋下“三级缓存”进入org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBeanprotected Object doCreateBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args) throws BeanCreationException { // bean 的包装类 BeanWrapper instanceWrapper null; if (mbd.isSingleton()) { instanceWrapper this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper null) { // 就只是 bean 的实例化createBeanInstance 的具体分支见 Spring-beanFactory.md instanceWrapper createBeanInstance(beanName, mbd, args); } Object bean instanceWrapper.getWrappedInstance(); Class? beanType instanceWrapper.getWrappedClass(); if (beanType ! NullBean.class) { mbd.resolvedTargetType beanType; } // 一般为 true单例 允许循环引用 当前正在创建 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); // ....省略部分 if (earlySingletonExposure) { // 这里是将一段 lambda 放入三级缓存中 // 可以看见 bean 填充属性之前就会将三级缓存创建好 // 它传入的是一个还未初始化的 bean addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject initializeBean(beanName, exposedObject, mbd); } // ..........省略部分 return exposedObject; }关键点在earlySingletonExposure的三个条件mbd.isSingleton()—— 只有单例 Bean 才需要缓存与复用this.allowCircularReferences—— 容器级开关默认为true可关闭isSingletonCurrentlyInCreation(beanName)—— 该 Bean 正处于创建过程中。其中实例化阶段createBeanInstance的完整分支Supplier、工厂方法、构造器推断、无参构造可对照 Spring-beanFactory.md#createbeaninstance 查看循环依赖问题的起点就在createBeanInstance之后、populateBean之前。addSingletonFactory把 lambda 放入三级缓存// 其中 singletonFactory 是一个 lambda 表达式 protected void addSingletonFactory(String beanName, ObjectFactory? singletonFactory) { Assert.notNull(singletonFactory, Singleton factory must not be null); synchronized (this.singletonObjects) { // 如果一级缓存中不存在这个叫 beanName 的 bean if (!this.singletonObjects.containsKey(beanName)) { // 放入三级缓存中 this.singletonFactories.put(beanName, singletonFactory); // 把二级缓存中叫 beanName 的半成品 bean 删除 this.earlySingletonObjects.remove(beanName); // 标记当前注册的 bean this.registeredSingletons.add(beanName); } } }该方法的代码与 Spring-beanFactory.md#addsingletonfactory 中呈现的DefaultSingletonBeanRegistry#addSingletonFactory一致以singletonObjects为锁对象保证与后续getSingleton、addSingleton的原子性。lambda 真正执行的方法getEarlyBeanReference三级缓存中存放的不是对象本身而是一段 lambda() - getEarlyBeanReference(beanName, mbd, bean)。protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; // 普通 bean 是进不来的 if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject bp.getEarlyBeanReference(exposedObject, beanName); } } // 直接返回传进来的 bean返回的是一个还未初始化的 bean是提前暴露的 return exposedObject; }普通 Bean 没有SmartInstantiationAwareBeanPostProcessorlambda 直接返回那个还未初始化的实例这就是“提前暴露”的实体。而当容器中注册了 AOP 相关处理器如AbstractAutoProxyCreator#getEarlyBeanReference见 Spring-beanFactory.md#getearlybeanreference时三级缓存的存在还保证了 AOP 场景下提前暴露的是“提前生成的代理对象”从而避免同一 Bean 出现“注入的是原对象、最终用的是代理”的不一致——这也解释了为什么三级缓存里存的是“工厂”而非直接存对象把生成引用的动作延迟到真正发生循环依赖的那一刻。属性填充循环依赖被触发的现场populateBean内部最终调用applyPropertyValues完成属性解析与填充调用链见 Spring-beanFactory.md#populatebean 与 Spring-beanFactory.md#applypropertyvaluesprotected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { // Create a deep copy, resolving any references for values. ListPropertyValue deepCopy new ArrayList(original.size()); boolean resolveNecessary false; for (PropertyValue pv : original) { if (pv.isConverted()) { deepCopy.add(pv); } else { // 属性名字 String propertyName pv.getName(); // 当你引用另一个 bean 的时候会把它封装成 RuntimeBeanReference 这个对象便于操作 Object originalValue pv.getValue(); // 这里是解析的工作也就是会产生循环依赖的地方 Object resolvedValue valueResolver.resolveValueIfNecessary(pv, originalValue); // 省略.... } } }applyPropertyValues中有一个关键的方法调用resolveValueIfNecessary// 我们当前需要的就是这个分支 public Object resolveValueIfNecessary(Object argName, Nullable Object value) { // 当前 bean 的属性值的类型正是 RuntimeBeanReference if (value instanceof RuntimeBeanReference) { RuntimeBeanReference ref (RuntimeBeanReference) value; return resolveReference(argName, ref); } // 省略... }而resolveReference中的重点代码是// 这个方法会调用 getBean Nullable private Object resolveReference(Object argName, RuntimeBeanReference ref) { // 省略... resolvedName String.valueOf(doEvaluate(ref.getBeanName())); // 获取所依赖的 bean —— 循环依赖就在这里被递归触发 bean this.beanFactory.getBean(resolvedName); // 省略... }于是填充 A 时解析到RuntimeBeanReference(b)→ 递归getBean(b)→ 创建 B → 填充 B 时解析到RuntimeBeanReference(a)→ 再次递归getBean(a)。若没有缓存机制这一递归将无限加深直至栈溢出。getSingleton三级缓存“逐级降级”的读取顺序getBean最终进入doGetBean其中关键一行是Object sharedInstance getSingleton(beanName);获取缓存的顺序是先从一级缓存取不存在则从二级缓存取仍不存在则从三级缓存取并“升级”到二级缓存。Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 一级缓存中是否存在 Object singletonObject this.singletonObjects.get(beanName); // 想要获取的 bean 正在创建中且一级缓存中没有 if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { // 尝试二级缓存 singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject this.singletonObjects.get(beanName); if (singletonObject null) { // 获取二级缓存 singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null) { // 获取三级缓存 ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { // 调用三级缓存中的 lambda() - getEarlyBeanReference(beanName, mbd, bean) // 因为所有普通 bean 会首先提前进行三级缓存 // 所以这里会获取到还未初始化的 bean // 从而赋值给依赖当前 singletonObject 的 bean singletonObject singletonFactory.getObject(); // 放入二级缓存中半成品升级 this.earlySingletonObjects.put(beanName, singletonObject); // 三级缓存中移除当前 beanName 的 lambda this.singletonFactories.remove(beanName); } } } } } } // 返回完整对象或者还未初始化的对象 return singletonObject; }注意两个细节前提是isSingletonCurrentlyInCreation(beanName)只有当该 Bean 正处于创建中被标记在singletonsCurrentlyInCreation里时才会去查二、三级缓存。否则直接返回null走完整创建流程。三级缓存取出后立即“升级”到二级缓存并移除三级缓存条目保证同一个半成品对象只通过工厂生成一次。从源码结构可以推断二级缓存的意义正在于“缓存已生成提前引用的结果”避免后续重复执行ObjectFactory.getObject()尤其是 AOP 场景下重复生成代理而三级缓存的“延迟执行”意义在于若整个创建过程从未发生循环依赖那个 lambda 永远不被执行也就没有任何额外开销。getSingleton(String beanName, boolean allowEarlyReference)的完整对照实现含无参版本与ObjectFactory版本见 Spring-DefaultSingletonBeanRegistry.md#getsingleton。全流程串联A 与 B 的“半成品交换”以本文的 A、B 为例将上述源码串成完整时序getBean(a)→doCreateBean(a)实例化 A 得到半成品a0earlySingletonExposure为真把() - getEarlyBeanReference(a, mbd, a0)放入三级缓存并标记 A 正在创建populateBean(a)解析property nameb refb/→resolveReference→getBean(b)doCreateBean(b)实例化 B 得到半成品b0B 的三级缓存就位标记 B 正在创建populateBean(b)解析property namea refa/→resolveReference→getBean(a)getSingleton(a)一级缓存无 A → A 正在创建 → 二级缓存无 → 从三级缓存取出工厂并执行 lambda得到半成品a0同时把a0放入二级缓存、移除三级缓存条目B 拿到半成品a0完成属性填充与初始化B 成为完整对象随后addSingleton(b)将完整 B 放入一级缓存回到步骤 3 的resolveReference把完整 B 注入 A 的b属性A 继续完成initializeBean成为完整对象并进入一级缓存。最终效果B 拿到的是 A 的半成品引用A 拿到的是 B 的完整对象双方都正确完成创建。对应示意图即本文开头的 images/spring/循环依赖.png。机制的适用边界与源码依据只对单例 Bean 生效earlySingletonExposure的第一个条件是mbd.isSingleton()且addSingletonFactory只服务于单例。从代码结构可以直接推断prototype多例Bean 每次都会新建对象、不进入单例缓存无法享受提前暴露机制。只对 setter / 字段注入生效提前暴露发生在createBeanInstance之后此时构造器已经执行完毕因此构造器注入导致的循环依赖无法被该机制兜底会在构造阶段就递归卡死。可通过开关关闭容器属性allowCircularReferences置为false时earlySingletonExposure恒为假此时再出现循环依赖会直接抛出BeanCurrentlyInCreationException。相关声明可对照 Spring-beanFactory.md 中doCreateBean的完整实现。AOP 场景循环依赖与代理对象同时出现时三级缓存中的工厂方法会经过SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference如AbstractAutoProxyCreator的实现见 Spring-beanFactory.md#getearlybeanreference提前产出代理保证各处拿到的引用一致纯普通 Bean 场景则直接返回原实例。这也是“三级缓存存工厂而非直接存对象”的关键设计。延伸阅读仓库内关联文档doCreateBean / createBeanInstance 完整实现Bean 实例化的五种方式与属性填充、初始化全流程DefaultSingletonBeanRegistry 全部缓存字段与 getSingleton 实现一级/二级/三级缓存及singletonsCurrentlyInCreation的声明与操作依赖注入(DI).md)populateBean触发依赖注入的完整过程与earlySingletonExposure的上下文BeanDefinition 的资源定位过程 与 将 bean 解析封装成 BeanDefinition理解property ref...如何一步步变成RuntimeBeanReference。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表