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循环依赖的方案优先级:

  1. 首选重构设计 - 从根本上解决问题
  2. setter注入 - Spring官方推荐方案
  3. @Lazy注解 - 简单有效的临时方案
  4. @PostConstruct - 保持构造器注入的折中方案
  5. ApplicationContextAware - 复杂场景下的终极方案

核心原则:

  • 优先考虑设计优化而非技术绕过
  • 根据实际场景选择最合适的解决方案
  • 保持代码可维护性和可测试性

所有示例代码可在GitHub项目中获取。


原始标题:Circular Dependencies in Spring

» 下一篇: AssertJ的Java 8特性