【Spring】Spring是如何解决循环依赖问题的
Spring 通过 三级缓存 和 提前暴露对象引用 的机制解决单例 Bean 的循环依赖问题。以下是详细原理和实现流程:
一、循环依赖的场景
假设存在以下相互依赖的 Bean:
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }
此时 Spring 需要处理 A → B → A 的循环依赖。
二、Spring 的解决机制
1. 三级缓存
Spring 使用三级缓存存储不同状态的 Bean:
缓存名称 | 作用 |
---|---|
singletonObjects | 存储完全初始化后的单例 Bean(一级缓存) |
earlySingletonObjects | 存储早期暴露的 Bean(未完成属性填充和初始化,二级缓存) |
singletonFactories | 存储 Bean 的 ObjectFactory(用于生成早期引用,三级缓存) |
2. Bean 创建流程(以 A → B → A 为例)
• 步骤 1:创建 Bean A
- 实例化 A(调用构造函数,但属性未填充)。
- 将 A 的 ObjectFactory 放入 三级缓存(singletonFactories)。
- 开始填充 A 的属性,发现依赖 B。
• 步骤 2:创建 Bean B
- 实例化 B(调用构造函数,属性未填充)。
- 将 B 的 ObjectFactory 放入 三级缓存。
- 开始填充 B 的属性,发现依赖 A。
• 步骤 3:解决依赖 A
- 从 三级缓存 中获取 A 的 ObjectFactory,生成 A 的早期引用。
- 将 A 的早期引用放入 二级缓存(earlySingletonObjects),并从三级缓存中移除。
- 将 A 的早期引用注入到 B 中,完成 B 的初始化。
- 将 B 的完整实例放入 一级缓存(singletonObjects)。
• 步骤 4:完成 Bean A 的初始化
- 将已注入 B 的 A 实例继续初始化。
- 将 A 的完整实例从 二级缓存 移到 一级缓存。
三、核心源码解析
• 关键类:DefaultSingletonBeanRegistry
• 缓存操作:
// 从缓存获取 Bean protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存 if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory singletonFactory = this.singletonFactories.get(beanName); // 三级缓存 if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } return singletonObject; }
四、限制条件
1. 仅支持单例(Singleton)作用域的 Bean
• 原型(Prototype)作用域的 Bean 无法解决循环依赖,Spring 直接抛出 BeanCurrentlyInCreationException。
2. 不支持构造器注入的循环依赖
• 构造器注入的循环依赖无法解决(实例化阶段就需要依赖对象,但此时依赖的 Bean 未创建)。
• 错误示例:
@Component public class A { private B b; public A(B b) { this.b = b; } // 构造器注入导致循环依赖无法解决 }
3. 需要启用代理
• 若 Bean 被 AOP 代理(如通过 @Async、@Transactional),需确保代理对象能正确暴露到缓存中。
五、如何避免循环依赖?
-
代码设计优化:
(图片来源网络,侵删)• 避免双向依赖,改为单向依赖。
• 使用 @Lazy 延迟加载(将依赖标记为惰性初始化)。
(图片来源网络,侵删)• 通过事件驱动(ApplicationEvent)解耦 Bean。
-
依赖注入方式:
(图片来源网络,侵删)• 优先使用 Setter 注入 而非构造器注入。
-
示例:使用 @Lazy:
@Component public class A { @Autowired @Lazy // 延迟注入 B 的代理对象 private B b; }
六、总结
机制 | 说明 |
---|---|
三级缓存 | 通过缓存不同状态的 Bean 解决循环依赖 |
提前暴露引用 | 在 Bean 未完成初始化前,提前将引用暴露给其他 Bean 使用 |
单例限制 | 仅支持单例 Bean,原型 Bean 和构造器注入的循环依赖无法解决 |
最佳实践:优先通过代码设计避免循环依赖,仅在必要场景下依赖 Spring 的自动解决机制。
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。