Spring的循环依赖?如何解决?【通俗易懂】

一、前言

  • Spring的循环依赖(Circular Dependency)是指两个或多个Bean之间相互依赖,形成闭环
  • 例如,Bean A依赖Bean B,而Bean B又依赖Bean A。
  • 这种情况下,Spring在初始化Bean时可能因无法确定创建顺序而抛出异常。
  • 代码示例
//A依赖B
@Service
public class A{
	@Autowired
	private B b;
}
//B依赖A
@Service
public class B{
	@Autowired
	private A a;
}
//或者自己依赖自己
public class C{
	@Autowired
	private C c;
}
  • 可能产生的问题
    • 例如A要依赖B,发现B还没创建,于是开始创建B;
    • 但创建B的过程中发现B又要依赖A,而A也未创建好,因为它要等B创建好,这样它们两个就在这卡Bug了。
      Spring的循环依赖?如何解决?【通俗易懂】_第1张图片

二、如何解决循环依赖?

  • Spring通过三级缓存机制提前暴露未完全初始化的Bean来解决循环依赖问题,具体流程如下:

1.三级缓存的核心作用

  • 一级缓存(singletonObjects):存储完全初始化后的单例Bean。
  • 二级缓存(earlySingletonObjects):存储早期Bean(已实例化但未完成属性注入和初始化的半成品)。
  • 三级缓存(singletonFactories):存储Bean的工厂对象(ObjectFactory),用于生成早期Bean。

2.解决流程

  • (1)创建Bean A
    • 实例化A(通过构造方法反射),生成原始对象(未注入属性),此时A是一个半成品
    • 将A的工厂对象(用于生成代理对象,如存在AOP)存入三级缓存singletonFactories。
  • (2)依赖注入
    • 发现A依赖B,尝试从缓存中获取B:
      • 一级缓存无B → 创建B。
    • 创建Bean B:
      • 实例化B,生成半成品对象
      • 将B的工厂对象存入三级缓存。
      • 发现B依赖A,尝试从缓存获取A:
        • 一级缓存无A → 从三级缓存获取A的工厂对象 → 生成A的早期对象(可能是代理对象) → 存入二级缓存earlySingletonObjects。
      • B完成属性注入(将二级缓存中的A的早期对象注入B)→ B初始化完成 → 存入一级缓存。
  • (3)完成A的初始化
    • 从一级缓存获取已初始化的B,注入到A中。
    • A初始化完成 → 从二级缓存移除,存入一级缓存。

3.关键点

  • 提前暴露半成品:通过三级缓存提前暴露未完全初始化的Bean,打破循环依赖的闭环。
  • 处理AOP代理:若Bean需要代理(如AOP),三级缓存的ObjectFactory会生成代理对象,确保依赖注入的是最终代理对象,避免对象不一致。
  • 仅支持单例模式Spring的循环依赖解决方案仅适用于单例作用域的Bean,原型(Prototype)作用域的Bean无法解决。

4.通俗理解

Spring的循环依赖?如何解决?【通俗易懂】_第2张图片

  • 先创建 A,此时的 A是不完整的(没有注入B),用个 map 保存这个不完整的 A;
  • 再创建 B,B需要A,所以从那个 map 得到“不完整”的 A,此时的 B 就完整了;
  • 然后 A 就可以注入 B,然后 A 就完整了;
  • 最终A和B 都完整了,且它们是相互依赖的。

5.实现步骤

Spring的循环依赖?如何解决?【通俗易懂】_第3张图片


三、常见限制与注意事项

  • 构造器注入无法解决循环依赖
    • 构造器注入在实例化阶段就需要依赖对象,此时Bean尚未存入缓存,导致无法解析。
    • 推荐使用Setter注入或字段注入。
  • 避免复杂依赖链
    • 虽然Spring解决了循环依赖,但复杂依赖可能影响性能
    • 建议通过设计模式(如中介者模式)或代码重构减少耦合。
  • 配置allow-circular-references
    • Spring Boot 2.6+默认禁止循环依赖,可通过配置开启:
spring:
  main:
    allow-circular-references: true

四、总结

  • Spring通过三级缓存机制,在Bean实例化后属性注入前暴露半成品对象;
  • 并结合动态代理技术(如AOP)解决了单例Bean的循环依赖问题。
  • 开发者应优先通过代码设计避免循环依赖,仅在必要时使用Spring的解决方案。

你可能感兴趣的:(spring,java,后端,spring,boot,循环依赖,java,ee,spring的三级缓存)