Spring 中的三级缓存机制详解
🤟致敬读者
- 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉
📘博主相关
- 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息
文章目录
- Spring 中的三级缓存机制详解
- 一、三级缓存定义(在 `DefaultSingletonBeanRegistry` 中)
- 二、解决循环依赖的核心流程(以 A→B→A 为例)
- 1. 创建 Bean A
- 2. 创建 Bean B
- 3. 完成 Bean A
- 三、关键源码解析
- 1. 从缓存获取 Bean 的优先级 (`getSingleton()`)
- 2. 曝光早期引用 (`addSingletonFactory()`)
- 四、三级缓存的本质作用
- 五、高频面试问题详解
- 1. 为什么需要三级缓存?二级不行吗?
- 2. 哪些场景无法解决循环依赖?
- 3. 如何证明三级缓存的存在?
- 六、典型循环依赖解决方案对比
- 七、面试实战回答模板
📃文章前言
- 🔷文章均为学习工作中整理的笔记。
- 🔶如有错误请指正,共同学习进步。
Spring 中的三级缓存机制详解
Spring 的三级缓存机制是解决循环依赖问题的核心设计,也是面试中高频考点。下面从原理、流程、源码三个维度深入解析:
一、三级缓存定义(在 DefaultSingletonBeanRegistry
中)
// 1. 一级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 2. 二级缓存:存放早期曝光对象(已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);// 3. 三级缓存:存放单例工厂(用于生成代理对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
二、解决循环依赖的核心流程(以 A→B→A 为例)
1. 创建 Bean A
2. 创建 Bean B
graph TDB1[实例化B] --> B2[将B的ObjectFactory放入三级缓存]B2 --> B3[填充B的属性 - 发现依赖A]B3 --> B4[从三级缓存获取A的ObjectFactory]B4 --> B5[调用getObject()获取A的早期引用]B5 --> B6[将A从三级缓存升级到二级缓存]
3. 完成 Bean A
三、关键源码解析
1. 从缓存获取 Bean 的优先级 (getSingleton()
)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 从一级缓存查询Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 2. 从二级缓存查询singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 3. 从三级缓存获取ObjectFactoryObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 创建早期引用(可能生成代理对象)singletonObject = singletonFactory.getObject();// 升级到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
2. 曝光早期引用 (addSingletonFactory()
)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 将ObjectFactory放入三级缓存this.singletonFactories.put(beanName, singletonFactory);// 清除二级缓存中的旧数据this.earlySingletonObjects.remove(beanName);}}
}
四、三级缓存的本质作用
缓存级别 | 存储内容 | 核心作用 |
---|---|---|
一级缓存 | 完全初始化好的 Bean | 提供最终可用对象 |
二级缓存 | 早期曝光对象(未初始化) | 避免重复创建代理对象 |
三级缓存 | ObjectFactory | 分离实例化与初始化,支持AOP |
五、高频面试问题详解
1. 为什么需要三级缓存?二级不行吗?
- 关键原因:处理 AOP 代理对象的循环依赖
- 二级缓存缺陷:
// 若只有二级缓存: A 实例化 → 放入二级缓存 → 填充属性依赖 B B 实例化 → 从二级缓存拿到原始对象 A(非代理)→ 完成初始化 A 继续初始化 → 生成代理对象 → 导致 B 持有的是原始对象(非代理)
- 三级缓存解决方案:
通过ObjectFactory.getObject()
动态决定返回原始对象还是代理对象
2. 哪些场景无法解决循环依赖?
场景 | 原因 |
---|---|
构造器注入 | 实例化前就需要完整依赖,无法提前曝光对象 |
原型(prototype) | Spring 不缓存原型 Bean |
@Async 注解 | 代理对象在初始化后生成,无法通过 ObjectFactory 提前创建 |
自定义初始化 | 某些 InitializingBean 实现可能依赖完整状态 |
3. 如何证明三级缓存的存在?
通过调试查看容器状态:
// 查看缓存内容
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Field singletonObjects = beanFactory.getClass().getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
Map<String, Object> cache = (Map) singletonObjects.get(beanFactory);
System.out.println("一级缓存: " + cache.keySet());
六、典型循环依赖解决方案对比
方案 | 优点 | 缺点 |
---|---|---|
三级缓存 | Spring原生支持,自动处理 | 无法解决构造器注入 |
Setter注入 | 兼容三级缓存机制 | 代码侵入性强 |
@Lazy注解 | 延迟加载打破循环 | 可能引发NPE问题 |
重构代码 | 彻底解决设计问题 | 业务改造成本高 |
七、面试实战回答模板
面试官:请解释Spring如何解决循环依赖?
回答:
Spring 通过三级缓存机制解决单例Bean的循环依赖问题:
- 当Bean实例化后,会将其ObjectFactory存入三级缓存
- 依赖注入时若发现循环依赖,通过ObjectFactory.getObject()获取早期引用
- 获取到的对象会升级到二级缓存
- Bean完全初始化后放入一级缓存
关键点在于:
- 三级缓存的ObjectFactory支持AOP代理的动态生成
- 避免了构造器注入和原型Bean的循环依赖
- 通过缓存分离实例化和初始化过程
源码体现在DefaultSingletonBeanRegistry的getSingleton()方法中…
掌握三级缓存机制需要深入理解Spring生命周期,建议结合源码调试加深理解(关键类:AbstractAutowireCapableBeanFactory
和 DefaultSingletonBeanRegistry
)。
📜文末寄语
- 🟠关注我,获取更多内容。
- 🟡技术动态、实战教程、问题解决方案等内容持续更新中。
- 🟢《全栈知识库》技术交流和分享社区,集结全栈各领域开发者,期待你的加入。
- 🔵加入开发者的《专属社群》,分享交流,技术之路不再孤独,一起变强。
- 🟣点击下方名片获取更多内容🍭🍭🍭👇