解决SpringBoot循环依赖
概念引入
循环依赖:是指两个或多个模块之间相互依赖,形成一个循环的依赖关系。当这种情况发生时,编译器无法确定哪个模块应该先被编译,导致编译错误或者程序运行出现异常。循环依赖通常会在软件设计或者系统架构中出现,需要采取相应的解决方案来避免或者处理它们。
本文部分内容基于
Chat-GPT
完成
发生原理
当Spring的context开始加载所有beans的时候,它尝试按照某种顺序去创建这些beans,从而使得他们能完全工作。例如,如果我们没有循环依赖的话,可能会有如下的案例:
Bean A -> Bean B -> Bean C
这样Spring会先创建bean C,然后Spring再创建Bean B(同时把C注入到B中),然后再创建Bean A(同时把bean B注入到A中)。
但是,如果发生循环依赖,他们彼此依赖,导致Spring无法决定哪一个bean最先被创建。在这样的情况下,Spring将在加载context时产生一个BeanCurrentlyInCreationException
循环依赖只会在Spring使用 构造器注入(constructor injection)的时候才会产生;如果你使用其他类型的注入方式,这些bean只会在被调用的时候才加载到context中,这样你就不会遇到循环依赖的问题。
场景复现
在SpringBoot项目中复现循环依赖的场景:
ServiceA.java
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
ServiceB.java
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
此时,在启动类启动过程中控制台会输出下列信息提示:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| serviceA defined in file [/Users/defchan/Documents/quiz/target/classes/com/devon/quiz/service/ServiceA.class]
↑ ↓
| serviceB defined in file [/Users/defchan/Documents/quiz/target/classes/com/devon/quiz/service/ServiceB.class]
└─────┘
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.
Process finished with exit code 1
解决方法
- 方式1:配置文件添加配置项(使用属性注入情况下可生效)
在application.yml
中添加以下配置项:
spring:
main:
allow-circular-references: true
添加上述配置项后,Spring不再抛出循环依赖的错误
- 方式2:使用
@Lazy
注解
这里有一个简单的方法来打破这种循环,就是告诉Spring 在加载context之后再初始化Beans,而不是完全初始化这个bean,使用@Lazy时,Spring将创建一个代理bean注入到其他bean中,这种注入只有在bean第一次调用时才会被完全生效。
为了测试@Lazy
注解,需要将上述代码修改为以下的形式:
ServiceA.java
:
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
ServiceB.java
:
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public ServiceB(@Lazy ServiceA serviceA) {
this.serviceA = serviceA;
}
}
进过这次修改,可以发现能够正常启动了
- 方式3:使用Setter/Field注入
使用Setter注入是Spring官方文档推荐的,也是最受欢迎的。
你只需要把原来使用构造器注入(@Resource @AutoWired)的方式. 替换成setter注入(或field注入),就会解决问题,这样bean只会在被调用时才会被注入。
如下是使用Setter注入的例子:
ServiceA.java
:
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
public ServiceB getServiceB() {
return serviceB;
}
}
ServiceB.java
:
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
public ServiceA getServiceA() {
return serviceA;
}
}