Spring三级缓存详解

2023-11-11

Spring三级缓存是为了解决对象间的循环依赖问题。

A依赖B,B依赖A,这就是一个简单的循环依赖

我们来先看看三级缓存的源码。

(1)查看“获取Bean”的源码,注意getSingleton()方法。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
        //第1级缓存 用于存放 已经属性赋值、完成初始化的 单列BEAN
        private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
        //第2级缓存 用于存在已经实例化,还未做代理属性赋值操作的 单例BEAN
        private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
        //第3级缓存 存储创建单例BEAN的工厂
        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
        //已经注册的单例池里的beanName
        private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
        //正在创建中的beanName集合
        private final Set<String> singletonsCurrentlyInCreation =
                Collections.newSetFromMap(new ConcurrentHashMap<>(16));
        //缓存查找bean  如果第1级缓存没有,那么从第2级缓存获取。如果第2级缓存也没有,那么从第3级缓存创建,并放入第2级缓存。
        protected Object getSingleton(String beanName, boolean allowEarlyReference) {
            Object singletonObject = this.singletonObjects.get(beanName); //第1级
            if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
                synchronized (this.singletonObjects) {
                    singletonObject = this.earlySingletonObjects.get(beanName); //第2级
                    if (singletonObject == null && allowEarlyReference) {
                        //第3级缓存  在doCreateBean中创建了bean的实例后,封装ObjectFactory放入缓存的bean实例
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            //创建未赋值的bean
                            singletonObject = singletonFactory.getObject();
                            //放入到第2级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            //从第3级缓存删除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
            return singletonObject;
        }   
    }

(2)“添加到第1级缓存”的源码:

 protected void addSingleton(String beanName, Object singletonObject) {
            synchronized (this.singletonObjects) {
                // 放入第1级缓存
                this.singletonObjects.put(beanName, singletonObject);
                // 从第3级缓存删除
                this.singletonFactories.remove(beanName);
                // 从第2级缓存删除
                this.earlySingletonObjects.remove(beanName);
                // 放入已注册的单例池里
                this.registeredSingletons.add(beanName);
            }
        }

(3)“添加到第3级缓存”的源码:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
            synchronized (this.singletonObjects) {
                // 若第1级缓存没有bean实例
                if (!this.singletonObjects.containsKey(beanName)) {
                    // 放入第3级缓存
                    this.singletonFactories.put(beanName, singletonFactory);
                    // 从第2级缓存删除,确保第2级缓存没有该bean
                    this.earlySingletonObjects.remove(beanName);
                    // 放入已注册的单例池里
                    this.registeredSingletons.add(beanName);
                }
            }
        }

(4)“创建Bean”的源码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    
    if (instanceWrapper == null) {
        //实例化对象
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }
 
    final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
    Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
   
    //判断是否允许提前暴露对象,如果允许,则直接添加一个 ObjectFactory 到第3级缓存
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        //添加到第3级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
 
    //填充属性
    this.populateBean(beanName, mbd, instanceWrapper);
    //执行初始化方法,并创建代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    return exposedObject;
}

通过这段代码,我们可以知道:Spring 在实例化对象之后,就会为其创建一个 Bean 工厂,并将此工厂加入到三级缓存中。

因此,Spring 一开始提前暴露的并不是实例化的 Bean,而是将 Bean 包装起来的ObjectFactory。为什么要这么做呢?

这实际上涉及到 AOP。如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是,Spring一开始并不知道 Bean是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在“完成填充属性并且执行完初始化方法”之后再为其创建代理。但是,如果出现了循环依赖,Spring 就不得不为其提前创建"代理对象";否则,注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到"应该在哪里提前创建代理对象"?

Spring 的做法就是:在 ObjectFactory 中去提前创建代理对象。它会执行 getObject() 方法来获取到 Bean。实际上,它真正执行的方法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 如果需要代理,这里会返回代理对象;否则,返回原始对象。
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

提前进行对象的代理工作,并在 earlyProxyReferences map中记录已被代理的对象,是为了避免在后面重复创建代理对象。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 记录已被代理的对象
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
}

再次分析获取bean的方法getSingleton()方法,可知:

提前暴露的对象,虽然已实例化,但是没有进行属性填充,还没有完成初始化,是一个不完整的对象。 这个对象存放在二级缓存中,对于三级缓存机制十分重要,是解决循环依赖一个非常巧妙的设计。

让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情景。

  1. A 调用doCreateBean()创建Bean对象:由于还未创建,从第1级缓存singletonObjects查不到,此时只是一个半成品(提前暴露的对象),放入第3级缓存singletonFactories。
  2. A在属性填充时发现自己需要B对象,但是在三级缓存中均未发现B,于是创建B的半成品,放入第3级缓存singletonFactories。
  3. B在属性填充时发现自己需要A对象,从第1级缓存singletonObjects和第2级缓存earlySingletonObjects中未发现A,但是在第3级缓存singletonFactories中发现A,将A放入第2级缓存earlySingletonObjects,同时从第3级缓存singletonFactories删除。
  4. 将A注入到对象B中。
  5. B完成属性填充,执行初始化方法,将自己放入第1级缓存singletonObjects中(此时B是一个完整的对象),同时从第3级缓存singletonFactories和第2级缓存earlySingletonObjects中删除。
  6. A得到“对象B的完整实例”,将B注入到A中。
  7. A完成属性填充,执行初始化方法,并放入到第1级缓存singletonObjects中。
     

在创建过程中,都是从第三级缓存(对象工厂创建不完整对象),将提前暴露的对象放入到第二级缓存;从第二级缓存拿到后,完成初始化,并放入第一级缓存。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring三级缓存详解 的相关文章

随机推荐

  • matlab绘制奈奎图,matlab画奈奎斯特图

    基于matlab 的时域奈奎斯特定理验证课题名称 利用 matlab 检验采样定理 学院 计通学院 专业班级 通信 1402 2016 年 6 月 设计目的 1 掌握 matlab 的一些应用 奈奎斯特图和波特图解释 物理 自然科学 专业资
  • java-springboot、mybatis、redis相关面试题

    SpringBoot部分 相比Spring Spring Boot有哪些优点 Springboot是一个基于spring的框架 对spring做了大量简化 使开发流程更快 更高效 它大量简化maven依赖 管理了大量的基础依赖 基于注解配置
  • ovirt-engine平台vm rest-api新增接口字段

    背景 基于ovirt engine二次开发 为实现软删除功能 对现有的vm接口进行扩展字段 增加判断软删除字段以及软删除的时间字段 1 首先要再ovirt engine api model工程中 找到对应的资源类型 添加你需要的字段 typ
  • Similarity-Preserving Knowledge Distillation(2019ICCV)----论文阅读笔记

    Similarity Preserving Knowledge Distillation Abstract 1 Introduction 贡献 2 Method 3 Experiments 3 1 CIFAR 10 3 2 Transfer
  • 生成6位随机数

    随机生成六位不重复的数字 private static int generate6BitInt int arr 0 1 2 3 4 5 6 7 8 9 将数组随机打乱 据算法原理可知 重复概率 1 10 1 9 1 8 1 7 1 6 1
  • Yolov3 和 Yolov3-tiny目标检测算法理论与实现(TensorFlow2)

    文章目录 前言 一 Yolov3 和 Yolov3 tiny 1 网络结构 yolov3 tiny yolov3 框的回归 二 配置训练参数 1 目标检测数据集 2 设置anchor box 和classes 三 配置训练过程 四 模型预测
  • MySQL综合练习(50道)

    目录 一 准备工作 建表 插入数据 二 SQL练习 50道 1 查询 01 课程比 02 课程成绩高的学生的信息及课程分数 2 查询 01 课程比 02 课程成绩低的学生的信息及课程分数 3 查询平均成绩大于等于60分的同学的学生编号和学生
  • Git 检出、查看分支、切换分支、新建分支等简单命令

    Git 检出 查看分支 切换分支 新建分支等简单命令 首先需要安装 Git 并且配置 Git 环境变量 在需要检出工程的目录鼠标右键打开命令窗口 首先拿到 Git 仓库 SSH 地址或者 https 地址 1 执行 clone 命令 下面使
  • Ubuntu下文件权限管理

    参考 Ubuntu Linux 下文件权限管理 作者 莘莘 发布时间 2021 07 12 17 09 48 网址 https blog csdn net lcx1837 article details 118676383 spm 1001
  • GetLastError()返回值列表

    GetLastError 返回值列表 0 操作成功完成 1 功能错误 2 系统找不到指定的文件 3 系统找不到指定的路径 4 系统无法打开文件 5 拒绝访问 6 句柄无效 7 存储控制块被损坏 8 存储空间不足 无法处理此命令 9 存储控制
  • Debian小技巧1--常用软件服务配置方法

    Debian小技巧1 常用软件服务配置方法 最近 由于需要开始使用debian系统了 在使用过程会碰见一些经典的配置和操作方法 因此和往常一样记录下自己操作过程 后续将持续更新 优化 一方面以便于自己查阅 另一方面分享给有需要的人学习 1
  • 西门子触摸屏vb脚本从入门到精通_如何使用西门子触摸屏做一个弹窗?

    上篇文章我们讲到 西门子触摸屏变量的三种更新方式 根据命令 循环连续 循环使用 默认 这篇我们将一个实际案例 现场有台设备想在执行开机过程时触摸自动跳到执行过程的画面 假如PLC有个执行开机的变量M0 0 当M0 0为1的时候 自动切换画面
  • vue 评论小案例

  • vqvae详细解释

    模型综述 VQ VAE Vector Quantised Variational AutoEncoder 首先出现在论 Neural Discrete Representation Learning VAE假设隐向量分布服从高斯分布 VQV
  • 一些开源的,好看的前端组件/样式库

    以下是一些受欢迎的开源 CSS 样式库 Bootstrap https getbootstrap com Materialize CSS https materializecss com Bulma https bulma io Found
  • linux下测试磁盘的读写IO速度

    使用dd命令 这不是一个专业的测试工具 不过如果对于测试结果的要求不是很苛刻的话 平时可以使用来对磁盘的读写速度作一个简单的评估 另外由于这是一个免费软件 基本上 NIX系统上都有安装 首先了解两个特殊设备 dev null 伪设备 回收站
  • 使用OpenCV-python对植物图片进行分类

    文章目录 图片分类的思路和需要加载的库函数说明 核心代码分析 数据预处理 文件处理和命名匹配 数据预处理 特征提取和标签提取 学习模型 数据划分和模型训练 预测输出 利用训练好的模型进行图片分类 完整代码附录和运行结果 图片分类的思路和需要
  • vmware无法打开内核设备 打开模块DevicePowerOn电源失败

    无法打开内核设备 VMCIDev VMX 重叠 I O 操作在进 行中 你想要在安装 VMware Workstation 前重启吗 打开模块DevicePowerOn电源失败 启动VM时报错如上 解决方法一 亲测可行 对虚拟机相对应的 v
  • 使用GetProcAddress获取C++重载函数

    GetProcAddress函数是用来在动态加载完动态链接库后 从链接库中获取函数地址的 它的用法如下 FARPROC GetProcAddress HMODULE hModule LPCSTR lpProcName hModule参数就是
  • Spring三级缓存详解

    Spring三级缓存是为了解决对象间的循环依赖问题 A依赖B B依赖A 这就是一个简单的循环依赖 我们来先看看三级缓存的源码 1 查看 获取Bean 的源码 注意getSingleton 方法 public class DefaultSin