当前位置: 首页 > article >正文

在 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依赖BB又依赖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 的不同状态:

  1. 一级缓存(Singleton Objects)
    存储完全初始化好的单例 Bean(成品对象)。

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
     
  2. 二级缓存(Early Singleton Objects)
    存储提前暴露的未完全初始化的 Bean(半成品对象),仅用于解决循环依赖。

     

     

    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
     
  3. 三级缓存(Singleton Factories)
    存储 Bean 的工厂对象(ObjectFactory),用于生成未完全初始化的 Bean 的早期引用。

     

     

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
     

3. 解决循环依赖的流程

以 A 和 B 的循环依赖为例:

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

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

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

步骤 2:创建 Bean B
  1. Spring 开始创建 B,调用构造函数实例化 B

  2. 将 B 的工厂对象放入 三级缓存,并从 一级、二级缓存 中移除 B

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

步骤 3:解决 A 的依赖
  1. 查找 A一级缓存(无)→ 二级缓存(无)→ 三级缓存(找到 A 的工厂对象)。

  2. 通过工厂对象生成 A 的早期引用(未完成属性注入的 A 实例)。

  3. 将 A 的早期引用从三级缓存移动到 二级缓存

  4. 将 A 的早期引用注入到 B 中,完成 B 的属性注入。

  5. 将初始化完成的 B 放入 一级缓存,并清除二、三级缓存中的 B

步骤 4:完成 A 的初始化
  1. 将 B(已在一级缓存)注入到 A 中,完成 A 的属性注入。

  2. 执行 A 的初始化后方法(如 @PostConstruct)。

  3. 将 A 放入 一级缓存,并清除二、三级缓存中的 A


4. 关键限制条件

  1. 仅支持单例 Bean:原型(Prototype)作用域的 Bean 无法解决循环依赖。

  2. 依赖注入方式

    • Setter/Field 注入:支持循环依赖(属性注入在对象实例化后执行)。

    • 构造器注入:无法解决循环依赖(对象未实例化完成时无法提前暴露引用)。

  3. AOP 代理:若 Bean 被代理(如通过 @Async 或 @Transactional),三级缓存的工厂对象会生成代理对象的早期引用。


5. 如何避免循环依赖?

  1. 代码设计优化

    • 使用接口或抽象类解耦具体实现。

    • 通过 @Lazy 延迟加载非关键依赖。

  2. 依赖方向控制:使用 @DependsOn 显式指定 Bean 初始化顺序。

  3. 合并职责:若两个 Bean 强耦合,考虑合并为一个 Bean。


总结

Spring 通过三级缓存提前暴露未完成初始化的对象,巧妙地解决了单例 Bean 的循环依赖问题。理解这一机制有助于在开发中避免因设计缺陷导致的循环依赖,同时为排查相关异常提供理论依据。


http://www.kler.cn/a/550910.html

相关文章:

  • 前端模板引擎
  • 前端常见面试题-2025
  • ruby 的安装
  • 机器学习_19 集成学习知识点总结
  • less-8 boolen盲注,时间盲注 函数补全
  • 【Linux】Linux 文件系统——剖析文件权限概念,文件类型和inode号
  • VIM操作命令-全选复制删除
  • 【再读】2501.12948/DeepSeek-R1通过强化学习提升大型语言模型(LLMs)的推理能力
  • ceph部署-14版本(nautilus)-使用ceph-ansible部署实验记录
  • Linux中线程创建,线程退出,线程接合
  • 假面与演员:到底是接口在使用类,还是类在使用接口?编程接口与物理接口的区别又是什么?
  • Office-Tab-for-Mac Office 窗口标签化,Office 多文件标签化管理
  • java练习(28)
  • 【C++初阶】类和对象①
  • Maven 中的 Artifact 与 GroupId:定义与使用
  • Java并发编程——AQS原理解析
  • 【ISO 14229-1:2023 UDS诊断(会话控制0x10服务)测试用例CAPL代码全解析⑩】
  • NetCDF数据处理
  • Linux高并发服务器开发 第十八天(信号及相关概念 信号捕捉)
  • 三、tsp学习笔记——屏幕移植