导语: 上一篇我们深入探讨了Spring三级缓存如何解决循环依赖(点击回顾)。但灵魂拷问来了:为什么非得三级缓存?二级甚至一级缓存不行吗? 今天我们就撕开设计本质,结合Spring源码,彻底讲透三级缓存不可替代的深层原因!文末附高频面试题解析,助力斩获Offer!
一、再探三级缓存:设计目标与核心矛盾
Spring循环依赖解决方案的本质矛盾:
如何在Bean未完成初始化前暴露引用,同时确保该引用与最终Bean保持一致(尤其当存在AOP代理时)?
三级缓存的核心使命正是解决两大难题:
- 提前暴露引用:打破循环死锁
- 代理一致性 :确保依赖方注入的对象与最终Bean是同一个代理对象
二、简化方案对比:一级/二级缓存为何失败?
⚠️ 方案1:仅用一级缓存(singletonObjects
)
java
// DefaultSingletonBeanRegistry.java
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
致命缺陷:无法隔离"半成品"Bean
- 若将刚实例化(未初始化)的Bean直接放入一级缓存
- 其他Bean可能获取到残缺对象(如:未注入依赖、未执行
@PostConstruct
) - 结果:NPE或业务逻辑错误
java
// 伪代码:错误的一级缓存方案
protected Object doCreateBean(...) {
// 1. 实例化BeanA(半成品)
Object bean = createBeanInstance(beanName, mbd, args);
// ❌ 错误:将半成品直接放入一级缓存
singletonObjects.put(beanName, bean);
// 2. 填充属性(此时可能触发循环依赖)
populateBean(beanName, mbd, instanceWrapper);
// ... 初始化操作
}
结论 :一级缓存方案破产!必须区分成品Bean 和半成品Bean。
⚠️ 方案2:仅用二级缓存(earlySingletonObjects
+ singletonObjects
)
java
// DefaultSingletonBeanRegistry.java
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
无AOP时代:完美运行
以A→B→A循环为例:
- 实例化A → 原始对象存入
earlySingletonObjects
- 填充A属性发现需要B → 实例化B
- 填充B属性需要A → 从
earlySingletonObjects
取原始A注入 - B初始化完成 → 移入
singletonObjects
- A继续初始化 → 完成移入
singletonObjects
引入AOP:灾难降临
java
@Service
public class ServiceA {
@Autowired private ServiceB b;
@Async // 需要代理
public void method() {...}
}
@Service
public class ServiceB {
@Autowired private ServiceA a; // 注入的是原始对象!
}
致命问题:
- B持有的A是原始对象,而非代理对象
- 最终容器中的A是代理对象
- 结果 :B调用的A.method()异步失效!且内存中存在两个A对象(原始对象+代理对象)
三、三级缓存的救赎:ObjectFactory动态决策
🔥 核心机制:延迟生成代理对象
三级缓存的关键:
java
// DefaultSingletonBeanRegistry.java
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 暴露工厂方法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
singletonFactories.put(beanName, singletonFactory);
}
ObjectFactory的魔力:
- 不直接存储Bean对象
- 存储一个能动态生成Bean早期引用的工厂
- 工厂执行时机:首次发生循环依赖时
源码级解析:代理对象生成流程
java
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(...) {
// 1. 实例化原始对象
Object beanInstance = instantiateBean(beanName, mbd);
// 2. 关键!提前暴露ObjectFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
// 3. 属性填充(可能触发循环依赖)
populateBean(beanName, mbd, instanceWrapper);
}
// 生成早期引用(含代理逻辑)
protected Object getEarlyBeanReference(String beanName, ...) {
Object exposedObject = bean;
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
// 🔥 触发AOP代理创建(如AbstractAutoProxyCreator)
exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp)
.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
工作流程(A→B→A + AOP代理场景):
-
创建A
- 实例化原始A
- 向三级缓存注册工厂:
singletonFactories.put("A", ()->getEarlyBeanReference(A))
-
填充A属性时发现依赖B
- 触发创建B
-
填充B属性时发现依赖A
-
调用
getSingleton("A")
:java// DefaultSingletonBeanRegistry.java protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 查一级缓存 -> 无 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 查二级缓存 -> 无(首次) singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // 查三级缓存 -> 找到工厂! ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 🔥 触发工厂方法:创建代理对象! singletonObject = singletonFactory.getObject(); // 移入二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
-
此时生成A的代理对象,注入B中
-
-
B初始化完成
- 移入一级缓存
singletonObjects
- 移入一级缓存
-
A继续初始化
-
完成属性填充(注入B)
-
执行
initializeBean()
时尝试生成代理:java// AbstractAutoProxyCreator.java public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 🔥 关键判断:若已提前代理,则跳过 if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
-
此时发现A已在循环依赖时被代理过,不再重复创建
-
四、为什么二级缓存无法解决AOP代理问题?
矛盾根源:代理创建的时序冲突
阶段 | 二级缓存方案 | 三级缓存方案 |
---|---|---|
暴露时机 | 实例化后直接暴露原始对象 | 暴露生成代理的工厂方法 |
代理创建 | 初始化后创建代理 | 循环依赖发生时动态创建代理 |
一致性 | 依赖方拿到原始对象 | 依赖方直接拿到代理对象 |
二级缓存的致命缺陷:
- 提前暴露的是原始对象
- 无法在暴露时确定是否需要代理(依赖后置处理器)
- 代理对象创建滞后
- 导致循环依赖中注入对象与最终对象不一致
- 重复代理风险
- 若在初始化阶段再次创建代理,内存中存在两个对象
五、高频面试题深度解析
Q1:无AOP场景下能否用二级缓存替代三级缓存?
可以,但Spring为统一设计保留三级结构
-
三级缓存中
ObjectFactory
直接返回原始对象:javaaddSingletonFactory(beanName, () -> bean); // 等同于二级缓存
-
但Spring选择保持架构一致性,避免条件分支
Q2:为什么构造器注入无法用三级缓存解决?
根本原因:暴露时机太晚
java
// 构造器注入流程
protected BeanWrapper createBeanInstance(...) {
// 调用构造器时需要立即获得依赖Bean
Constructor<?> constructorToUse = ...;
// ❌ 此时尚未执行addSingletonFactory()!
return BeanUtils.instantiateClass(constructorToUse, args);
}
- 构造器注入必须在实例化阶段获得依赖
- 而三级缓存在实例化之后才注册
Q3:Spring如何避免重复创建代理?
双重校验机制:
-
提前代理标记
java// AbstractAutoProxyCreator.java public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 标记该Bean已提前代理 this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }
-
初始化阶段跳过已代理Bean
javapublic Object postProcessAfterInitialization(Object bean, String beanName) { if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 仅对未提前代理的Bean创建代理 return wrapIfNecessary(...); } return bean; }
六、设计启示录:三级缓存的本质
-
空间换时间
- 通过三级结构换取循环依赖解决能力
-
关注点分离
缓存层级 职责 一级缓存 存储完全可用Bean 二级缓存 避免重复创建早期引用 三级缓存 动态解决代理一致性问题 -
开闭原则典范
ObjectFactory
抽象允许扩展代理逻辑- 新增
BeanPostProcessor
不影响主流程
总结:
Spring的三级缓存是代理场景下解决循环依赖的唯一方案 。
二级缓存因无法解决代理对象一致性 问题被淘汰,
一级缓存因无法隔离半成品Bean 被弃用。
该设计展现了Spring在架构妥协与严谨性间的完美平衡!