Springboot怎么解决循环依赖
一、什么是循环依赖?
循环依赖(Circular Dependency) 指两个或多个 Bean 之间相互依赖,导致在创建 Bean 的过程中出现“死循环”,增加了对象依赖的混乱性,依赖关系变得错综复杂。
常见三种类型的循环依赖:
类型 | 举例 | Spring 是否能解决 |
---|---|---|
构造器注入循环依赖 | A → B → A(构造方法注入) | ❌ |
Setter / 字段注入循环依赖 | A → B → A(@Autowired) | ✅ |
Prototype 范围循环依赖 | A(原型) → B(原型) → A | ❌ |
1. 构造器注入循环依赖(Spring ❌无法解决)
@Component
public class A {private final B b;@Autowiredpublic A(B b) {this.b = b;}
}@Component
public class B {private final A a;@Autowiredpublic B(A a) {this.a = a;}
}
❌ 结果:
报错:
BeanCurrentlyInCreationException: Requested bean is currently in creation: Is there an unresolvable circular reference?
-
原因:Spring 必须先构造 A 才能注入 B,但 B 的构造又依赖 A,导致死循环,无法通过三级缓存提前暴露 Bean。
2. 字段(或 setter)注入循环依赖(Spring ✅能自动解决)
@Component
public class A {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate A a;
}
✅ 结果:
Spring 能自动解决,应用成功启动。
-
原因:Spring 会先构造出一个“空的 A 实例”,将其工厂加入三级缓存,B 注入 A 时就能拿到早期引用,从而打破循环。
-
前提:spring.main.allow-circular-references: true,必须开启情况下才能自动解决。然Spring 6.0 起(包括 Spring Boot 3.x)默认为false
3. 原型作用域的循环依赖(Spring ❌无法解决)
@Component
@Scope("prototype")
public class A {@Autowiredprivate B b;
}@Component
@Scope("prototype")
public class B {@Autowiredprivate A a;
}
❌ 结果:
报错:
BeanCurrentlyInCreationException
(创建过程中找不到可注入的 Bean)
-
原因:Spring 不缓存 prototype Bean 的创建过程,无法通过三级缓存解决依赖链,原型 Bean 不参与依赖管理。
二、Spring 如何解决循环依赖(基于单例 Bean)
Spring 采用一种经典的 三级缓存机制(3-level cache) 来解决循环依赖。这个机制存在于DefaultSingletonBeanRegistry中。
🚨 前提:仅对 @Scope("singleton")
且使用字段或 setter 注入有效!
1. Bean 创建流程概览(以 A → B → A 为例)
✅ Step-by-step:
-
创建 A 实例(构造函数执行);
-
A被标记为“正在创建”,并将一个工厂(ObjectFactory)放入三级缓存;
-
A 依赖 B → Spring 创建 B;
-
B 构造完成,发现依赖 A → 尝试获取 A;
-
Spring 发现 A 正在创建 → 从三级缓存拿到 ObjectFactory 生成早期 A 对象 → 放入二级缓存;
-
B 成功注入 A,初始化完成;
-
回到 A,完成初始化。
整个过程中 Spring 使用缓存提前暴露未完成的 A 实例,从而打破了循环。
2. 三级缓存详解
缓存层级 | 名称 | 描述 | 作用 |
---|---|---|---|
一级缓存 | singletonObjects | 完全初始化完成的 Bean | 最终返回 Bean 实例 |
二级缓存 | earlySingletonObjects | 早期曝光的 Bean 实例 | 用于依赖注入 |
三级缓存 | singletonFactories | 创建早期 Bean 的工厂 | 延迟暴露 Bean 引用,支持代理等 |
Spring 将 Bean 提前曝光的流程:
singletonFactories -> earlySingletonObjects -> singletonObjects
3. 核心方法说明(来自源码)
在 Spring 源码中,关键方法如下:
// DefaultSingletonBeanRegistry.java// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>();// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
-
在创建 Bean 前:Spring 把一个生成 Bean 的工厂方法放入三级缓存。
-
在注入依赖时:发现依赖的是一个“正在创建”的 Bean,就会去三级缓存中拿工厂生产早期对象。
-
最后再完成依赖注入,放入一级缓存,清除早期引用。
三、业务开发者解决循环依赖的方法
1、使用@Lazy懒加载依赖
使用`@Lazy`注解延迟注入依赖属性。
@Component
public class A {@Autowired@Lazyprivate B b;
}
2、将依赖的代码移入新类,打破依赖闭环。
A → MiddleService → B
3、在方法中动态调用spring容器的getBean方法获取依赖,达到延迟获取bean,避免类中直接注入循环依赖的bean
使用 ObjectFactory 或 ApplicationContext.getBean() 延迟获取 Bean
@Component
@Scope("prototype")
public class A {@Autowiredprivate ObjectFactory<B> bFactory;public void use() {B b = bFactory.getObject(); // 延迟获取}
}
4、改为 setter 或字段注入(避免构造器注入)
构造器注入是“强依赖”,无法提前暴露:
@Component
public class A {private B b;@Autowiredpublic void setB(B b) {this.b = b;}
}
5、使用@PostConstruct 或 工厂方法延迟注入
将依赖注入放到初始化之后:
@Component
public class A {private final B b;public A(B b) {this.b = b;}@PostConstructpublic void init() {// 在这里安全使用 b}
}
6、开启Spring Boot循环依赖(不推荐,除非必要)。
spring:main:allow-circular-references: true