Skip to content

SpringBoot 循环依赖

🏷️ Spring Boot

spring-boot-starter-web:2.7.5 中测试使用 Setter 方法注入是否如书中所说可以解决循环依赖问题,结果项目启动报了如下错误:

java
The dependencies of some of the beans in the application context form a cycle:

    helloController (field private service.ServiceA controller.HelloController.serviceA)
┌─────┐
|  serviceAImpl (field private service.ServiceB service.impl.ServiceAImpl.serviceB)
↑     ↓
|  serviceBImpl (field private service.ServiceA service.impl.ServiceBImpl.serviceA)
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. 
Update your application to remove the dependency cycle between beans. 
As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

根据最后一句提示,通过将 spring.main.allow-circular-references 配置为 true 确实可以解决单例模式 bean 的循环依赖问题。

根据这篇博客中的说法,自 2.6.0 版本开始,默认禁止了 bean 之间的循环依赖。

至于为何三级缓存可以解决循环依赖问题,网上文章比较多,这里仅记录下代码的写法。

方案1:允许循环依赖

设置 spring.main.allow-circular-referencestrue 以允许循环依赖:

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

之后通过字段注入或 Setter 方法注入都可以正常构建 bean 实例。

java
@Autowired
private ServiceA serviceA;
java
private ServiceA serviceA;

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

但使用构造函数注入时启动仍然会报循环引用的错误:

java
private final ServiceA serviceA;

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

这里的 @Autowired 注解不是必须的。

报错内容如下(和之前的基本一样):

java
The dependencies of some of the beans in the application context form a cycle:

   helloController (field private service.ServiceA controller.HelloController.serviceA)
┌─────┐
|  serviceAImpl defined in file [..\target\classes\service\impl\ServiceAImpl.class]
↑     ↓
|  serviceBImpl defined in file [..\target\classes\service\impl\ServiceBImpl.class]
└─────┘


Action:

Despite circular references being allowed, the dependency cycle between beans could not be broken. 
Update your application to remove the dependency cycle.

方案2:使用 @Lazy 注解

三种注入方式都可以使用 @Lazy 注解来解决循环依赖问题。

字段输入:

java
@Autowired
@Lazy
private ServiceA serviceA;

Setter 方法注入:

java
private ServiceA serviceA;

@Autowired
@Lazy
public void setServiceA(ServiceA serviceA) {
    this.serviceA = serviceA;
}

构造函数注入(@Lazy 注解也可以写在构造函数方法上):

java
private final ServiceA serviceA;

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

另外如果使用 lombok ,可以通过 @RequiredArgsConstructor 实现类似构造函数注入的效果:

java
@RequiredArgsConstructor(onConstructor_ = {@Lazy})

这样会自动在构造函数方法上添加 @Lazy 注解。

总结

  • 如果启用 spring.main.allow-circular-references 的话仍然可以同以前一样,使用 字段注入Setter 注入 可以解决循环依赖问题,但构造函数注入仍然不行。如果使用这种方案,注意避免使用原型模式。
    • 单例模式(singleton )时
      • 可以正常启动并调用
    • 原型模式( prototype )时
      • 字段注入 & Setter 方法注入
        • 调用服务的 Controller 是单例模式时,服务启动异常
        • 调用服务的 Controller 是原型模式时,服务可以正常启动,但是调用时仍然会报循环依赖的异常
  • 如果使用 @Lazy 注解,则三种注入方式都可以
    • 单例模式(singleton )和原型模式( prototype )都可以正常启动并调用

现在 lombok 基本上是项目的标配,个人推荐使用 lombok@RequiredArgsConstructor 注解。循环依赖时设置下 onConstructor 属性 -- @RequiredArgsConstructor(onConstructor_ = {@Lazy}) -- 就行了。这种写法除了便于编写测试方法外,注入的字段还可以设置为 final

参考

  1. Spring Boot 2.6.0 新特性默认禁止循环引用
  2. Spring注入方式及解决循环依赖