在Spring怎么解决循环依赖的问题?
- 手机
- 2025-09-04 23:00:02

目录
那么循环依赖又有哪几种方式呢?
1.互相依赖
2.间接依赖
3. 自我依赖
编辑
1. 循环依赖的触发场景
2. Spring 的三级缓存机制
3. 解决循环依赖的流程
步骤 1:创建 Bean A
步骤 2:创建 Bean B
步骤 3:解决 A 的依赖
步骤 4:完成 A 的初始化
4. 关键限制条件
5. 如何避免循环依赖?
总结
Spring是怎么解决循环依赖问题的。首先,我需要回忆一下Spring框架处理循环依赖的机制。可能用户在使用Spring的时候遇到了循环依赖的问题,或者在学习过程中对这个问题感到好奇。循环依赖指的是两个或多个Bean相互依赖,比如A依赖B,B又依赖A,这样的情况Spring是如何处理的呢?
那么循环依赖又有哪几种方式呢? 1.互相依赖 2.间接依赖 3. 自我依赖记得Spring主要通过三级缓存来解决循环依赖的问题。三级缓存分别是singletonObjects、earlySingletonObjects和singletonFactories。
在创建Bean的过程中,Spring会提前暴露一个尚未完全初始化的Bean的引用,这样当另一个Bean需要依赖它时,可以通过这个引用来完成注入,而不需要等待Bean完全初始化完成。
不过,三级缓存具体是怎么工作的呢?
比如,当创建Bean A时,A的实例化之后,初始化之前,会被放入三级缓存中。这时如果A依赖Bean B,而B在创建时又需要注入A,Spring会从三级缓存中找到A的早期引用,注入到B中,然后B完成初始化,接着A也能完成初始化。这样循环依赖就被解决了。
需要注意的是,这种解决方式只适用于单例作用域的Bean,并且是通过构造器注入的话,Spring是无法解决循环依赖的,只能抛出异常。而如果是通过setter方法或者字段注入的话,Spring可以处理。
在实际开发中遇到了循环依赖的问题,想知道Spring的处理机制,或者如何避免循环依赖。所以除了解释Spring的解决方法,可能还需要给出一些最佳实践,比如避免使用构造器注入,设计时尽量避免循环依赖等。
另外,可能用户对三级缓存的具体实现细节感兴趣,比如每一级缓存的作用,以及为什么需要三级而不是两级。这时候需要详细解释每一级缓存的用途,以及它们在Bean创建过程中的不同阶段如何被访问和更新。
也有可能用户的问题背后是想优化自己的代码结构,避免循环依赖带来的问题,或者是面试中被问到这个问题,想要深入理解Spring的机制。这时候需要给出更系统化的回答,包括Spring的处理步骤,不同注入方式的影响,以及设计上的建议。
总之,回答需要涵盖Spring解决循环依赖的基本原理,三级缓存的工作机制,适用的场景(单例、setter注入),以及无法处理的情况(构造器注入),同时可能还需要给出一些实际开发的建议,帮助用户理解和避免循环依赖的问题。
Spring 通过 三级缓存(三级对象工厂) 和 提前暴露未完全初始化的对象 的机制来解决单例 Bean 的循环依赖问题。以下是其核心实现逻辑:
1. 循环依赖的触发场景
假设存在以下依赖关系:
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }
当 Spring 容器初始化时,创建 A 需要先注入 B,而创建 B 又需要注入 A,形成循环依赖。
2. Spring 的三级缓存机制
Spring 通过三个缓存容器管理 Bean 的不同状态:
一级缓存(Singleton Objects) 存储完全初始化好的单例 Bean(成品对象)。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();二级缓存(Early Singleton Objects) 存储提前暴露的未完全初始化的 Bean(半成品对象),仅用于解决循环依赖。
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
三级缓存(Singleton Factories) 存储 Bean 的工厂对象(ObjectFactory),用于生成未完全初始化的 Bean 的早期引用。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
3. 解决循环依赖的流程
以 A 和 B 的循环依赖为例:
步骤 1:创建 Bean ASpring 开始创建 A,调用构造函数实例化 A(此时 A 尚未填充属性)。
将 A 的工厂对象(用于生成早期引用)放入 三级缓存,并从 一级、二级缓存 中移除 A。
开始注入 A 的依赖属性,发现需要注入 B。
步骤 2:创建 Bean BSpring 开始创建 B,调用构造函数实例化 B。
将 B 的工厂对象放入 三级缓存,并从 一级、二级缓存 中移除 B。
开始注入 B 的依赖属性,发现需要注入 A。
步骤 3:解决 A 的依赖查找 A:一级缓存(无)→ 二级缓存(无)→ 三级缓存(找到 A 的工厂对象)。
通过工厂对象生成 A 的早期引用(未完成属性注入的 A 实例)。
将 A 的早期引用从三级缓存移动到 二级缓存。
将 A 的早期引用注入到 B 中,完成 B 的属性注入。
将初始化完成的 B 放入 一级缓存,并清除二、三级缓存中的 B。
步骤 4:完成 A 的初始化将 B(已在一级缓存)注入到 A 中,完成 A 的属性注入。
执行 A 的初始化后方法(如 @PostConstruct)。
将 A 放入 一级缓存,并清除二、三级缓存中的 A。
4. 关键限制条件
仅支持单例 Bean:原型(Prototype)作用域的 Bean 无法解决循环依赖。
依赖注入方式:
Setter/Field 注入:支持循环依赖(属性注入在对象实例化后执行)。
构造器注入:无法解决循环依赖(对象未实例化完成时无法提前暴露引用)。
AOP 代理:若 Bean 被代理(如通过 @Async 或 @Transactional),三级缓存的工厂对象会生成代理对象的早期引用。
5. 如何避免循环依赖?
代码设计优化:
使用接口或抽象类解耦具体实现。
通过 @Lazy 延迟加载非关键依赖。
依赖方向控制:使用 @DependsOn 显式指定 Bean 初始化顺序。
合并职责:若两个 Bean 强耦合,考虑合并为一个 Bean。
总结
Spring 通过三级缓存和提前暴露未完成初始化的对象,巧妙地解决了单例 Bean 的循环依赖问题。理解这一机制有助于在开发中避免因设计缺陷导致的循环依赖,同时为排查相关异常提供理论依据。
在Spring怎么解决循环依赖的问题?由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“在Spring怎么解决循环依赖的问题?”
下一篇
嵌入式开发:接触器