当前位置: 首页 > java >正文

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
实例化A
将A的ObjectFactory放入三级缓存
填充A的属性 - 发现依赖B
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
B创建完成
将B注入A
执行A的初始化方法
将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的循环依赖问题:

  1. 当Bean实例化后,会将其ObjectFactory存入三级缓存
  2. 依赖注入时若发现循环依赖,通过ObjectFactory.getObject()获取早期引用
  3. 获取到的对象会升级到二级缓存
  4. Bean完全初始化后放入一级缓存

关键点在于:

  • 三级缓存的ObjectFactory支持AOP代理的动态生成
  • 避免了构造器注入和原型Bean的循环依赖
  • 通过缓存分离实例化和初始化过程

源码体现在DefaultSingletonBeanRegistry的getSingleton()方法中…

掌握三级缓存机制需要深入理解Spring生命周期,建议结合源码调试加深理解(关键类:AbstractAutowireCapableBeanFactoryDefaultSingletonBeanRegistry)。


📜文末寄语

  • 🟠关注我,获取更多内容。
  • 🟡技术动态、实战教程、问题解决方案等内容持续更新中。
  • 🟢《全栈知识库》技术交流和分享社区,集结全栈各领域开发者,期待你的加入。
  • 🔵​加入开发者的《专属社群》,分享交流,技术之路不再孤独,一起变强。
  • 🟣点击下方名片获取更多内容🍭🍭🍭👇

http://www.xdnf.cn/news/12733.html

相关文章:

  • MySQL索引:7大类型+4维分类
  • 《Windows 10下QT+OpenCV+Yolo11:AI视觉开发实战指南》
  • GNSS高精度定位之-----星基差分
  • 数据网格的革命:从集中式到分布式的数据管理新范式
  • C++中的数组
  • Linux Docker的简介
  • uni-app学习笔记三十三--触底加载更多和下拉刷新的实现
  • 重新定义 AI 协同:三款开源 MCP 工具开启智能体从“聊天”到“操控”
  • [论文阅读] 人工智能+软件工程(软件测试) | 当大语言模型遇上APP测试:SCENGEN如何让手机应用更靠谱
  • 【论文阅读29】区间预测CIPM(2025)
  • RabbitMQ fanout交换机
  • 国防科技大学计算机基础慕课课堂学习笔记
  • Unity中的Mathf.Clamp01
  • 6.5 自学测试 数据库基础 Day5
  • 利用frp和腾讯云服务器将内网暴露至外网(内网穿透)
  • 【MATLAB代码】基于MCC(最大相关熵)的EKF,一维滤波,用于解决观测噪声的异常|附完整代码,订阅专栏后可直接查看
  • 模拟法解题的思路与算法分享
  • [GitHub] 优秀开源项目
  • python训练营打卡第47天
  • 27、基于map实现的简易kv数据库
  • AIGC的产品设计演进:从工具到协作者
  • 黑马Sting四道练习题
  • 《Progressive Transformers for End-to-End Sign Language Production》复现报告
  • windows使用脚本杀死python进程
  • STM32学习之I2C(理论篇)
  • Addressable-配置相关
  • 操作系统:分页存储管理方式(精简版、含例题)
  • 源码级拆解:如何搭建高并发「数字药店+医保购药」一体化平台?
  • 6.7 打卡
  • AtCoder Beginner Contest 408 D-F 题解