Spring 循环依赖

354 阅读4分钟

1、版本说明

1. 说明

关于循环依赖,SpringBoot 在 2.6.x 版本有两个改动

  1. 循环依赖是否开启的控制变量位置变动

    • 从原本的 AbstractAutowireCapableBeanFactory 类移动到了 SpringApplication
  2. 默认值从 true 改为无默认值,但是默认的赋值是 false

 // 2.5.x 及以前
 class AbstractAutowireCapableBeanFactory
 private boolean allowCircularReferences = true;
 ​
 // 2.6.x 及以后
 class SpringApplication
 private boolean allowCircularReferences;

虽然没有默认值,但是通过 debug,可以看到默认启动时,默认赋值为 false

image.png

2、开关配置

// 2.6.x 之后
spring.main.allow-circular-references = true

3、为何改动

2.6.x 之后 SpringBoot 默认禁止循环依赖(Spring 并没有),因为认为循环依赖的出现本身就意味着项目存在设计问题,不应该容忍循环依赖的存在

二、循环依赖Bean创建流程

image.png

步骤说明(图示说明)

 // 例子
 class A {
     @Autowired
     private B b;
 }
 class B {
     @Autowired
     private A a;
 }
  1. 步骤 1:通过 A 的构造器构建原生对象 A,并将其放入三级缓存,之后进行属性注入

  2. 步骤 3:分别从一级、二级、三级缓存中尝试获取(当然没有啦),于是开始通过 B 的构造器构建原生对象 B,并将其放入三级缓存,之后进行属性注入

  3. 步骤 4 和 步骤 5:从三级缓存中可以找到原生对象 A,然后对原生对象 A 进行 AOP 处理,生成代理对象 ProxyA,将其放入二级缓存,注入给原生对象 B

    • 将 ProxyA 放入二级缓存并将其从三级缓存中删除
  4. 步骤 6:原生对象 B 属性注入完成,开始进行 AOP 处理,生成代理对象 ProxyB,放入一级缓存并返回给原生对象 A 进行属性注入

  5. 步骤 8:原生对象 A 从二级缓存中取出自己的代理对象 ProxyA,放入一级缓存

一级、二级、三级缓存列表展示(A代表 People,B代表 User)

image.png

三、循环依赖有哪些

以下都是说单例,非单例循环依赖无法解决

普通 + 普通、普通 AOP + 普通 AOP:依靠 spring 三级缓存解决

普通 + 构造器、普通 AOP + 构造器 AOP:通过 @DependsOn 注解决定加载优先级解决

构造器 + 构造器:通过 @Lazy 解决

四、字段注入 + 构造器注入解决方案

解决方案

  1. 【不推荐】构造器注入的 Bean 名字在字典中靠前

    • 例如 People 比 User 靠前
  2. 使用 @DependsOn(String[] beanName) 让指定 bean 加载优先级高于当前 bean

  3. 反向操作【也不推荐】:使用 @Lazy 懒加载,可以理解为降低了优先级,让当前 Bean 低于其他 Bean 加载

说明

字段注入 + 构造器注入进行循环依赖是否会报错取决于两个 Bean 的加载顺序

当字段注入的 Bean 先行加载,则不会报错

  • 字段注入的 Bean 先行加载,在属性注入前已经将自己的原生 Bean 放入三级缓存,构造器注入可以取到原生对象进行注入,所以可以正常注入

当构造器注入的 Bean 先行加载就会报错

  • 构造器注入的 Bean 先行加载,未进行到三级缓存,导致字段注入的 Bean 加载时无法获取原生对象而报错

Bean 的加载顺序:取决于 Bean Name 和优先级

  1. 优先级高的先行加载,通过 @DependsOn(String[] beanName) 注解提高优先级

  2. Bean Name 通过字典排序,靠前的优先加载

    • 例如 A Bean 优先于 B Bean 加载

示例

 @Component
 @AllArgsConstructor
 @DependsOn({"user"})
 public class People {
     private User user;
 }
 ​
 @Component
 public class User {
     @Autowired
     private People people;
 }
 ​
 // 如果上述 People 不加 @DependsOn,则会导致循环依赖异常

五、构造器注入 + 构造器注入解决方案

解决方案

让参与循环依赖的 Bean 的相关构造器上加入 @Lazy(其中一个构造器加上就可以) 可以解决构造器注入造成的循环依赖问题

说明

构造器加入 @Lazy 不会导致懒加载,只会让注入参数变为代理对象

@Lazy 底层原理

本质是为注解类生成代理类,当注入时,注入的是该类的代理类,所以可以正常通过构造器生成代理对象,从而依赖三级缓存完成注入

即引入一个第三方,来打破循环依赖

示例

 @Component
 public class User {
     private People people;
 ​
     @Lazy
     public User(People people) {
         this.people = people;
     }
 }
 ​
 @Component
 public class People {
     private User user;
 ​
     public People(User user) {
         this.user = user;
     }
 }