Skip to content

循环依赖

在 Spring Boot(或更广泛地说,在 Spring 框架中),循环依赖是指两个或多个 Bean 之间相互依赖,形成一个闭环。默认情况下,Spring 容器能够处理某些类型的循环依赖,特别是当至少有一个依赖是通过 setter 或字段注入时。然而,有时候你可能希望禁用这种行为,尤其是在遇到复杂的依赖关系或者为了确保代码的清晰性和可维护性。

禁用循环依赖的方法

方法 1: 使用构造器注入

最推荐的方法是使用构造器注入而不是字段注入或 setter 注入。构造器注入不仅可以避免循环依赖问题,还可以使依赖关系更加明确和不可变。如果两个 Bean 形成循环依赖,而它们都使用构造器注入,则 Spring 将抛出 BeanCurrentlyInCreationException 异常,从而阻止应用程序启动,这有助于及早发现问题。

示例:

java
@Component
public class ServiceA {
    private final ServiceB serviceB;

    @Autowired
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Component
public class ServiceB {
    private final ServiceA serviceA;

    @Autowired
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

上述代码会导致应用程序无法启动,并抛出异常,因为 Spring 无法解析这个循环依赖。

方法 2: 配置 spring.main.allow-circular-references 属性

从 Spring Boot 2.2 开始,你可以通过设置 spring.main.allow-circular-references=false 来显式地禁止循环依赖。这将导致任何循环依赖都会被检测到并抛出异常,即使这些依赖可以通过 setter 或字段注入来解决。

application.properties 示例:

properties
spring.main.allow-circular-references=false

application.yml 示例:

yaml
spring:
  main:
    allow-circular-references: false

方法 3: 使用 @Lazy 注解

如果你确实需要某种形式的循环依赖,但又想避免在启动时就创建所有相关 Bean,可以考虑使用 @Lazy 注解。这样,Bean 只会在第一次被实际调用时才被实例化,而不是在应用程序启动时立即创建。

示例:

java
@Component
public class ServiceA {
    private final ServiceB serviceB;

    @Autowired
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

这种方法可以缓解某些情况下的循环依赖问题,但它并没有真正“禁用”循环依赖,而是推迟了它的发生。

方法 4: 修改 Bean 的作用域

对于特定场景,改变 Bean 的作用域也可能有助于解决循环依赖问题。例如,将其中一个 Bean 的作用域改为 prototype 而不是默认的 singleton。不过,这种方法通常不是最佳实践,因为它改变了 Bean 的生命周期管理方式。

构造器注入 是防止循环依赖的最佳实践,它不仅提高了代码的可测试性,还使得依赖关系更加清晰。

设置 spring.main.allow-circular-references=false 可以全局禁用循环依赖,确保任何潜在的问题都能在开发阶段被发现。

使用 @Lazy 注解可以在不影响功能的前提下缓解一些循环依赖问题,但并不推荐作为常规解决方案。

修改 Bean 的作用域应该谨慎使用,因为它会改变 Bean 的生命周期管理逻辑。

通过以上方法之一或结合使用,你可以有效地管理和禁用 Spring Boot 应用程序中的循环依赖,确保应用的稳定性和可维护性。