1. 循环依赖是什么?
循环依赖发生在两个或多个bean互相依赖的情况下,形成闭环依赖链。最简单的形式是:
Bean A → Bean B → Bean A
更复杂的场景可能涉及多个bean:
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
这种依赖关系在Spring容器启动时会导致初始化顺序问题,需要特别注意。
2. Spring中的循环依赖问题
当Spring上下文加载所有bean时,会按照依赖关系顺序创建bean。以正常依赖链为例:
Bean A → Bean B → Bean C
Spring会按顺序创建:C → B → A,并正确注入依赖。
但遇到循环依赖时,Spring无法确定初始化顺序,因为bean互相依赖。此时会抛出BeanCurrentlyInCreationException
异常。
关键点:
- 这个问题主要出现在构造器注入场景
- 使用setter/字段注入时通常不会出现,因为依赖注入发生在实际使用时而非容器启动时
3. 一个踩坑实例
下面通过构造器注入展示循环依赖问题:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(CircularDependencyB circB) {
this.circB = circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(CircularDependencyA circA) {
this.circA = circA;
}
}
测试配置类:
@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}
JUnit测试用例(空测试即可触发问题):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
@Test
public void givenCircularDependency_whenConstructorInjection_thenItFails() {
// 空测试;我们只需要上下文加载
}
}
运行结果会抛出异常:
BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?
4. 实用解决方案
4.1. 重构设计(治本之策)
循环依赖通常是设计问题的信号,建议优先考虑:
- 重新划分组件职责
- 调整依赖层次结构
- 引入中间层解耦
但实际开发中可能受限于:
- 遗留代码无法修改
- 测试覆盖限制
- 时间/资源不足
这时可以考虑以下替代方案。
4.2. 使用@Lazy注解
简单粗暴的解决方案:延迟初始化其中一个bean。Spring会创建代理对象,实际使用时才初始化完整bean。
修改CircularDependencyA
:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
效果: 测试通过,不再抛出异常。
4.3. 改用Setter/字段注入
Spring官方推荐方案:将构造器注入改为setter注入或字段注入。
修改后的bean:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(@Lazy CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
配套测试用例:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
@Autowired
ApplicationContext context;
@Bean
public CircularDependencyA getCircularDependencyA() {
return new CircularDependencyA();
}
@Bean
public CircularDependencyB getCircularDependencyB() {
return new CircularDependencyB();
}
@Test
public void givenCircularDependency_whenSetterInjection_thenItWorks() {
CircularDependencyA circA = context.getBean(CircularDependencyA.class);
Assert.assertEquals("Hi!", circA.getCircB().getMessage());
}
}
核心机制:
@Bean
定义bean获取方式@Lazy
延迟初始化依赖bean- 实际使用时才完成注入,打破循环依赖链
4.4. @PostConstruct方案
通过初始化后回调设置依赖:
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
@PostConstruct
public void init() {
circB.setCircA(this);
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
优点: 保持构造器注入的不可变性,同时解决循环依赖。
4.5. 实现ApplicationContextAware
手动控制bean获取过程:
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
private CircularDependencyB circB;
private ApplicationContext context;
public CircularDependencyB getCircB() {
return circB;
}
@Override
public void afterPropertiesSet() throws Exception {
circB = context.getBean(CircularDependencyB.class);
}
@Override
public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
context = ctx;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
适用场景: 需要精细控制bean初始化流程的复杂场景。
5. 总结
处理Spring循环依赖的方案优先级:
- 首选重构设计 - 从根本上解决问题
- setter注入 - Spring官方推荐方案
- @Lazy注解 - 简单有效的临时方案
- @PostConstruct - 保持构造器注入的折中方案
- ApplicationContextAware - 复杂场景下的终极方案
核心原则:
- 优先考虑设计优化而非技术绕过
- 根据实际场景选择最合适的解决方案
- 保持代码可维护性和可测试性
所有示例代码可在GitHub项目中获取。