Spring 通过三级缓存机制解决单例 Bean 的循环依赖问题,但仅适用于通过 setter/字段注入的循环依赖,构造器注入的循环依赖无法通过三级缓存解决。
一、三级缓存结构
// Spring 中的三级缓存
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的 Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期暴露的 Bean(半成品,已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放 Bean 工厂,用于生成早期引用(可能创建代理对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
二、解决流程(A 依赖 B,B 依赖 A)
1. 创建 A 的流程
// 1. 开始创建 A
// 2. 实例化 A(调用构造器),但未设置属性 → 得到一个"原始对象"
// 3. 将 A 的 ObjectFactory 放入三级缓存(singletonFactories)
// 4. 开始为 A 注入属性,发现依赖 B
// 5. 尝试从一级缓存获取 B(没有) → 开始创建 B
2. 创建 B 的流程
// 1. 实例化 B
// 2. 将 B 的 ObjectFactory 放入三级缓存
// 3. 为 B 注入属性,发现依赖 A
// 4. 查找 A 的流程:
// - 一级缓存:无
// - 二级缓存:无
// - 三级缓存:找到 A 的 ObjectFactory
// - 通过 ObjectFactory.getObject() 获取 A 的早期引用
// - 将 A 从三级缓存移到二级缓存
// 5. B 成功注入 A 的早期引用
// 6. B 初始化完成,放入一级缓存
3. 完成 A 的创建
// 1. B 创建完成后,A 获得完整的 B
// 2. A 完成属性注入和初始化
// 3. 将 A 从二级缓存移除,放入一级缓存
三、关键设计
1. 三级缓存的作用
- 一级缓存:完整的 Bean,可直接使用
- 二级缓存:避免重复创建代理对象(性能优化)
- 三级缓存:生成早期引用,支持 AOP 代理
2. 代码示例
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB; // setter/字段注入,支持循环依赖
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA; // 循环依赖
}
// 构造器注入无法解决循环依赖!
@Component
public class ServiceC {
private final ServiceD serviceD;
@Autowired
public ServiceC(ServiceD serviceD) { // 构造器注入,循环依赖会报错
this.serviceD = serviceD;
}
}
四、使用条件与限制
✅ 支持的情况
- 必须是单例 Bean(scope=”singleton”)
- 必须是Setter 注入或字段注入(@Autowired)
- 必须是非构造器注入
❌ 不支持的情况
// 1. 构造器注入的循环依赖(会抛出 BeanCurrentlyInCreationException)
@Bean
public A a(B b) { return new A(b); }
@Bean
public B b(A a) { return new B(a); }
// 2. 原型(prototype)作用域的 Bean
@Component
@Scope("prototype") // 原型 Bean 不支持循环依赖
public class PrototypeBean { ... }
// 3. @Async 注解的方法(因为需要代理)
五、源码关键方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 2. 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 4. 创建早期引用(可能生成代理)
singletonObject = singletonFactory.getObject();
// 5. 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
六、实际开发建议
- 尽量避免循环依赖,这是代码设计问题
- 如果必须使用,确保:
- 使用字段注入或 Setter 注入
- Bean 是单例的
- 考虑使用
@Lazy延迟加载
@Component public class ServiceA { @Autowired @Lazy // 延迟注入,打破循环 private ServiceB serviceB; } - 使用 ApplicationContext 手动获取
@Component public class ServiceA { @Autowired private ApplicationContext context; public void doSomething() { // 需要时才获取 ServiceB serviceB = context.getBean(ServiceB.class); } }
总结
Spring 通过三级缓存机制解决了大部分循环依赖问题,但这本质上是框架层面的妥协方案。良好的代码设计应该避免循环依赖,保持依赖关系的单向性。