Spring 三级缓存三个小问题记录
bean的流程
三级缓存存储:
存储的是工厂对象(ObjectFactory),用于生成代理对象或普通 Bean 的早期引用。
工厂的 getObject() 方法会调用后置处理器生成代理对象,或直接返回普通 Bean 的早期引用。
二级缓存存储:
存储的是早期引用,可能是未完成初始化的普通 Bean 或代理对象的早期引用。
二级缓存仅在有循环依赖时参与。
一级缓存存储:
存储的是完全初始化好的 Bean,包括代理对象和普通 Bean。
用户创建的 Bean 的路径。
bean依赖创建:
有循环依赖必定会创建代理。
普通 Bean(无循环依赖无代理):
Bean 实例化后,工厂对象放入三级缓存(但无循环依赖时不会实际使用)。
初始化完成后,直接将完全初始化的 Bean 放入一级缓存。
路径:三级缓存(工厂)→ 一级缓存(但三级缓存的工厂未实际使用)。
有代理的Bean:
路径 1(有循环依赖有代理):三级缓存工厂生成早期引用 → 二级缓存 → 一级缓存。 --这里生成的也有可能
路径 2 (无循环依赖有代理):正常初始化(无循环依赖)时,通过后置处理器生成代理对象 → 一级缓存。
问题一:三级缓存的作用与普通 Bean 为何经过三级缓存
三级缓存的核心作用:
// 三级缓存定义
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
-
延迟决策:
- 在 Bean 实例化后立即加入三级缓存,此时:
- 尚未进行属性注入
- 未执行初始化方法
- 无法确定是否需要代理(代理决策在初始化阶段)
- 示例:
@Transactional
等注解是在初始化阶段解析的
- 在 Bean 实例化后立即加入三级缓存,此时:
-
循环依赖解决方案:
// 关键工厂方法 () -> getEarlyBeanReference(beanName, mbd, bean)
- 当发生循环依赖时,通过此工厂动态创建早期引用
- 普通 Bean 直接返回原始对象
- 代理 Bean 返回代理对象
-
资源优化:
- 无循环依赖时,工厂不会被调用
- 避免无谓的代理创建(约 30% 的性能提升)
普通 Bean 为何经过三级缓存:
graph TDA[实例化] --> B[加入三级缓存]B --> C{是否有循环依赖?}C -->|无| D[继续初始化]C -->|有| E[触发工厂方法]D --> F[完成初始化]F --> G[加入一级缓存]
- 设计一致性:所有单例 Bean 统一处理流程
- 防患未然:即使当前无循环依赖,后续依赖链可能产生
- 框架简化:避免写特殊处理逻辑
问题二:三级缓存和二级缓存的内容
三级缓存存储内容:
// 存储 ObjectFactory 工厂
singletonFactories.put(beanName, () -> {return getEarlyBeanReference(beanName, mbd, rawBean);
});
- 工厂方法:能生成 Bean 的早期引用
- 非 Bean 本身:内存占用小(函数式接口)
二级缓存存储内容:
// 存储早期 Bean 对象
earlySingletonObjects.put(beanName, earlyReference);
- 半成品 Bean:
- 普通 Bean:原始对象
- 代理 Bean:代理对象
- 特征:
- 已完成实例化
- 未完成属性注入
- 未执行初始化方法
问题三:代理 Bean 的正常初始化路径
路径解析:
典型场景:
@Service
public class OrderService {@Transactional // 事务注解触发代理public void createOrder() {...}
}
缓存状态变化:
阶段 | 一级缓存 | 二级缓存 | 三级缓存 | Bean 状态 |
---|---|---|---|---|
实例化后 | ❌ | ❌ | ✅ (工厂) | 原始对象(未注入) |
属性注入后 | ❌ | ❌ | ✅ (工厂) | 原始对象(依赖注入完成) |
初始化后 | ❌ | ❌ | ✅ (工厂) | 原始对象(初始化完成) |
AOP 处理后 | ✅ (代理) | ❌ | ❌ | 代理对象 |
关键点:
-
原始对象全程存在:
- 存在于 Bean 创建流程中
- 从未加入一级缓存
- 最终被代理对象包装
-
代理创建时机:
// AbstractAutoProxyCreator public Object postProcessAfterInitialization(Object bean, String beanName) {return wrapIfNecessary(bean, beanName); }
- 在初始化完成后创建代理
- 此时原始对象已是完全体(注入+初始化完成)
-
最终缓存对象:
// 存入一级缓存的是代理对象 addSingleton(beanName, proxyObject);
三级缓存必要性验证
假设只有二级缓存:
-
问题根源:
-
三级缓存解决方案:
三级缓存工作原理对比图
关键区别说明
-
决策时机不同:
- 有三级缓存:在需要注入时才决定是否创建代理(按需)
- 无三级缓存:在实例化后立即固定对象类型(过早)
-
对象类型灵活性:
- 有三级缓存:可以返回原始对象或代理对象
- 无三级缓存:只能返回原始对象
-
代理创建位置:
- 有三级缓存:在工厂方法内动态创建
- 无三级缓存:无法在依赖注入阶段创建
这就是为什么 Spring 必须使用三级缓存而不是二级缓存的原因
设计精髓:
“不要提前承诺对象类型,在真正需要时才动态决定”
三级缓存的工厂模式实现了这一理念,完美解决了循环依赖中的代理一致性问题。
总结
三级缓存 = 注册中心(记录哪些 Bean 可提供早期引用)
二级缓存 = 急诊室(临时存放循环依赖中的半成品)
一级缓存 = VIP 室(只存放完全就绪的成品)
“二级缓存只处理循环依赖的,三级缓存的作用是提前生成 Bean 的原始引用,如果没有循环依赖,其实三级缓存就是直接放到一级缓存的”
这就是 Spring 设计的高明之处——用统一的基础设施处理两种场景:
普通 Bean:三级缓存"虚位以待",直达终点
循环依赖 Bean:三级缓存"雪中送炭",二级缓存"临时避难",最终到达终点
这种设计在保证功能的同时,最大程度减少了资源开销,体现了"按需创建"的设计哲学。