Skip to content

SpringBoot 循环依赖

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注入方式及解决循环依赖

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.