三级缓存的终极奥义:Spring 如何用 3 个 Map 解决循环依赖?
🎯 本文定位:Spring IoC 容器核心第三篇,从源码角度彻底搞懂为什么是"三级"而不是"两级",源码基于v6.2.0
一、面试题引入
高频面试问题:
- Spring 是如何解决循环依赖的?
- 为什么要用三级缓存?两级不行吗?
- 构造器注入的循环依赖为什么解决不了?
- 如果 A 依赖 B,B 依赖 A,创建流程是怎样的?
为什么这道题能区分候选人水平:
大部分人知道"三级缓存"这个词,但说不清楚每一级缓存存的是什么、为什么第三级要存 ObjectFactory 而不是直接存对象、AOP 代理如何与三级缓存配合。这些才是面试官真正想考察的。
二、功能与用法(知其然)
2.1 什么是循环依赖
@Component
public class A {
@Autowired
private B b; // A 依赖 B
}
@Component
public class B {
@Autowired
private A a; // B 依赖 A
}
如果没有特殊处理:
2.2 Spring 支持的循环依赖场景
| 单例 + Setter/字段注入 | ✅ 支持 | 可以先创建对象,后填充属性 |
| 单例 + 构造器注入 | ❌ 不支持 | 构造器执行时对象尚未创建完成 |
| 原型 Bean | ❌ 不支持 | 原型 Bean 不走缓存 |
| @Async + 循环依赖 | ⚠️ 需特殊处理 | @Async 代理可能破坏依赖关系 |
2.3 如何检测循环依赖问题
当出现不支持的循环依赖时,Spring 会抛出异常:
BeanCurrentlyInCreationException: Error creating bean with name 'a':
Requested bean is currently in creation: Is there an unresolvable circular reference?
2.4 解决构造器循环依赖的方法
方法一:使用 @Lazy 延迟加载
@Component
public class A {
private final B b;
public A(@Lazy B b) { // 🔥 注入的是 B 的代理,首次使用时才真正创建
this.b = b;
}
}
方法二:使用 Setter 注入替代构造器注入
@Component
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
方法三:使用 ObjectProvider 延迟获取
@Component
public class A {
private final ObjectProvider<B> bProvider;
public A(ObjectProvider<B> bProvider) {
this.bProvider = bProvider;
}
public void doSomething() {
B b = bProvider.getObject(); // 使用时才获取
}
}
三、源码实现拆解(知其所以然)
3.1 三级缓存的定义
源码路径:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/**
* 一级缓存:存放完全初始化好的 Bean
* Cache of singleton objects: bean name to bean instance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 三级缓存:存放 Bean 工厂对象,用于生成早期引用
* Creation-time registry of singleton factories: bean name to ObjectFactory
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
/**
* 二级缓存:存放早期暴露的 Bean(尚未填充属性)
* Cache of early singleton objects: bean name to bean instance
*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/**
* 正在创建中的 Bean 名称集合
* Names of beans that are currently in creation
*/
private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet(16);
}
3.2 三级缓存的核心流程
┌──────────────────────────────────────────────────────────────────────────────┐
│ 三级缓存工作流程 │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ 创建 Bean A │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 1. 将 A 标记为"正在创建" │ │
│ │ singletonsCurrentlyInCreation.add("a") │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 2. 实例化 A(调用构造器) │ │
│ │ 此时 A 是"半成品" │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 3. 将 A 的 ObjectFactory 放入三级缓存 │ ← 关键步骤 │
│ │ singletonFactories.put("a", () -> …) │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 4. 填充属性,发现需要 B │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 5. 去获取 B,发现 B 不存在,开始创建 B │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 6. 创建 B:实例化 → 放入三级缓存 → 填充属性 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 7. B 填充属性时发现需要 A,去获取 A │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 8. getSingleton("a"): │ ← 核心逻辑 │
│ │ 一级缓存没有 │ │
│ │ → 检查 A 正在创建中 │ │
│ │ → 二级缓存没有 │ │
│ │ → 从三级缓存获取 ObjectFactory │ │
│ │ → 调用 getObject() 获得 A 的早期引用 │ │
│ │ → 放入二级缓存,删除三级缓存 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 9. B 获得 A 的早期引用,完成属性填充 │ │
│ │ B 完成初始化,放入一级缓存 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 10. 回到 A,获得 B,完成属性填充 │ │
│ │ A 完成初始化,放入一级缓存 │ │
│ └─────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
3.3 getSingleton() 方法源码
// DefaultSingletonBeanRegistry.java
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 🔥 第一步:从一级缓存获取(完整 Bean)
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存没有,且该 Bean 正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 🔥 第二步:从二级缓存获取(早期引用)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 需要加锁保证并发安全
if (!this.singletonLock.tryLock()) {
return null;
}
try {
// Double Check
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 🔥 第三步:从三级缓存获取 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 🔥 调用 ObjectFactory.getObject() 获取早期引用
singletonObject = singletonFactory.getObject();
// 升级到二级缓存,删除三级缓存
if (this.singletonFactories.remove(beanName) != null) {
this.earlySingletonObjects.put(beanName, singletonObject);
}
else {
singletonObject = this.singletonObjects.get(beanName);
}
}
}
}
}
finally {
this.singletonLock.unlock();
}
}
}
return singletonObject;
}
3.4 三级缓存中的 ObjectFactory 是什么?
在 doCreateBean() 中放入三级缓存:
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// … 实例化 Bean …
// 判断是否需要提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 🔥 放入三级缓存的是一个 Lambda 表达式
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// … 属性填充、初始化 …
}
getEarlyBeanReference() 方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
// 🔥 这里可能返回 AOP 代理对象
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
3.5 为什么是三级缓存?两级不行吗?
假设只有两级缓存:
// 假设:实例化后直接放入二级缓存
Map<String, Object> singletonObjects; // 一级:完整 Bean
Map<String, Object> earlySingletonObjects; // 二级:早期引用
问题来了:当 A 被 AOP 增强时:
1. 创建 A,实例化后放入二级缓存(此时是原始对象)
2. A 填充属性,需要 B
3. 创建 B,B 需要 A,从二级缓存拿到 A 的原始对象
4. B 完成创建
5. A 完成初始化,在 postProcessAfterInitialization 生成代理
6. ❌ 问题:B 持有的是 A 的原始对象,不是代理对象!
三级缓存的解决方案:
1. 创建 A,实例化后放入三级缓存(存的是 ObjectFactory)
2. A 填充属性,需要 B
3. 创建 B,B 需要 A
4. 从三级缓存获取 A 的 ObjectFactory,调用 getEarlyBeanReference()
🔥 这时会提前生成 A 的代理对象
5. 将代理对象放入二级缓存,B 拿到的是代理
6. B 完成创建
7. A 完成初始化,检查二级缓存,发现已有代理,直接使用
3.6 AOP 与三级缓存的配合
AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor:
// AbstractAutoProxyCreator.java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 🔥 记录已经提前创建过代理
this.earlyProxyReferences.put(cacheKey, bean);
// 🔥 返回代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 🔥 如果已经通过 getEarlyBeanReference 创建过代理,就不再创建
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
核心设计:通过 earlyProxyReferences 这个 Map 避免重复创建代理。
四、设计动机分析(知其所以必然)
4.1 为什么不在实例化后直接创建代理?
如果直接创建代理:
// ❌ 假设这样设计
Object bean = createBeanInstance();
Object proxy = createProxy(bean); // 实例化后立即创建代理
earlySingletonObjects.put(beanName, proxy);
问题:
4.2 ObjectFactory 的妙处
ObjectFactory 是惰性求值的:
// 放入三级缓存时,只是保存了 Lambda,没有执行
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
只有当真正发生循环依赖时,才会调用 getObject() 创建早期引用。
4.3 为什么要有二级缓存?
二级缓存的作用是避免重复调用 ObjectFactory:
假设 A 被 B、C、D 三个 Bean 依赖:
没有二级缓存:
– B 获取 A → 调用 ObjectFactory → 创建代理 1
– C 获取 A → 调用 ObjectFactory → 创建代理 2
– D 获取 A → 调用 ObjectFactory → 创建代理 3
❌ 问题:创建了三个不同的代理对象!
有二级缓存:
– B 获取 A → 调用 ObjectFactory → 创建代理 → 放入二级缓存
– C 获取 A → 从二级缓存获取 → 同一个代理
– D 获取 A → 从二级缓存获取 → 同一个代理
✅ 始终是同一个对象
4.4 Spring 6.x 的变化
在 Spring 6.2.0 中,三级缓存使用了 ConcurrentHashMap:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
并增加了 singletonLock 来保护关键操作:
private final Lock singletonLock = new ReentrantLock();
这相比老版本的 synchronized 同步块,提供了更细粒度的并发控制。
五、局限性与扩展
5.1 无法解决的循环依赖
| 构造器注入 | 构造器执行时对象还未创建,无法放入缓存 |
| 原型 Bean | 原型 Bean 不使用缓存 |
| @Async Bean 的循环依赖 | @Async 代理在 postProcessAfterInitialization 创建,可能导致不一致 |
5.2 @Async 循环依赖的特殊处理
@Component
public class A {
@Autowired
private B b;
@Async
public void asyncMethod() { }
}
@Component
public class B {
@Autowired
private A a;
}
可能报错:
Bean with name 'a' has been injected into other beans [b] in its raw version
as part of a circular reference, but has eventually been wrapped.
解决方案:
// 方式一:使用 @Lazy
@Autowired
@Lazy
private A a;
// 方式二:设置 allowRawInjectionDespiteWrapping
@Bean
public static BeanFactoryPostProcessor allowRawInjection() {
return beanFactory -> {
((DefaultListableBeanFactory) beanFactory)
.setAllowRawInjectionDespiteWrapping(true);
};
}
5.3 如何关闭循环依赖支持
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setAllowCircularReferences(false); // 关闭循环依赖
app.run(args);
}
}
或在配置文件中:
spring:
main:
allow-circular-references: false
六、面试回答分层
P6 回答(功能用法层)
“Spring 通过三级缓存解决单例 Bean 的 Setter/字段注入循环依赖。一级缓存存完整 Bean,二级缓存存早期暴露的半成品 Bean,三级缓存存 ObjectFactory。当 A 依赖 B、B 依赖 A 时,A 创建过程中会先把自己放入三级缓存,创建 B 时 B 能从缓存获取 A 的早期引用,从而打破循环。构造器注入的循环依赖无法解决,可以用 @Lazy 延迟加载解决。”
P7 回答(实现原理层)
“三级缓存的核心是 DefaultSingletonBeanRegistry 中的三个 Map。关键方法是 getSingleton():先查一级缓存,没有且 Bean 正在创建中则查二级缓存,还没有就从三级缓存获取 ObjectFactory 并调用 getObject() 获取早期引用。三级缓存存的是 Lambda 表达式 () -> getEarlyBeanReference(),这个方法会调用 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference(),如果有 AOP 增强,AbstractAutoProxyCreator 会在这里提前创建代理对象。”
P8 回答(设计思想层)
“三级缓存的设计体现了惰性求值思想:正常情况下 AOP 代理在 postProcessAfterInitialization 创建,只有发生循环依赖时才通过 getEarlyBeanReference() 提前创建。ObjectFactory 的存在让框架可以延迟决策是否需要创建代理。二级缓存的意义是缓存复用,保证多个 Bean 依赖同一个正在创建的 Bean 时,拿到的是同一个早期引用。这种设计在不影响正常流程的前提下,优雅地处理了边界情况。Spring 6.x 改用 ConcurrentHashMap + ReentrantLock 替代 synchronized,体现了对并发场景的优化。”
七、面试必杀一句话
三级缓存的精髓:一级存成品、二级存半成品、三级存制造半成品的工厂。ObjectFactory 的惰性求值让 AOP 代理可以"按需提前创建",二级缓存保证多次获取返回同一对象。没有三级缓存,发生循环依赖时 B 拿到的会是 A 的原始对象而非代理,这是两级缓存无法解决的根本问题。
八、关键源码速查表
| singletonObjects | DefaultSingletonBeanRegistry | 一级缓存 |
| earlySingletonObjects | DefaultSingletonBeanRegistry | 二级缓存 |
| singletonFactories | DefaultSingletonBeanRegistry | 三级缓存 |
| getSingleton(name, allowEarlyReference) | DefaultSingletonBeanRegistry | 三级缓存查找逻辑 |
| addSingletonFactory() | DefaultSingletonBeanRegistry | 放入三级缓存 |
| getEarlyBeanReference() | AbstractAutowireCapableBeanFactory | 获取早期引用 |
| AbstractAutoProxyCreator | spring-aop | AOP 代理创建 |
| singletonsCurrentlyInCreation | DefaultSingletonBeanRegistry | 正在创建的 Bean |
下一篇预告:《BeanDefinition:Spring 的"设计图纸"是如何画出来的?》—— 深入剖析 Spring 如何将 XML/注解转化为 BeanDefinition

