Spring 循环依赖

循环依赖

  • 一级缓存(singletonObjects):存放完全初始化好的Bean,可直接使用。

  • 二级缓存(earlySingletonObjects):存放提前暴露的Bean(已实例化但未完成属性填充和初始化)。

  • 三级缓存(singletonFactories):存放Bean工厂对象(ObjectFactory),用于生成未完成初始化的Bean。

过程:

  • 创建Bean A: 实例化A:调用构造函数创建A对象(此时属性未填充)。将A的ObjectFactory存入三级缓存。发现A依赖B:尝试从一级缓存获取B,若不存在则开始创建B。
  • 创建Bean B: 调用构造函数创建B对象。将B的ObjectFactory存入三级缓存。发现B依赖A:尝试从一级缓存获取A,未找到。从三级缓存获取A的ObjectFactory并生成A的早期引用(未完成初始化的A对象,也就是二级缓存内的bean)。将A的早期引用注入B:B完成属性填充和初始化,并移入一级缓存。
  • 完成Bean A的初始化,从一级缓存获取B,A完成属性填充和初始化,移入一级缓存。

注意事项:

  • Spring 只能解决 单例模式(singleton scope)下的、通过 setter / 字段注入的循环依赖。

如果使用的是:

  • 构造器注入(Constructor injection) → Spring 无法解决,抛出异常;

  • 原型模式(prototype scope) → Spring 不做缓存,也无法处理循环依赖。

什么场景不支持循环依赖

https://blog.csdn.net/weixin_44772566/article/details/137157048

如果使用的是:

  • 构造器注入(Constructor injection) → Spring 无法解决,抛出异常;这是因为在创建 Bean 的过程中,构造函数的调用是在对象实例化之前发生的,此时无法确定构造函数所需的依赖对象是否已经创建,从而导致循环依赖无法被解决。BeanA 的构造函数依赖于 BeanB,而 BeanB 的构造函数又依赖于 BeanA,构成了构造器循环依赖。

    @Controller
    public class StudentController {
    //3.构造方法注入
    private final StudentService studentService;
    @Autowired
    public StudentController(StudentService studentService) {
    this.studentService = studentService;
    }
    }如果当前的类中只有一个构造方法,那么 @Autowired 也可以省略,所以以上代码还可以这样写:

    @Controller
    public class StudentController { //3.构造方法注入
    private final StudentService studentService;
    public StudentController(StudentService studentService) {
    this.studentService = studentService;
    }
    }

  • 原型模式(prototype scope) → Spring 不做缓存,也无法处理循环依赖。对于原型(prototype)作用域的 Bean,Spring 容器在创建时不会缓存对象实例而是在每次请求时都会创建一个新的实例。因此,如果原型 Bean A 的某个属性依赖于原型 Bean B,而 Bean B 的某个属性又依赖于 Bean A,这种循环依赖无法通过 Spring 的循环依赖处理机制解决。这是因为 Spring 容器无法在创建原型 Bean 时提前暴露半初始化的对象,也无法缓存原型 Bean 的实例。PrototypeBeanA 的属性 beanB 依赖于 PrototypeBeanB,而 PrototypeBeanB 的属性 beanA 又依赖于 PrototypeBeanA,构成了原型 Bean 属性注入循环依赖。

    // PrototypeBeanA.java
    @Scope("prototype")
    public class PrototypeBeanA {
    private PrototypeBeanB beanB;

    复制代码
      public void setBeanB(PrototypeBeanB beanB) {
          this.beanB = beanB;
      }

    }

    // PrototypeBeanB.java
    @Scope("prototype")
    public class PrototypeBeanB {
    private PrototypeBeanA beanA;

    复制代码
      public void setBeanA(PrototypeBeanA beanA) {
          this.beanA = beanA;
      }

    }

如何处理的循环依赖

Spring 的循环依赖解决方案主要依赖于两个技术:BeanPostProcessor 和三级缓存。

  • BeanPostProcessor

BeanPostProcessor 是 Spring 中的一个接口,它提供了两个方法:postProcessBeforeInitialization 和 postProcessAfterInitialization。这两个方法分别在 Bean 的初始化前后被调用,可以用来对 Bean 进行定制化处理。

在解决循环依赖问题时,Spring 使用 BeanPostProcessor 在 Bean 初始化之前对 Bean 进行处理,从而实现提前暴露半成品对象的目的。

  • 三级缓存

Spring 中的 BeanFactory 是一个三级缓存结构,其中包含了singletonObjects、earlySingletonObjects 和 singletonFactories 三个缓存。

当 Spring 创建一个 Bean 的时候,它会先检查 singletonObjects 缓存中是否存在该 Bean 的实例。如果存在,直接返回该实例;否则继续创建该 Bean 的实例。

如果在创建该 Bean 的过程中出现了循环依赖,Spring 会将该 Bean 的半成品对象存储在 earlySingletonObjects 缓存中,并将其标记为"当前正在创建的 Bean",然后继续创建该 Bean 所依赖的其他 Bean。当所有的 Bean 都被创建完成后,Spring 会调用 BeanPostProcessor 的 postProcessAfterInitialization 方法,将所有标记为"当前正在创建的 Bean"的半成品对象转化为完整的 Bean 对象,并存储在 singletonObjects 缓存中。

如果在创建该 Bean 的过程中需要调用其他 Bean 的工厂方法,则 Spring会将该 Bean 的工厂方法存储在 singletonFactories 缓存中,以便在创建其他 Bean 时使用。当所有的 Bean 都被创建完成后,Spring 会遍历 singletonFactories 缓存中的所有工厂方法,调用它们的 getObject() 方法,将其转换为完整的 Bean 对象,并存储在 singletonObjects 缓存中。

通过使用三级缓存和 BeanPostProcessor,Spring 能够在 Bean 的创建过程中解决循环依赖问题,并保证所有的 Bean 都被正确地创建和初始化。

从源码上解析下

https://www.aliyun.com/sswb/1569336.html

相关推荐
aithinker5 分钟前
使用QQ邮箱收发邮件遇到的坑 有些WIFI不支持ipv6
java
星火开发设计22 分钟前
C++ 预处理指令:#include、#define 与条件编译
java·开发语言·c++·学习·算法·知识
Hx_Ma1639 分钟前
SpringMVC返回值
java·开发语言·servlet
Yana.nice43 分钟前
openssl将证书从p7b转换为crt格式
java·linux
独自破碎E1 小时前
【滑动窗口+字符计数数组】LCR_014_字符串的排列
android·java·开发语言
想逃离铁厂的老铁1 小时前
Day55 >> 并查集理论基础 + 107、寻找存在的路线
java·服务器
Jack_David1 小时前
Java如何生成Jwt之使用Hutool实现Jwt
java·开发语言·jwt
瑞雪兆丰年兮1 小时前
[从0开始学Java|第六天]Java方法
java·开发语言
一点技术1 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
datalover1 小时前
CompletableFuture 使用示例
java·开发语言