循环依赖 :两个或多个 Bean 互相注入(A依赖B,B依赖A),Spring 默认只能解决单例 Bean 的 setter/字段注入循环依赖,构造器注入、多例、代理场景会直接报错。
我会分场景+解决方案,从最简单到最复杂讲清楚。
一、先判断:你的循环依赖属于哪种?
1. 最常见:单例 Bean + 字段/setter 注入(无报错)
Spring 自动解决,无需处理。
java
@Service
public class A {
@Autowired
private B b; // 字段注入
}
@Service
public class B {
@Autowired
private A a;
}
✅ 正常运行,Spring 三级缓存天然支持。
2. 构造器注入循环依赖(必报错)
这是最常见的报错场景!
java
@Service
public class A {
// 构造器注入 A 依赖 B
public A(B b) {}
}
@Service
public class B {
// 构造器注入 B 依赖 A
public B(A a) {}
}
❌ 报错:Requested bean is currently in creation
解决方案(3种最优解)
方案1:改用字段/@Autowired 注入(最简单)
直接把构造器注入改成字段注入,Spring 自动解决。
方案2:使用 @Lazy 懒加载(推荐)
在构造器参数上加 @Lazy,创建临时代理对象,打破循环:
java
@Service
public class A {
public A(@Lazy B b) { // 关键:@Lazy
this.b = b;
}
}
@Service
public class B {
public B(A a) {
this.a = a;
}
}
方案3:使用 ApplicationContext 手动获取 Bean
java
@Service
public class A implements ApplicationContextAware {
private B b;
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.b = ctx.getBean(B.class); // 手动获取
}
}
3. 多例(prototype)Bean 循环依赖(必报错)
Spring 不解决多例循环依赖,无三级缓存支持。
解决方案
- 放弃多例:改成单例(90%场景适用)
- 手动创建对象:不用 Spring 管理依赖
- 使用
Provider延迟注入
java
@Component
@Scope("prototype")
public class A {
@Autowired
private ObjectProvider<B> bProvider;
public B getB() {
return bProvider.getObject();
}
}
4. 代理类导致的循环依赖(AOP/事务)
开启 AOP/@Transactional 后,Bean 被代理,默认三级缓存会失效,报错。
解决方案
方案1:启动类加配置(最省事)
java
@SpringBootApplication
public class App {
public static void main(String[] args) {
// 允许循环依赖
System.setProperty("spring.main.allow-circular-references", "true");
SpringApplication.run(App.class, args);
}
}
方案2:yml 配置
yaml
spring:
main:
allow-circular-references: true
5. 终极方案:重构代码(最规范,推荐)
循环依赖本质是代码设计不合理,最好从根源解决:
- 抽取公共类:把 A、B 共用逻辑抽到 C,A、B 都依赖 C
- 使用事件/观察者模式:解耦互相依赖
- 使用方法调用:不用注入 Bean,直接调用方法
示例(抽取公共类):
java
// 公共逻辑
@Service
public class C {}
@Service
public class A {
@Autowired private C c;
}
@Service
public class B {
@Autowired private C c;
}
✅ 彻底消除循环依赖,代码更优雅。
二、快速排查工具
如果你不知道哪里循环依赖,开启日志:
yaml
logging:
level:
org.springframework: DEBUG
日志会打印完整依赖链,直接定位报错 Bean。
三、总结:按场景选择方案
| 依赖场景 | 是否报错 | 解决方案 |
|---|---|---|
| 单例+字段注入 | 否 | 无需处理 |
| 构造器注入 | 是 | @Lazy / 字段注入 |
| 多例Bean | 是 | 改单例 / ObjectProvider |
| AOP/事务代理 | 是 | 开启 allow-circular-references |
| 所有场景 | - | 重构代码(最优) |
核心记住
- Spring 只自动解决单例+字段/setter 注入的循环依赖
- 构造器注入用
@Lazy最快解决 - 生产环境优先重构代码,不要依赖配置强行开启循环依赖