关于循环注入依赖的问题

155 阅读3分钟

一、什么是循环依赖?

在 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)提前发现循环依赖。
  • 解决:核心是通过重构(抽象接口、中间层、拆分职责)打破依赖闭环,框架场景可辅助使用注入方式调整或懒加载。

避免循环依赖的最佳实践是在设计阶段保持依赖关系清晰,遵循 “高内聚、低耦合” 原则,减少不必要的双向依赖。