什么是循环依赖
循环依赖(Circular Dependency)是指两个或多个 Bean 相互直接或间接依赖,导致容器无法正常初始化这些 Bean。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // ServiceA 依赖 ServiceB
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // ServiceB 依赖 ServiceA
}
Spring Boot 基于 Spring 框架,其循环依赖的处理机制与 Spring 一致,但在 Spring Boot 2.6+ 版本中默认禁止了循环依赖(通过spring.main.allow-circular-references=false)。
产生循环依赖的原因
1.构造函数注入循环依赖:
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { // 构造函数注入
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) { // 构造函数注入
this.serviceA = serviceA;
}
}
-
直接报错:构造函数注入的循环依赖无法解决,容器启动时抛出
BeanCurrentlyInCreationException。
2.Setter/Field 注入循环依赖:
在spring中使用@Autowired注解标签进行自动注入,如果不加以处理,会出现循环依赖问题。
怎么解决循环依赖
在Springboot2.5以前可以通过三级缓存解决单例 Bean 的循环依赖问题。
| 缓存名称 | 职责 |
|---|---|
singletonObjects |
存放完全初始化好的 Bean(一级编程客栈缓存) |
earlySingletonObjects |
存放提前暴露的早期 Bean(二级缓存) |
singletonFactories |
存放 Bean 的工厂对象(三级缓存) |
以最初的ServiceA与ServiceB为例,
-
创建
ServiceA,通过工厂将其半成品引用存入三级缓存。 -
ServiceA注入ServiceB,触发ServiceB的创建。 -
创建
ServiceB,同样将其半成品引用存入三级缓存。 -
ServiceB注入ServiceA时,从三级缓存中获取ServiceA的早期引用,完成ServiceB的初始化。 -
ServiceB&nbKVwaOKlNwKsp;初始化完成后,ServicjseA完成依赖注入,最终初始化。
出现循环依赖之后的几个解决思路:
1.避免循环依赖(推荐)
-
重构代码:将公共逻辑抽离到第三个 Bean 中。
-
使用接口或抽象类:通过面向接口编程解耦具体实现。
2.允许循环依赖(临时方案)
在application.properties中显式允许循环依赖:
# Spring Boot 2.6+ 需要手动开启 spring.main.allow-circular-references=true
这种只适用于Springboot版本在2.6以上的循环依赖被禁止的情形。
3.使用@Lazy延迟加载
在其中一个依赖上添加@Lazy,延迟注入 Bean 的初始化:
@Service
public class ServiceA {
@Lazy
@Autowired
private ServiceB serviceB; // 延迟初始化 ServiceB
}
4.调整注入方式
优先使用 Setter/Field 注入:避免构造函数注入导致的不可解循环依赖。
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) { // Setter 注入
this.serviceB = serviceB;
}
}
使用setter注入
循环依赖的局限性
KVwaOKlNwK
-
构造函数注入无法解决循环依赖:Spring 容器在创建 Bean 时需先完成构造函数调用,此时依赖的 Bean 尚未初始化。
-
原型(Prototype)作用域的 Bean:Spring 不管理原型 Bean 的完整生命周期,无法解决其循环依赖。
-
AOP 代理问题:如果 Bean 被 AOP 代理(如
@Async、@Transactional),可能导致循环依赖解决失败。