循环依赖的定义
循环依赖指的是两个或多个Bean相互依赖,形成闭环。例如,BeanA依赖BeanB,BeanB又依赖BeanA,导致Spring容器无法正确初始化这些Bean。
构造器循环依赖
构造器注入的循环依赖无法解决,因为Spring在创建Bean时必须先完成构造器调用。这种情况会直接抛出BeanCurrentlyInCreationException。
解决方案:
- 改为使用Setter注入或字段注入
- 重新设计代码结构,消除循环依赖
Setter/字段注入循环依赖
对于Setter注入或字段注入的循环依赖,Spring通过三级缓存机制可以解决:
- 一级缓存(单例池):存放完全初始化好的Bean
- 二级缓存:存放早期的Bean引用(已实例化但未完全初始化)
- 三级缓存:存放Bean工厂,用于生成早期引用
Spring处理流程:
- 创建BeanA时,先将其工厂放入三级缓存
- 发现需要BeanB,开始创建BeanB
- 创建BeanB时发现需要BeanA,从三级缓存获取BeanA的早期引用
- BeanB创建完成后,BeanA可以完成初始化
最佳实践解决方案
使用@Lazy注解 在其中一个依赖上添加@Lazy,延迟加载打破循环:
java
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
重新设计代码结构
- 提取公共逻辑到第三个服务中
- 使用接口分离依赖
- 考虑使用事件驱动模式代替直接调用
使用ApplicationContextAware
java
@Service
public class ServiceA implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.context = ctx;
}
public void someMethod() {
ServiceB serviceB = context.getBean(ServiceB.class);
// 使用serviceB
}
}
配置文件中允许循环依赖 Spring Boot 2.6+默认禁止循环依赖,如需允许:
properties
spring.main.allow-circular-references=true
注意事项
- 循环依赖通常是设计问题,应优先考虑重构
- 在测试环境下允许循环依赖可能掩盖设计缺陷
- 过多使用@Lazy可能导致运行时错误更难排查
- Spring的解决方案仅适用于单例作用域的Bean
代码结构优化示例
将共享功能提取到新服务:
java
@Service
public class CommonService {
// 共享逻辑
}
@Service
public class ServiceA {
private final CommonService commonService;
}
@Service
public class ServiceB {
private final CommonService commonService;
}