一句话总结
Spring 通过三级缓存解决单例 Bean 的 Setter/字段注入循环依赖:一级缓存 singletonObjects(完全初始化好的 Bean)、二级缓存 earlySingletonObjects(早期引用,未完成属性填充)、三级缓存 singletonFactories(ObjectFactory,可生成代理对象)。核心流程:A 创建 → 放入三级缓存 → 发现依赖 B → 创建 B → B 发现依赖 A → 从三级缓存获取 A 的早期引用 → B 完成 → A 完成。构造器注入无法解决(因为对象还没创建出来)。
初级理解
什么是循环依赖?
# 循环依赖示例
@Service
public class A {
@Autowired
private B b; // A 依赖 B
}
@Service
public class B {
@Autowired
private A a; // B 依赖 A
}
# 创建流程
# 1. 创建 A → 发现需要 B → 去创建 B
# 2. 创建 B → 发现需要 A → 去创建 A
# 3. 死循环!
哪些循环依赖可以解决?
# ✅ 可以解决:Setter/字段注入的单例 Bean
@Service
public class A {
@Autowired
private B b; // ✅ 字段注入
}
# ❌ 不能解决:构造器注入
@Service
public class A {
private final B b;
public A(B b) { // ❌ 构造器注入
this.b = b;
}
}
# 原因:构造器调用时对象还没创建,无法放入缓存
# ❌ 不能解决:原型(prototype)Bean
# 原因:原型 Bean 不放入缓存,每次都是新对象
中级深入
三级缓存详解
# 三级缓存定义(DefaultSingletonBeanRegistry)
# 一级缓存:完全初始化好的 Bean
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
# 二级缓存:早期引用(已实例化但未填充属性)
Map<String, Object> earlySingletonObjects = new HashMap<>(16);
# 三级缓存:Bean 工厂(可生成代理对象)
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
# 获取 Bean 的流程(getSingleton)
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<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
高级拓展
为什么需要三级缓存?二级不行吗?
# 核心原因:AOP 代理
# 如果 A 需要 AOP 代理,B 注入的必须是代理后的 A
# 只用二级缓存的问题:
# 二级缓存存的是原始对象
# 如果 A 需要代理,B 注入的是原始 A(错误!)
# 三级缓存的优势:
# 三级缓存存的是 ObjectFactory
# ObjectFactory.getObject() 可以返回代理对象
# 在 getObject() 中调用 BeanPostProcessor 生成代理
# 流程:
# 1. A 实例化后 → 放入三级缓存(ObjectFactory)
# 2. B 需要 A → 调用 ObjectFactory.getObject()
# 3. getObject() 中执行 BeanPostProcessor → 生成 A 的代理
# 4. 代理 A 放入二级缓存
# 5. B 注入代理 A ✅
总结:如果没有 AOP,二级缓存就够了。三级缓存是为了解决 AOP 代理场景下的循环依赖。
实战场景
场景:如何避免循环依赖?
# 1. 重新设计,消除循环依赖
# A → B → A 改为 A → B → C → A(引入中间层)
# 2. 使用 @Lazy 延迟注入
@Service
public class A {
@Lazy
@Autowired
private B b; // 延迟注入,先创建 A,用到 B 时才注入
}
# 3. 使用 Setter 注入代替构造器注入
@Service
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
# 4. 使用 ApplicationContext.getBean()
@Service
public class A {
@Autowired
private ApplicationContext context;
public void doSomething() {
B b = context.getBean(B.class); // 延迟获取
}
}
面试模拟
面试官:Spring 如何解决循环依赖?
你:通过三级缓存。一级存完全初始化好的 Bean,二级存早期引用,三级存 ObjectFactory。A 创建时先放入三级缓存,发现依赖 B 就去创建 B,B 发现依赖 A 就从三级缓存获取 A 的早期引用,B 完成后再完成 A。三级缓存是为了处理 AOP 代理场景。
面试官:为什么构造器注入无法解决循环依赖?
你:因为构造器调用时对象还没创建出来,无法放入缓存。而 Setter/字段注入时对象已经实例化,可以先放入缓存暴露早期引用。