主页 > 手机  > 

在Spring怎么解决循环依赖的问题?

在Spring怎么解决循环依赖的问题?

目录

那么循环依赖又有哪几种方式呢? 

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 A

Spring 开始创建 A,调用构造函数实例化 A(此时 A 尚未填充属性)。

将 A 的工厂对象(用于生成早期引用)放入 三级缓存,并从 一级、二级缓存 中移除 A。

开始注入 A 的依赖属性,发现需要注入 B。

步骤 2:创建 Bean B

Spring 开始创建 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怎么解决循环依赖的问题?