SpringBoot 循环依赖

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

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 以允许循环依赖:

spring:
  main:
    allow-circular-references: true
  

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

@Autowired
private ServiceA serviceA;
  
private ServiceA serviceA;

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

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

private final ServiceA serviceA;

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

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

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

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 注解来解决循环依赖问题。

字段输入:

@Autowired
@Lazy
private ServiceA serviceA;
  

Setter 方法注入:

private ServiceA serviceA;

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

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

private final ServiceA serviceA;

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

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

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