Spring 三级缓存详解

Spring 的三级缓存是解决单例Bean循环依赖的核心机制。理解三级缓存对于掌握Spring的Bean创建过程至关重要。

一、三级缓存定义与作用

三级缓存的含义

// 在 DefaultSingletonBeanRegistry 中定义 public class DefaultSingletonBeanRegistry extends ... { // 一级缓存:存放完全初始化好的Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:存放早期暴露的Bean(已实例化但未完全初始化) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:存放Bean工厂,用于创建早期引用 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); }

各级缓存的作用

缓存级别名称存储内容作用
一级缓存singletonObjects完全初始化好的单例Bean缓存最终可用的Bean
二级缓存earlySingletonObjects早期Bean(半成品)解决循环依赖,避免重复创建代理
三级缓存singletonFactoriesObjectFactory(Bean工厂)创建早期引用,支持AOP等扩展

二、循环依赖场景分析

场景一:最简单的循环依赖

@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }

场景二:自我依赖(少见但能说明问题)

@Component public class SelfRefBean { @Autowired private SelfRefBean self; }

三、三级缓存解决循环依赖的完整流程

步骤详解(以A↔B循环依赖为例)

// 模拟三级缓存解决循环依赖的过程 public class ThreeLevelCacheDemo { public static void main(String[] args) { // 创建过程模拟 createBeanA(); } static void createBeanA() { System.out.println("1. 开始创建Bean A"); // Step 1: 实例化A(调用构造函数) Object a = new A(); System.out.println("2. A实例化完成,将A的ObjectFactory放入三级缓存"); // 三级缓存:singletonFactories.put("a", () -> getEarlyBeanReference("a", a)); // Step 2: 属性填充,发现需要B System.out.println("3. 开始填充A的属性,发现需要B"); // Step 3: 创建B createBeanB(); // Step 6: 完成A的初始化 System.out.println("9. B创建完成,继续完成A的初始化"); System.out.println("10. 将A从二级缓存移除,放入一级缓存"); // 一级缓存:singletonObjects.put("a", a); // 二级缓存:earlySingletonObjects.remove("a"); } static void createBeanB() { System.out.println("4. 开始创建Bean B"); // Step 3: 实例化B Object b = new B(); System.out.println("5. B实例化完成,将B的ObjectFactory放入三级缓存"); // 三级缓存:singletonFactories.put("b", () -> getEarlyBeanReference("b", b)); // Step 4: 属性填充,发现需要A System.out.println("6. 开始填充B的属性,发现需要A"); // Step 5: 获取A(从三级缓存获取早期引用) System.out.println("7. 从三级缓存获取A的早期引用"); // Object earlyA = singletonFactories.get("a").getObject(); System.out.println("8. 将A放入二级缓存,从三级缓存移除A的工厂"); // 二级缓存:earlySingletonObjects.put("a", earlyA); // 三级缓存:singletonFactories.remove("a"); // 将earlyA注入到B System.out.println("9. 将A的早期引用注入B,完成B的初始化"); System.out.println("10. 将B从二级缓存移除,放入一级缓存"); // 一级缓存:singletonObjects.put("b", b); // 二级缓存:earlySingletonObjects.remove("b"); } }

流程图解

创建A → 实例化A → 将A工厂放入三级缓存 ↓ 填充A属性 → 需要B → 创建B ↓ 实例化B → 将B工厂放入三级缓存 ↓ 填充B属性 → 需要A → 从三级缓存获取A工厂 ↓ ↓ 创建A早期引用 ← 执行A工厂getObject() ↓ 将A早期引用放入二级缓存,移除三级缓存中的A工厂 ↓ 将A早期引用注入B → 完成B初始化 ↓ 将B放入一级缓存 → 返回B ↓ 获取到B → 注入B到A → 完成A初始化 ↓ 将A放入一级缓存,移除二级缓存中的A

四、三级缓存源码深度解析

1. 获取Bean的核心方法

protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1. 先从一级缓存获取 Object singletonObject = this.singletonObjects.get(beanName); // 如果一级缓存没有,且Bean正在创建中(解决循环依赖) if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 2. 从二级缓存获取 singletonObject = this.earlySingletonObjects.get(beanName); // 如果二级缓存没有,且允许早期引用 if (singletonObject == null && allowEarlyReference) { // 3. 从三级缓存获取Bean工厂 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 通过工厂创建早期Bean singletonObject = singletonFactory.getObject(); // 放入二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); // 移除三级缓存中的工厂 this.singletonFactories.remove(beanName); } } } } return singletonObject; }

2. Bean创建过程中的缓存操作

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { // 1. 实例化Bean BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); Object bean = instanceWrapper.getWrappedInstance(); // 2. 判断是否支持早期暴露(单例、允许循环引用) boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 3. 将Bean工厂添加到三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 4. 属性填充(可能触发循环依赖) populateBean(beanName, mbd, instanceWrapper); // 5. 初始化 Object exposedObject = initializeBean(beanName, exposedObject, mbd); // 6. 处理早期引用 if (earlySingletonExposure) { // 从一级缓存获取(检查是否已被其他Bean初始化过程修改) Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 如果exposedObject没有被增强,使用早期引用 if (exposedObject == bean) { exposedObject = earlySingletonReference; } } } return exposedObject; } // 添加工厂到三级缓存 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 放入三级缓存 this.singletonFactories.put(beanName, singletonFactory); // 清除二级缓存(确保从工厂创建) this.earlySingletonObjects.remove(beanName); // 记录已注册的单例 this.registeredSingletons.add(beanName); } } }

3. 获取早期Bean引用(支持AOP)

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; // 1. 如果Bean需要被后处理器增强(如AOP) if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // 2. 获取早期引用(可能是代理对象) exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }

五、为什么需要三级缓存?二级不够吗?

场景分析:AOP代理的循环依赖

@Component public class A { @Autowired private B b; public void doSomething() { System.out.println("A do something"); } } @Component public class B { @Autowired private A a; // 这里期望注入的是A的代理对象,而不是原始对象 public void test() { // 如果注入的是原始对象,AOP增强会失效 a.doSomething(); } } @Aspect @Component public class LogAspect { @Before("execution(* com.example.A.doSomething(..))") public void logBefore() { System.out.println("Log before method execution"); } }

三级缓存的必要性

  1. 延迟代理创建:三级缓存存储的是ObjectFactory,可以延迟决定何时以及如何创建代理

  2. 保证代理一致性:确保所有Bean注入的是同一个代理实例

  3. 避免重复创建代理:如果没有三级缓存,每次获取早期引用都可能创建新的代理

如果只有二级缓存的问题

// 假设只有二级缓存 public Object getSingleton(String beanName) { Object bean = singletonObjects.get(beanName); if (bean == null && isSingletonCurrentlyInCreation(beanName)) { // 只有二级缓存:直接创建早期引用 bean = createEarlyBeanReference(beanName); earlySingletonObjects.put(beanName, bean); } return bean; } // 问题:如果多个地方同时获取早期引用,可能创建多个不同的代理实例 // 特别是当A需要被AOP增强时

六、特殊场景处理

1. 构造器循环依赖(无法解决)

@Component public class ConstructorA { private ConstructorB b; @Autowired public ConstructorA(ConstructorB b) { // 构造器注入 this.b = b; } } @Component public class ConstructorB { private ConstructorA a; @Autowired public ConstructorB(ConstructorA a) { // 构造器注入 this.a = a; } } // 抛出 BeanCurrentlyInCreationException

原因:构造器注入时,Bean还未实例化,无法放入三级缓存。

2. 原型Bean的循环依赖(无法解决)

@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeA { @Autowired private PrototypeB b; } @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTotype) public class PrototypeB { @Autowired private PrototypeA a; } // 抛出 BeanCurrentlyInCreationException

原因:原型Bean不缓存,每次都是新创建,Spring不支持原型Bean的循环依赖。

3. @Async注解的循环依赖

@Component public class AsyncA { @Autowired private AsyncB b; @Async public void asyncMethod() { // 异步方法 } } @Component public class AsyncB { @Autowired private AsyncA a; // 这里注入的可能是原始对象,而不是代理 }

解决方法:使用@Lazy注解

@Component public class AsyncB { @Lazy @Autowired private AsyncA a; }

七、三级缓存与AOP的协作

代理创建时机

// AbstractAutoProxyCreator(AOP核心类)中的处理 public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 将原始Bean和代理关系缓存起来 this.earlyProxyReferences.put(cacheKey, bean); // 创建代理 return wrapIfNecessary(bean, beanName, cacheKey); } // 后续初始化完成后检查 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; }

八、性能与线程安全考虑

1. 缓存访问的同步

// DefaultSingletonBeanRegistry 中的同步控制 protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 以一级缓存为锁 this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }

2. 性能优化

  • 一级缓存使用ConcurrentHashMap,支持高并发读

  • 二级缓存使用ConcurrentHashMap,但实际访问需要加锁

  • 三级缓存使用HashMap,因为只在创建Bean时访问,且需要同步

九、实际调试技巧

1. 查看三级缓存状态

@SpringBootTest public class CacheDebugTest { @Autowired private ApplicationContext applicationContext; @Test public void debugThreeLevelCache() throws Exception { DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) ((AbstractApplicationContext) applicationContext).getBeanFactory(); // 通过反射查看缓存内容 Field singletonObjectsField = DefaultSingletonBeanRegistry.class .getDeclaredField("singletonObjects"); singletonObjectsField.setAccessible(true); Map<String, Object> singletonObjects = (Map<String, Object>) singletonObjectsField.get(registry); System.out.println("一级缓存大小: " + singletonObjects.size()); // 类似方式可以查看二级和三级缓存 } }

2. 循环依赖调试配置

# application.properties # 开启循环依赖调试日志 logging.level.org.springframework.beans.factory.support=DEBUG # 关闭Spring的循环依赖快速失败(仅用于调试) spring.main.allow-circular-references=true

十、最佳实践与注意事项

1. 避免循环依赖

  • 优先使用构造器注入:强制在编译期发现循环依赖

  • 代码重构:提取公共逻辑到第三个类中

  • 使用@Lazy注解:延迟加载打破循环

2. 设计建议

// 不好的设计:双向紧密耦合 @Service public class OrderService { @Autowired private UserService userService; } @Service public class UserService { @Autowired private OrderService orderService; } // 好的设计:提取公共逻辑 @Service public class OrderService { @Autowired private CommonService commonService; } @Service public class UserService { @Autowired private CommonService commonService; } @Service public class CommonService { // 公共业务逻辑 }

3. 配置建议

@Configuration public class AppConfig { // 如果不使用AOP,可以关闭循环引用支持提升性能 @Bean public static BeanFactoryPostProcessor disableCircularReferences() { return beanFactory -> { if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowCircularReferences(false); } }; } }

总结

Spring三级缓存的核心价值:

  1. 解决循环依赖:通过提前暴露Bean引用

  2. 支持AOP:确保注入的是正确的代理对象

  3. 保证单例唯一性:避免重复创建Bean实例

  4. 性能优化:减少不必要的Bean创建

理解三级缓存不仅有助于解决循环依赖问题,更能深入理解Spring容器的设计哲学。在实际开发中,虽然三级缓存解决了技术问题,但良好的设计应该尽量避免循环依赖的出现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1119988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025金三银四:大模型训练岗年薪百万技术栈揭秘,非常详细收藏我这一篇就够了!

​​摘要​​ 2025年大模型训练岗位校招薪资峰值突破​​180万​​&#xff08;OpenAI中国研究院Offer&#xff09;&#xff0c;较算法工程师均值高出​​300%​​。本文基于猎聘/脉脉35家头部企业招聘数据&#xff0c;深度解析百万年薪背后的​​技术能力图谱​​&#xff1a;…

前端Vue开发环境搭建(安装Node.js)

一、官网下载Node.js下载地址https://nodejs.org/zh-cn​​​​二、测试安装是否成功在键盘按下【winR】键&#xff0c;输入cmd&#xff0c;然后回车&#xff0c;打开cmd窗口分别输入node -v和npm -v&#xff0c;测试安装是否成功&#xff08;Node.js已经整合了npm&#xff09;…

Azure Dataverse 权限设计学习

1. 创建表 https://make.powerapps.com/ 2. 管理员配置权限端 https://admin.powerplatform.microsoft.com/ 3 Dataverse 权限设计 业务部门 -> 相当于组织架构 团队 -> 组织架构的分组权限&#xff0c;可以把业务部门下的人员放在多个团队 用户 -> 职员 角色 -…

分布式事务原理及实际业务场景详解

一、分布式事务基础概念1.1 什么是分布式事务&#xff1f;分布式事务是指跨多个数据库、服务或系统的操作序列&#xff0c;这些操作作为一个整体&#xff0c;要么全部成功&#xff0c;要么全部失败&#xff0c;保证数据的一致性。1.2 本地事务 vs 分布式事务维度本地事务分布式…

基于JAVA框架的学生宿舍寝室报修管理系统的设计与实现应用和研究

文章目录摘要项目简介大数据系统开发流程主要运用技术介绍爬虫核心代码展示结论源码文档获取定制开发/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 随着高校规模的扩大和学生人数的增加&#xff0c;宿舍设施的维护与管理成为一项重要任务。传…

【SCI复现】基于RSSA算法的冷热电联供型微网优化调度附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1…

车辆GPS数据实战方案:SpringBoot + Kafka + Redis + MongoDB 全栈实现

1. 系统架构设计 1.1 整体架构 车辆终端 → SpringBoot接入层 → Kafka消息队列 → 数据处理层 → MongoDB存储 + Redis缓存 1.2 技术栈选择理由 技术 作用 选择理由 SpringBoot 接入层、快速开发 生态丰富、快速开发 Kafka 消息队列,高吞吐 解耦系统组件、支持顺序消息 Re…

力扣数据库——员工奖金

员工奖金https://leetcode.cn/problems/employee-bonus/ 一 题目 表&#xff1a;Employee Column NameTypeempIdintnamevarcharsupervisorintsalaryint empId 是该表中具有唯一值的列。该表的每一行都表示员工的 id 和姓名&#xff0c;以及他们经理的 id 和他们的工资。表…

基于Java的心理咨询在线评测系统设计与开发应用和研究

文章目录心理咨询在线评测系统设计与开发摘要项目简介大数据系统开发流程主要运用技术介绍爬虫核心代码展示结论源码文档获取定制开发/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;心理咨询在线评测系统设计与开发摘要 该系统基于Java技术栈开发&…

毫米波V2I网络的链路层仿真研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f34a;个人信条&#xff1a;格物致知,完整Matlab代码及仿真咨询…

把智能体当“新员工“带:一文搞懂大模型智能体运作流程

文章通过将智能体比作新员工的形象比喻&#xff0c;揭示了智能体的运作流程&#xff1a;首先通过系统提示词设定角色和基本信息&#xff0c;然后配置工具让智能体使用。智能体会尝试解决问题&#xff0c;但结果可能不完善&#xff0c;需要根据反馈优化。ReAct Agent模型通过规划…

高并发订单系统架构设计:Redis + MySQL + Elasticsearch 实践

1000万日订单查询优化:冷热分离与分层缓存全攻略 面对每日 10,000,000 条订单查询,本文提供系统化的优化方案,涵盖缓存策略、数据库分库分表、历史归档、查询路由与降级策略、容量规划及生产级运维实践,同时提供 Java 完整示例代码,便于落地实现。 架构总览 一、应用层缓存…

基于Java的银行储蓄存业务系统的设计与实现应用和研究

文章目录摘要项目简介大数据系统开发流程主要运用技术介绍爬虫核心代码展示结论源码文档获取定制开发/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 银行储蓄业务系统是金融信息化建设的重要组成部分&#xff0c;基于Java技术开发的系统能够有…

力扣数据库——第二高的薪水

第二高的薪水https://leetcode.cn/problems/second-highest-salary/ 一 题目 Employee 表&#xff1a; Column NameTypeidintsalaryint id 是这个表的主键。表的每一行包含员工的工资信息。查询并返回 Employee 表中第二高的 不同 薪水 。如果不存在第二高的薪水&#xff0c…

Python 之 fuzzywuzzy 进行字符串模糊匹配

fuzzywuzzy 是 Python中 基于 Levenshtein 距离算法的字符串模糊匹配库&#xff0c;提供 fuzz.ratio 、 partial_ratio 、 token_sort_ratio 等核心函数&#xff0c;用于高效计算字符串相似度。该库广泛应用于数据清洗、拼写纠错、文本挖掘和用户输入处理等场景。比如识别相同新…

基于SpringBoot+Vue的厨艺美食菜品分享交流系统的设计与实现应用和研究

文章目录摘要项目简介大数据系统开发流程主要运用技术介绍爬虫核心代码展示结论源码文档获取定制开发/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 随着互联网技术的快速发展&#xff0c;线上美食分享与交流平台逐渐成为用户展示厨艺、学习烹…

深度学习毕设选题推荐:基于深度学习python的鞋类分类

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

华为OD机考双机位C卷 - 任务编排系统 (Java Python JS C/C++ GO )

最新华为上机考试 真题目录&#xff1a;点击查看目录 华为OD面试真题精选&#xff1a;点击立即查看 华为OD机考双机位C卷 - 任务编排系统 题目描述 任务编排服务负责对任务进行组合调度。参与编排的任务有两种类型&#xff0c;其中一种执行时长为taskA&#xff0c;另一种执…

力扣数据库——组合两个表

175. 组合两个表https://leetcode.cn/problems/combine-two-tables/ 一 题目 表: Person 列名类型PersonIdintFirstNamevarcharLastNamevarchar personId 是该表的主键&#xff08;具有唯一值的列&#xff09;。该表包含一些人的 ID 和他们的姓和名的信息。表: Address 列…

基于人脸识别的企业员工考勤管理系统没视频应用和研究

文章目录人脸识别考勤系统的研究背景系统核心技术非视频应用场景研究进展与挑战实际应用价值项目简介大数据系统开发流程主要运用技术介绍爬虫核心代码展示结论源码文档获取定制开发/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;人脸识别考勤系统的…