循环依赖
循环依赖(Circular Dependency)指两个或多个 Bean 相互依赖,形成闭环。Spring 能解决部分循环依赖,但 Spring Boot 2.6 起默认禁止循环依赖,鼓励通过设计消除。
问题表现
BeanCurrentlyInCreationException: Error creating bean with name 'A':
Requested bean is currently in creation: Is there an unresolvable circular reference?
典型场景:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // A 依赖 B
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // B 依赖 A → 循环
}Spring 三级缓存机制
Spring 通过三级缓存解决单例 + setter/field 注入的循环依赖(不适用于构造器注入):
| 级别 | Map 名称 | 存储内容 |
|---|---|---|
| 一级缓存 | singletonObjects | 完整初始化好的 Bean |
| 二级缓存 | earlySingletonObjects | 实例化但未完成属性注入的早期引用 |
| 三级缓存 | singletonFactories | Bean 工厂(用于生成早期引用,支持 AOP 代理) |
解决过程(A 依赖 B,B 依赖 A):
1. 创建 A 的实例(构造器调用),放入三级缓存(ObjectFactory)
2. 注入 A 的属性 → 需要 B
3. 创建 B 的实例,放入三级缓存
4. 注入 B 的属性 → 需要 A
5. 从三级缓存获取 A 的 ObjectFactory,生成 A 的早期引用,放入二级缓存
6. B 完成属性注入,移入一级缓存
7. A 完成属性注入(B 已就绪),移入一级缓存
为什么构造器注入无法解决?
构造器注入时,Bean 实例化(调用构造器)本身就需要依赖对象,此时没有任何缓存可以提前暴露,必然死锁:
// 必然报错:构造器注入的循环依赖无法解决
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { // 构造器注入
this.serviceB = serviceB;
}
}Spring Boot 2.6+ 默认禁止循环依赖
Spring Boot 2.6 起,循环依赖默认报错。若临时需要允许(不推荐):
spring:
main:
allow-circular-references: true这是一个逃生口,不应作为长期方案。
解决方案
方案一:重构设计(根本解决,首选)
循环依赖通常意味着职责划分不清,提取公共逻辑到第三个类:
// 原问题:OrderService ↔ UserService 互相依赖
@Service
public class OrderService {
@Autowired UserService userService;
}
@Service
public class UserService {
@Autowired OrderService orderService;
}
// 解决:提取共用逻辑到 OrderUserRelationService
@Service
public class OrderService {
@Autowired OrderUserRelationService relationService; // 无循环
}
@Service
public class UserService {
@Autowired OrderUserRelationService relationService; // 无循环
}
@Service
public class OrderUserRelationService {
@Autowired OrderRepository orderRepo;
@Autowired UserRepository userRepo;
// 承载原来两个 Service 互相调用的逻辑
}方案二:@Lazy(延迟注入,最快修复)
在其中一个注入点加 @Lazy,使该 Bean 在首次使用时才真正初始化,打破启动时的依赖环:
@Service
public class ServiceA {
private final ServiceB serviceB;
// @Lazy 让 Spring 注入一个代理对象,首次调用 serviceB 方法时才真正初始化
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}@Lazy 用于 Field 注入:
@Service
public class ServiceA {
@Autowired
@Lazy
private ServiceB serviceB;
}
@Lazy注入的是 CGLIB 代理,首次方法调用才触发真实 Bean 的初始化,几乎没有功能影响,但要注意代理不适用于final类。
延迟加载机制详见 延迟加载。
方案三:setter / field 注入替代构造器注入
构造器注入的循环依赖无法解决,可改为 setter 注入打破环:
// 将其中一个改为 setter 注入
@Service
public class ServiceA {
private ServiceB serviceB;
// setter 注入允许在实例化后再注入,三级缓存可以介入
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}注意:Spring 团队推荐构造器注入(便于测试、不可变),setter 注入仅作为打破循环的手段,而不是常规选择。
方案四:@PostConstruct 延迟获取
通过 @PostConstruct 在 Bean 完全初始化后再手动获取依赖:
@Service
public class ServiceA implements ApplicationContextAware {
private ApplicationContext context;
private ServiceB serviceB; // 不在构造时注入
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.context = ctx;
}
@PostConstruct
public void init() {
// Bean 已完全就绪后再获取,避免循环
this.serviceB = context.getBean(ServiceB.class);
}
}方案五:事件驱动解耦
将相互调用改为事件发布/订阅,彻底消除代码层面的依赖:
// ServiceA 发布事件,不再直接依赖 ServiceB
@Service
@RequiredArgsConstructor
public class ServiceA {
private final ApplicationEventPublisher publisher;
public void doSomething() {
publisher.publishEvent(new SomethingHappenedEvent(this));
// 不再需要 serviceB 字段
}
}
// ServiceB 监听事件,不再直接依赖 ServiceA
@Service
public class ServiceB {
@EventListener
public void handle(SomethingHappenedEvent event) {
// 处理逻辑
}
}事件机制详见 事件机制。
特殊场景:@Configuration 类的循环依赖
@Configuration 类之间的 @Bean 方法互相依赖,Spring 通过 CGLIB 代理 @Configuration 类来保证 @Bean 方法的单例语义,通常可以自动处理:
@Configuration
public class ConfigA {
@Bean
public ServiceA serviceA(ServiceB serviceB) { // 依赖 B
return new ServiceA(serviceB);
}
}
@Configuration
public class ConfigB {
@Bean
public ServiceB serviceB(ServiceA serviceA) { // 依赖 A → 循环
return new ServiceB(serviceA);
}
}
// 此场景通常报错,需要重构或使用 @LazyAOP 代理与循环依赖
使用 @Transactional、@Async、自定义切面时,Bean 会被 AOP 代理包装。三级缓存中的 ObjectFactory 正是为此设计——可以在早期引用阶段就生成代理对象,而非真实对象:
@Service
@Transactional // AOP 代理
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // 注入的是 ServiceA 的 AOP 代理,正常工作
}如果循环依赖涉及代理且 Spring Boot 禁止了循环依赖,需要先解决循环,再加切面注解。
诊断循环依赖
Spring Boot 启动失败时,错误信息会显示依赖链:
The dependencies of some of the beans in the application context form a cycle:
serviceA → serviceB → serviceA
排查步骤:
- 查看错误信息中的依赖链
- 找到环中”最不必要”的依赖关系
- 优先用设计重构(抽取公共类)消除
- 无法重构时,在一侧加
@Lazy
各方案对比
| 方案 | 推荐度 | 适用场景 |
|---|---|---|
| 重构设计(抽取公共类) | ★★★★★ | 职责有明确重叠 |
| 事件驱动解耦 | ★★★★☆ | 单向通知场景 |
@Lazy | ★★★☆☆ | 快速修复,短期过渡 |
| setter 注入 | ★★☆☆☆ | 不适合构造器注入的遗留代码 |
allow-circular-references: true | ★☆☆☆☆ | 仅用于迁移老项目的临时方案 |