SpringBoot 循环依赖
🏷️ Spring Boot
在 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-references 为 true
以允许循环依赖:
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 是原型模式时,服务可以正常启动,但是调用时仍然会报循环依赖的异常
- 字段注入 & Setter 方法注入
- 单例模式(singleton )时
- 如果使用
@Lazy
注解,则三种注入方式都可以- 单例模式(singleton )和原型模式( prototype )都可以正常启动并调用
现在 lombok 基本上是项目的标配,个人推荐使用 lombok 的 @RequiredArgsConstructor
注解。循环依赖时设置下 onConstructor 属性 -- @RequiredArgsConstructor(onConstructor_ = {@Lazy})
-- 就行了。这种写法除了便于编写测试方法外,注入的字段还可以设置为 final
。