一、什么是循环依赖?
在 Java 中,循环依赖(Circular Dependency)指的是两个或多个类之间形成相互依赖的关系,形成一个闭环。
简单来说,就是:
- 类 A 依赖于类 B(例如 A 中持有 B 的实例或调用 B 的方法)
- 类 B 同时也依赖于类 A(例如 B 中持有 A 的实例或调用 A 的方法)
这种依赖关系可以是直接的,也可以是间接的(通过多个类形成的链条最终回到起点)。
//类A依赖类B
public class A{
private B b;
public A(){
this.b = new B();
}
}
//类B依赖类A
public class B{
private A a;
public B(){
this.a = new A();
}
}
在这个例子中,创建 A 的实例会触发 B 的创建,而创建 B 的实例又会触发 A 的创建,形成无限循环,最终会导致栈溢出错误(StackOverflowError)。
二、解决循环依赖的核心思路
循环依赖的本质是 依赖关系设计不合理 ,解决原则是 打破依赖闭环 ,使依赖方向变为单向。
1、重构代码直接消除依赖
提取公共接口 / 抽象类:将相互依赖的部分抽象为接口,使类依赖于接口而非具体实现。
// 抽象接口
public interface AService {}
public interface BService {}
// 类A实现接口,依赖B的接口
public class A implements AService {
private BService b;
// 通过构造器或setter注入BService
public A(BService b) {
this.b = b;
}
}
// 类B实现接口,依赖A的接口
public class B implements BService {
private AService a;
// 通过构造器或setter注入AService
public B(AService a) {
this.a = a;
}
}
引入中间层:新增一个类作为中介,承担原循环依赖中的交互逻辑,使原类仅依赖中间层。
// 中间层
public class Mediator {
private A a;
private B b;
// 处理A和B的交互
}
// A和B仅依赖Mediator,不再相互依赖
public class A {
private Mediator mediator;
}
public class B {
private Mediator mediator;
}
2. 改变依赖注入方式(针对 Spring 等框架)
Spring 默认支持 单例 Bean 的循环依赖 (通过三级缓存),但构造器注入可能失败,可改用:
- Setter 注入:避免在构造器中依赖,通过 setter 方法延迟注入。
- 字段注入:使用
@Autowired直接注入字段(需注意设计合理性)。
@Service
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
// Setter注入
}
@Service public class B {
private A a;
@Autowired public void setA(A a) {
this.a = a;
}
// Setter注入
}
3. 懒加载(Lazy Initialization)
通过延迟初始化依赖对象,避免初始化时的循环调用:
- 使用
@Lazy注解(Spring):延迟加载依赖 Bean。 - 手动延迟初始化:在需要时才创建依赖实例,而非在构造器中。
@Service public class A {
private B b;
@Autowired public A(@Lazy B b) {
this.b = b;
}
// 懒加载B
}
4. 拆分职责
若类因职责过重导致循环依赖,可按单一职责原则拆分:
- 将类 A 中依赖 B 的功能拆分到新类 C。
- 使 A 依赖 C,B 依赖 C,消除 A 与 B 的直接依赖。
三、总结
- 检测:优先使用 IDE 或静态分析工具(如 SonarQube)提前发现循环依赖。
- 解决:核心是通过重构(抽象接口、中间层、拆分职责)打破依赖闭环,框架场景可辅助使用注入方式调整或懒加载。
避免循环依赖的最佳实践是在设计阶段保持依赖关系清晰,遵循 “高内聚、低耦合” 原则,减少不必要的双向依赖。