在Spring Boot的开发中循环依赖是一个常见的问题,两个或多个类之间存在彼此依赖的情况,形成一个循环依赖链,在2.6.0之前,Spring Boot会自动处理循环依赖的问题,而2.6.0版本以上开始检查循环依赖,存在该问题则会报错。本文将给大家说说Spring Boot循环依赖的原理和解决方案。
循环依赖定义
当一个类在初始化时需要另一个类的实例,而另一个类又需要第一个类的实例时,就会出现循环依赖问题。这会导致应用程序无法正确地初始化和运行,因为Spring Boot 无法处理这种循环依赖关系。在启动时程序就会报错。
循环依赖问题的示例
UserService 类注入VipService 类,VipService 类注入UserService 类,就会发生循环依赖的问题。
@Service
public class UserService {
@Resource
private VipService vipService;
}
@Service
public class VipService {
@Resource
private UserService userService;
}
错误
现在,2.6.0 这个版本已经默认禁止 Bean 之间的循环引用, 则基于上面的代码,会报错:
错误提示
解决方法
-
构造函数注入: 在构造函数中注入依赖项,而不是在属性中注入。
-
Setter注入: 使用setter方法注入依赖项,而不是在构造函数中注入。
-
延迟注入: 使用@Lazy注解延迟加载依赖项。
-
@Autowired注解的required属性: 将required属性设置为false,以避免出现循环依赖问题。
-
@DependsOn注解: 使用@DependsOn注解指定依赖项的加载顺序,以避免出现循环依赖问题。
-
修改配置让SpringBoot 回到2.6以前的自动检测循环依赖。
假设有以下两个类:
public class A {
private B b;
public A() {
// ...
}
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public B() {
// ...
}
public void setA(A a) {
this.a = a;
}
}
构造函数注入
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
这样,在创建 A 实例时,只需要将 B 实例传递给 A 的构造函数即可,不需要再通过 setter 方法将 B 实例注入到 A 中。同理,在创建 B 实例时,只需要将 A 实例传递给 B 的构造函数即可,不需要再通过 setter 方法将 A 实例注入到 B 中。这样可以避免循环依赖。
延迟注入
为了解决这个问题,可以使用@Lazy注解,将类A或类B中的其中一个延迟加载。
例如,我们可以在类A中使用@Lazy注解,将类A延迟加载,这样在启动应用程序时,Spring容器不会立即加载类A,而是在需要使用类A的时候才会进行加载。这样就避免了循环依赖的问题。
@Component
public class A {
private final B b;
public A(@Lazy B b) {
this.b = b;
}
//...
}
@Component
public class B {
private final A a;
public B(A a) {
this.a = a;
}
//...
}
接口隔离的案例
假设有两个类A和B,它们之间存在循环依赖:
public class A {
private final B b;
public A(B b) {
this.b = b;
}
}
public class B {
private final A a;
public B(A a) {
this.a = a;
}
}
这时候,如果直接在Spring Boot中注入A和B,就会出现循环依赖的问题。为了解决这个问题,可以使用接口隔离。
首先,定义一个接口,包含A和B类中需要使用的方法:
public interface ComonService {
void doThings();
}
然后,在A和B类中分别注入Service接口:
public class A {
private final ComonService comonService ;
public A(ComonService comonService ) {
this.comonService = comonService ;
}
}
public class B {
private final ComonService service;
public B(ComonService comonService ) {
this.comonService = comonService ;
}
}
最后,在Spring Boot中注入Service实现类:
@Service
public class ComonServiceImpl implements ComonService {
private final A a;
private final B b;
public ComonServiceImpl (A a, B b) {
this.a = a;
this.b = b;
}
@Override
public void doThings() {
// ...
}
}
通过这种方式,A和B类不再直接依赖于彼此,而是依赖于同一个接口。同时,Spring Boot也能够正确地注入A、B和ComonServiceImpl ,避免了循环依赖的问题。
修改配置文件
spring:
main:
allow-circular-references:true
结束语
综上所述,Spring Boot循环依赖是一个需要注意和解决的问题。通过对循环依赖的原理和解决方案的深入理解,我们可以更好地避免循环依赖带来的问题,提高软件的质量和可维护性。
参考文章地址1:百度安全验证