前言
循环依赖最根本的解决之道仍是重构设计 ,避免双向依赖。如果无法重构,优先考虑Spring提供的@Lazy或字段/Setter注入(配合三级缓存)。其他手动方式应作为备选,因为它们增加了代码与容器的耦合。
介绍
从单例Bean的初始化来看,循环依赖发生在第二步,也就是填充属性的一步。


通过spring三级缓存机制解决
1、三级缓存原理
在Spring容器的整个生命周期中,只有Scope为singleton(单例)才会注入到spring工厂中,由于单例Bean只有一个对象,于是可以使用缓存来管理它。




在Spring框架中,两个单例Bean互相依赖(即循环依赖)时,可以使用字段注入 (如@Autowired标注在字段上)或Setter注入 (如@Autowired标注在Setter方法上)来解决,Spring通过三级缓存 机制能够自动处理这种循环依赖。这种机制依赖于对象先实例化、后注入属性的顺序,因此字段注入和Setter注入都支持循环依赖。
2、通过字段注入@Autowired 解决示例
使用字段注入 (如@Autowired标注在字段上)来解决,Spring通过三级缓存 机制能够自动处理这种循环依赖。这种机制依赖于对象先实例化、后注入属性的顺序,因此字段注入支持循环依赖。
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
3、两个Bean使用Setter注入解决示例
在Spring中,两个单例Bean互相依赖(即循环依赖)时,可以使用Setter注入来解决。Setter注入属于属性注入的一种,Spring容器会先实例化Bean,再通过Setter方法注入依赖,因此可以利用三级缓存提前暴露早期对象引用,从而打破循环。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ClassA {
private ClassB classB;
// Setter注入,标注@Autowired
@Autowired
public void setClassB(ClassB classB) {
this.classB = classB;
}
public void doSomething() {
System.out.println("ClassA 使用了 ClassB:" + classB);
}
}
@Component
public class ClassB {
private ClassA classA;
@Autowired
public void setClassA(ClassA classA) {
this.classA = classA;
}
public void doSomething() {
System.out.println("ClassB 使用了 ClassA:" + classA);
}
}
4、字段注入和Setter注入的区别
- 字段注入 :在Bean实例化后,通过反射直接设置字段值(即使字段是
private)。这绕过了正常的访问控制,依赖于Spring内部机制。 - Setter注入:通过调用公共的Setter方法完成注入,符合JavaBean规范,是更标准的依赖注入方式。
虽然字段注入和Setter注入都能解决单例Bean的循环依赖,但循环依赖本身往往意味着设计不够合理(如高度耦合)。建议通过重构消除循环依赖
通过延迟初始化解决
1、@Lazy什么时候需要在去将对象注入。
@Lazy注解并不是通过Spring的三级缓存来解决循环依赖的,它采用的是延迟初始化策略。
@Lazy注解用于指示Spring延迟初始化一个Bean,或者延迟注入一个依赖。当标注在依赖注入点上时(如字段、setter、构造器参数),Spring会注入一个代理对象,而真正的目标Bean只有在第一次使用该代理时才会被创建和初始化。
java
@Component
public class A {
@Lazy
@Autowired
private B b; // 注入的是B的代理,B实际未创建
}
@Component
public class B {
@Autowired
private A a; // 正常注入A
}
java
@Component
public class A {
private final B b;
public A(@Lazy B b) { // 注入的是B的代理
this.b = b;
}
public void useB() {
b.doSomething(); // 首次调用时触发B的创建
}
}
@Component
public class B {
private final A a;
public B(A a) { // 注入真实的A(此时A已创建)
this.a = a;
}
}
2、在使用时从容器获取依赖
可以在需要时直接从容器中获取依赖。
java
@Component
public class A implements ApplicationContextAware {
private ApplicationContext context;
private B b; // 可以缓存,避免重复获取
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
public void doSomething() {
// 延迟获取B(首次获取后可以缓存)
if (b == null) {
b = context.getBean(B.class);
}
b.help();
}
}
@Component
public class B {
private final A a;
public B(A a) {
this.a = a;
}
public void help() {
System.out.println("B帮助A完成工作");
}
}
无法通过构造函数解决
这段代码展示的是典型的构造器循环依赖,Spring无法自动解决
java
@Component
public class A {
// B成员变量
private B b;
public A(B b){
System.out.println("A的构造方法执行了...");
this.b = b;
}
}
@Component
public class B {
// A成员变量
private A a;
public B(A a){
System.out.println("B的构造方法执行了...");
this.a = a;
}
}