[设计模式与源码]1_Spring三级缓存中的单例模式

欢迎来到啾啾的博客🐱,一个致力于构建完善的Java程序员知识体系的博客📚,记录学习的点滴,分享工作的思考、实用的技巧,偶尔分享一些杂谈💬。
欢迎评论交流,感谢您的阅读😄。

本篇总结了Spring单例Bean循环依赖的解决机制——“三级缓存”,其本质是创建过程依赖的解耦,提前暴露对象引用,利用多级缓存机制存储引用与实例对象。

需要先理解SpringBean的生命周期,不理解也没关系,看一下doCreateBean方法也就记住了。

⬅️前文

设计模式概览

目录

单例模式-Spring DefaultSingletonBeanRegistry

三级缓存解决单例Bean循环依赖

三级缓存机制

循环依赖具体解决步骤

循环依赖限制

单例Bean限制

构造器注入限制

单例模式-Spring DefaultSingletonBeanRegistry

单例模式定义为“确保一个类只有一个实例,并提供一个全局访问点来访问这个实例”。

Spring默认的Bean作用域就是单例的,每个Bean在Spring容器中只有一个实例。

Spring框架DefaultSingletonBeanRegistry类,使用CouncurrentHashMap存储单例对象,并提供getSingleton方法获取对象。

DefaultSingletonBeanRegistry中的单例模式代码提取如下:

public class DefaultSingletonBeanRegistry {private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);public Object getSingleton(String beanName) {return singletonObjects.get(beanName);}
}

值得注意的是,一级缓存初始化时指定了256的初始容量,在Java基础-集合篇中有提到这是一种常见的提升性能的写法。

同时,这段代码也可以用于解释Spring是如何通过三级缓存机制来解决单例Bean的依赖循环问题。

三级缓存解决单例Bean循环依赖

以 Bean A → 依赖 Bean B → 依赖 Bean A 的循环依赖场景为例。

总所周知,Bean的生命周期为实例化、属性注入、初始化、使用、销毁。

在BeanA实例化后进行属性注入操作创建属性BeanB时,发现BeanB属性注入操作需要创建BeanA,此时便形成了循环依赖。

Spring通过三级缓存机制来解决循环依赖。

三级缓存机制

  • 一级缓存(singletonObjects):存储完全初始化的单例Bean。

  • 二级缓存(earlySingletonObjects):存储提前暴露的Bean(未完成属性注入)。

  • 三级缓存(singletonFactories):存储Bean的工厂对象,用于生成提前暴露的Bean。

核心机制是二级缓存的对象早期引用Early Bean Reference,相当于在对象完成初始化前体现暴露对象引用。

辅助理解:类似面向接口编程思想,体现暴露对象引用相当于提供接口,创建Bean不管具体实现,实现创建过程的解耦,进而解决循环依赖问题。

循环依赖具体解决步骤

1.Bean A实例化后
        将ObjectFactory存入三级缓存(此时A尚未完成属性注入)
        触发populateBean()时发现需要注入Bean B
2.创建Bean B
        实例化Bean B后同样存入三级缓存
        触发populateBean()时发现需要注入Bean A
3.获取Bean A的早期引用
        通过getSingleton("A")从三级缓存获取ObjectFactory
        通过工程对象调用getEarlyBeanReference()获取未完成初始化的BeanA对象,即BeanA的早期引用(Early Bean Reference)
4.完成Bean B的初始化
        Bean B获得A的代理对象后完成初始化
        Bean B被存入一级缓存
5.继续完成Bean A的初始化
        将Bean B注入到Bean A
        Bean A完成初始化后存入一级缓存
        清除二级缓存中的A对象

结合AbstractAutowireCapableBeanFactory源码分析,创建Bean时,实例化后将工程对象存入三级缓存,用于后续创建对象早期引用。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {// 1. 实例化Bean A(此时对象未初始化)Object bean = instanceWrapper.getWrappedInstance();// 2. 将Bean A的工厂对象存入三级缓存(关键步骤!)addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 3. 属性注入(触发Bean B的创建)populateBean(beanName, mbd, instanceWrapper);// 4. 初始化(调用InitializingBean等)exposedObject = initializeBean(beanName, exposedObject, mbd);return exposedObject;
}

核心方法⬇️


protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 存入三级缓存this.singletonFactories.put(beanName, singletonFactory);// 确保二级缓存中没有残留this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

获取Bean的源码提取如下⬇️:

public class DefaultSingletonBeanRegistry {// 一级缓存:存放完全初始化的Bean(成品对象)private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();// 二级缓存:存放早期暴露的Bean(半成品对象,未完成属性注入)private final Map<String, Object> earlySingletonObjects = new HashMap<>();// 三级缓存:存放Bean工厂对象(用于生成半成品对象)private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();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. 从三级缓存获取工厂对象ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 生成早期引用(可能被AOP代理)singletonObject = singletonFactory.getObject();// 升级到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;}
}

补充源码图如下:

循环依赖限制

三级缓存机制仅能解决单例Bean且非构造器注入的循环依赖。

单例Bean限制

原型(Prototype)作用域的Bean每次请求都会创建新实例,即提前暴露引用也没用,引用会发生变化,无法通过缓存保存中间状态,因此无法解决循环依赖。

创建bean的源码AbstractAutowireCapableBeanFactory.doCreateBean中也做了bean是否是singleton的判断

构造器注入限制

构造器注入无法解决循环依赖。构造器注入要求在实例化Bean时立即注入依赖,而Spring需要实例化完成后才能提前暴露对象引用。构造器循环依赖时,Bean尚未实例化,无法提前暴露引用,导致死锁。

@Service
public class A {private final B b;@Autowired // 构造器注入B,创建B需要注入Apublic A(B b) {this.b = b;}
}@Service
public class B {private final A a;@Autowiredpublic B(A a) { // 构造器注入A,创建A需要注入Bthis.a = a;}
}

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

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

相关文章

微服务架构中的API网关:Spring Cloud与Kong/Traefik等方案对比

微服务架构中的API网关&#xff1a;Spring Cloud与Kong/Traefik等方案对比 一、API 网关的概念二、API 网关的主要功能2.1 统一入口与路由转发2.2 安全与权限控制2.3 流量管理与容错2.4 API 管理与聚合2.5 监控与日志2.5 协议转换与适配2.6 控制平面与配置管理 三、API 网关选型…

中兴B860AV3.2-T/B860AV3.1-T2_S905L3-B_2+8G_安卓9.0_先线刷+后卡刷固件-完美修复反复重启瑕疵

中兴电信B860AV3.2-T&#xff0f;B860AV3.1-T2_晶晨S905L3-B芯片_28G_安卓9.0_先线刷后卡刷-刷机固件包&#xff0c;完美修复刷机后盒子反复重启的瑕疵。 这两款盒子是可以通刷的&#xff0c;最早这个固件之前论坛本人以及其他水友都有分享交流过不少的固件&#xff0c;大概都…

Stable Diffusion lora训练(一)

一、不同维度的LoRA训练步数建议 2D风格训练 数据规模&#xff1a;建议20-50张高质量图片&#xff08;分辨率≥10241024&#xff09;&#xff0c;覆盖多角度、多表情的平面风格。步数范围&#xff1a;总步数控制在1000-2000步&#xff0c;公式为 总步数 Repeat Image Epoch …

Web3 时代数据保护的关键挑战与应对策略

Web3 时代数据保护的关键挑战与应对策略 随着互联网技术的飞速发展&#xff0c;我们正步入 Web3 时代&#xff0c;这是一个以去中心化、用户主权和数据隐私为核心的新时代。在这个时代&#xff0c;数据保护成为了一个至关重要的议题。本文将探讨 Web3 时代数据保护面临的主要挑…

微信小程序计算属性与监听器:miniprogram-computed

小程序框架没有提供计算属性相关的 api &#xff0c;但是官方为开发者提供了拓展工具库 miniprogram-computed。 该工具库提供了两个功能&#xff1a; 计算属性 computed监听器 watch 一、安装 miniprogram-computed 在项目的根目录下&#xff0c;使用如下命令&#xff0c;…

实体机安装linux视频教程。windows和ubuntu共存。启动时选择切换引导系统。

登录ubuntu官网下载iso镜像。 https://ubuntu.com/download 桌面版带G U I 操作界面&#xff0c;服务版靠远程命令行操作&#xff0c;类似wsl&#xff0c;没有图形界面&#xff0c;显卡跑满无需分散算力到显示交互界面上。 点alter natice downloads可以下载旧版本。具体版本选…

Numpy

一、Numpy优势 学习目标 目标 了解Numpy运算速度上的优势 知道Numpy的数组内存块风格 知道Numpy的并行化运算 1 Numpy介绍 Numpy&#xff08;Numerical Python&#xff09;是一个开源的Python科学计算库&#xff0c;用于快速处理任意维度的数组。 Numpy支持常见的数组和矩…

小红书不绑定手机号会显示ip吗

小红书作为一个生活方式分享平台&#xff0c;拥有庞大的用户群体。在小红书上&#xff0c;用户可以分享自己的生活点滴、购物心得、美食体验等&#xff0c;与其他用户进行互动交流。最近&#xff0c;不少用户对于小红书是否会在不绑定手机号的情况下显示IP属地产生了疑问&#…

FPGA multiboot 方案

FPGA multiboot 方案 初版方案 初版方案不需要软件参与&#xff0c;只是为了验证flash启动。当前已完成。 使用jtag 通过vivaod harwaremanager去将fpga bit流文件加载到demo板flash中。 具体操作&#xff1a; 约束添加for golden bitstream # 设置电源参考&#xff0c;1.…

SpringBoot的启动原理?

大家好&#xff0c;我是锋哥。今天分享关于【SpringBoot的启动原理&#xff1f;】面试题。希望对大家有帮助&#xff1b; SpringBoot的启动原理&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot的启动原理主要是通过 SpringApplication 类来…

aws训练快速入门教程

AWS 相关核心概念 简洁地介绍一下AWS训练云服务的核心关联概念: AWS核心服务层: 基础设施层: EC2(计算), S3(存储), RDS(数据库)等人工智能层: SageMaker(训练平台), AI服务等 机器学习服务分级: 高层: 预构建AI服务(开箱即用)中层: SageMaker(主要训练平台)底层: 框架和基…

(一)飞行器的姿态欧拉角, 欧拉旋转, 完全数学推导(基于坐标基的变换矩阵).(偏航角,俯仰角,横滚角)

(这篇写的全是基矢变换矩阵)不是坐标变换矩阵,坐标变换矩阵的话转置一下,之后会有推导. 是通过M转置变换到P撇点.

工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管…

在 Windows 系统下,将 FFmpeg 编译为 .so 文件

1. 准备环境 确保你的 Windows 系统已安装以下工具&#xff1a; Android Studio NDK&#xff08;Native Development Kit&#xff09; MSYS2&#xff08;用于提供类 Unix 环境&#xff09; FFmpeg 源码 Git Bash&#xff08;可选&#xff0c;推荐使用&#xff09; 安装 …

蓝桥杯备考---》分类讨论之Fixed Points

这道题的意思啊&#xff0c;就是说我们在数组里输入n个数&#xff0c;我们尽可能让下标和数组的值相同&#xff0c;我们只能交换一次&#xff0c;最多能有多少个值和下标一样 这里我们需要分类讨论&#xff0c;如果每个下标的值都符合要求&#xff0c;我们就不交换了 如果不是…

什么是数学建模?数学建模是将实际问题转化为数学问题

数学建模是将实际问题转化为数学问题&#xff0c;并通过数学工具进行分析、求解和验证的过程。 一、数学建模的基本流程 问题分析 • 明确目标&#xff1a;确定需要解决的核心问题。 • 简化现实&#xff1a;识别关键变量、忽略次要因素。 • 定义输入和输出&#xff1a;明确模…

Microchip AN1477中关于LLC数字补偿器的疑问

最近在学习Microchip的AN1477关于LLC的功率级传递函数推导及数字补偿器设计&#xff0c;对其中的2P2Z数字补偿器的系数有一些困惑。我在MATLAB中运行了源程序提供的VMC_LLC.m文件&#xff0c;发现有些地方和AN1477中的结果不一致。现在把相关有疑问的地方列举出来&#xff0c;也…

【软考-架构】8.4、信息化战略规划-CRO-SCM-应用集成-电子商务

✨资料&文章更新✨ GitHub地址&#xff1a;https://github.com/tyronczt/system_architect 文章目录 信息化战略体系&#x1f4af;考试真题第一题第二题 信息系统战略规划&#x1f4af;考试真题第一题第二题 ✨客户关系管理CRM供应链管理SCM&#x1f4af;考试真题第一题第二…

Excel处理控件Spire.XLS系列教程:C# 在 Excel 中添加或删除单元格边框

单元格边框是指在单元格或单元格区域周围添加的线条。它们可用于不同的目的&#xff0c;如分隔工作表中的部分、吸引读者注意重要的单元格或使工作表看起来更美观。本文将介绍如何使用 Spire.XLS for .NET 在 C# 中添加或删除 Excel 单元格边框。 安装 Spire.XLS for .NET E-…

【工具分享】vscode+deepseek的接入与使用

目录 第一章 前言 第二章 获取Deepseek APIKEY 2.1 登录与充值 2.2 创建API key 第三章 vscode接入deepseek并使用 3.1 vscode接入deepseek 3.2 vscode使用deepseek 第一章 前言 deepseek刚出来时有一段时间余额无法充值&#xff0c;导致小编没法给大家发完整的流程&…