Springboot循环依赖
什么是循环依赖
循环依赖(Circular Dependency) 是指两个或多个 Bean 相互直接或间接依赖,导致容器无法正常初始化这些 Bean。
@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB; // ServiceA 依赖 ServiceB
}@Service
public class ServiceB {@Autowiredprivate 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
初始化完成后,ServiceA
完成依赖注入,最终初始化。
出现循环依赖之后的几个解决思路:
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@Autowiredprivate ServiceB serviceB; // 延迟初始化 ServiceB
}
4. 调整注入方式
优先使用 Setter/Field 注入:避免构造函数注入导致的不可解循环依赖。
@Service
public class ServiceA {private ServiceB serviceB;@Autowiredpublic void setServiceB(ServiceB serviceB) { // Setter 注入this.serviceB = serviceB;}
}
使用setter注入
循环依赖的局限性
-
构造函数注入无法解决循环依赖:Spring 容器在创建 Bean 时需先完成构造函数调用,此时依赖的 Bean 尚未初始化。
-
原型(Prototype)作用域的 Bean:Spring 不管理原型 Bean 的完整生命周期,无法解决其循环依赖。
-
AOP 代理问题:如果 Bean 被 AOP 代理(如
@Async
、@Transactional
),可能导致循环依赖解决失败。
总结
Spring Boot 的循环依赖本质是 Spring 框架的机制问题,解决核心在于:
-
理解三级缓存的工作原理。
-
优先通过代码设计避免循环依赖。
-
必要时合理使用
@Lazy
或调整注入方式。
尽可能在设计之初就避免循环依赖