今天来学习下Spring的三级缓存机制,主要涉及到以下问题
- 三级缓存是为了解决什么问题?
- 二级缓存为什么不行?
- 三级缓存的实现机制
设计动机
Spring的三级缓存机制,主要是为了解决循环依赖问题,蕴含着对对象创建、依赖注入、代理生成等复杂场景的深度权衡。
- 当对象A依赖对象B,对象B反向依赖对象A,传统单线程造成死锁困境
- AOP代理对象在对象初始化完成后,但依赖循环需要提前注入
核心矛盾
- 必须在对象初始化后才能确定是否要生成代理对象
- 循环依赖需要在初始化前完成暴露对象引用
矛盾推演:
- 如果提前生成代理:可能错误生成不需要代理的对象代理逻辑依赖初始化后的状态
- 如果延后生成代理:早期注入的对象与最终对象不一致破坏单例原则
解法演进
三级缓存源码结构
// 核心缓存定义(DefaultSingletonBeanRegistry)
public class DefaultSingletonBeanRegistry ... {
// 一级缓存:完整 Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露对象(半成品)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:对象工厂(生成半成品的 lambda)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
Bean创建流程与缓存交互
要透彻理解 Spring 的三级缓存机制,我们需要深入源码层面分析其设计哲学。这里以 Spring Framework 5.3.x 的 DefaultSingletonBeanRegistry 源码为核心进行解析。
一、三级缓存源码架构
// 核心缓存定义(DefaultSingletonBeanRegistry)
public class DefaultSingletonBeanRegistry ... {
// 一级缓存:完整 Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露对象(半成品)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:对象工厂(生成半成品的 lambda)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
缓存访问核心方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 第一级缓存查询(已完全初始化的 Bean)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 第二级缓存查询(已暴露但未初始化的 Bean)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 第三级缓存查询(对象工厂)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 关键操作:通过工厂获取早期引用
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
二、Bean 创建流程与缓存交互 典型场景:Bean A → 依赖 → Bean B → 依赖 → Bean A 创建 Bean A
protected Object doCreateBean(...) {
// 1. 实例化(此时对象是原始状态)
instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. 将对象工厂放入三级缓存(关键!)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 3. 属性填充(触发依赖解析)
populateBean(beanName, mbd, instanceWrapper);
}
解析依赖 Bean B触发 getBean(B) → 重复上述流程创建 Bean B 当 Bean B 需要注入 Bean A 时:
// 在 getSingleton(A) 时:
ObjectFactory<?> factory = singletonFactories.get("A");
Object earlyA = factory.getObject(); // 触发 SmartInstantiationAwareBeanPostProcessor
代理对象生成时机
protected Object getEarlyBeanReference(...) {
Object exposedObject = bean;
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
// 这里会生成 AOP 代理(如有必要)
exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp)
.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
三、为什么二级缓存不够? 假设只有二级缓存(删除三级缓存): // 伪代码示意:
public Object getSingleton(String beanName) {
Object bean = singletonObjects.get(beanName);
if (bean == null) {
bean = earlySingletonObjects.get(beanName); // 直接取二级缓存
if (bean == null) {
bean = createBean(...); // 直接创建新实例
earlySingletonObjects.put(beanName, bean);
}
}
return bean;
}
会导致两个致命问题:
- 代理对象重复创建 2. 当Bean需要AOP代理时,第一次是从二级缓存中获取原始对象,后续初始化时生成代理对象,导致内存中存在两个对象
2.并发场景下的状态不一致
// 线程1:正在创建 Bean A
// 线程2:同时请求 Bean A
if (bean == null) {
// 两个线程都可能进入创建流程
bean = createBean(...);
}
四、三级缓存如何解决这些问题?
- 代理对象单例保证 通过 ObjectFactory 延迟代理生成:
singletonFactories.put(beanName, () -> {
// 确保在此刻统一处理代理逻辑
return getEarlyBeanReference(beanName, mbd, bean);
});
无论有多少个依赖方,只通过工厂生成一次代理 2. 创建过程原子性 三级缓存到二级缓存的转移是同步操作:
synchronized (this.singletonObjects) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
- 对象状态追踪 三个缓存明确区分生命周期阶段:
三级缓存:已实例化,未初始化,可生成早期引用 二级缓存:已生成早期引用,正在初始化 一级缓存:完全初始化完成
代价权衡
- 内存开销:每个 Bean 在创建过程中需要额外存储 ObjectFactory(约 40-60 bytes)
- 并发性能:全局 synchronized 块导致创建吞吐量下降
- 复杂度提升:缓存状态转移逻辑增加了调试难度
总结
三级缓存主要是为了解决循环依赖与AOP代理对象之间的矛盾点,之所以二级缓存不够是因为无法保证对象单例性的同时,处理对象生成时序的问题