Spring——三级缓存解决循环依赖详解

news2024/11/25 13:13:42

三级缓存解决循环依赖详解

  • 一、什么是三级缓存
  • 二、三级缓存详解
    • Bean实例化前
    • 属性赋值/注入前
    • 初始化后
    • 总结
  • 三、怎么解决的循环依赖
  • 四、不用三级缓存不行吗
  • 五、总结

一、什么是三级缓存

就是在Bean生成流程中保存Bean对象三种形态的三个Map集合,如下:

// 一级缓存Map 存放完整的Bean(流程跑完的)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

// 二级缓存Map 存放不完整的Bean(只实例化完,还没属性赋值、初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

// 三级缓存Map 存放一个Bean的lambda表达式(也是刚实例化完)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

用来解决什么问题?

这个大家应该熟知了,就是循环依赖

什么是循环依赖?

就像下面这样,AService 中注入了BService ,而BService 中又注入了AService ,这就是循环依赖

@Service
public class AService {

    @Resource
    private BService bService;
}

@Service
public class BService {

    @Resource
    private AService aService;
}

这几个问题我们结合源码来一起看一下

三级缓存分别在什么地方产生的?

三级缓存是怎么解决循环依赖的?

一定需要三级缓存吗?二级缓存不行?

二、三级缓存详解

不管你了不了解源码,我们先看一下Bean的生成流程,看看三级缓存是在什么地方有调用,就三个地方:

  1. Bean实例化前会先查询缓存,判断Bean是否已经存在
  2. Bean属性赋值前会先向三级缓存中放入一个lambda表达式,该表达式执行则会生成一个半成品Bean放入二级缓存
  3. Bean初始化完成后将完整的Bean放入一级缓存,同时清空二、三级缓存

接下来我们一个一个看!

在这里插入图片描述

Bean实例化前

AbstractBeanFactory.doGetBean

Bean实例化前会从缓存里面获取Bean,防止重复实例化

在这里插入图片描述

DefaultSingletonBeanRegistry.getSingleton(String beanName, boolean allowEarlyReference)

我们看看这个获取的方法逻辑:

  1. 从一级缓存获取,获取到了,则返回
  2. 从二级缓存获取,获取到了,则返回
  3. 从三级缓存获取,获取到了,则执行三级缓存中的lambda表达式,将结果放入二级缓存,清除三级缓存
public Object getSingleton(String beanName) {
    return this.getSingleton(beanName, true);
}

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存中获取Bean 获取到了则返回 没获取到继续
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        // 从二级缓存中获取Bean  获取到了则返回 没获取到则继续
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 加一把锁防止 线程安全 双重获取校验
            synchronized(this.singletonObjects) {
                // 从一级缓存中获取Bean 获取到了则返回 没获取到继续
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    // 从二级缓存中获取Bean  获取到了则返回 没获取到则继续
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 从三级缓存中获取 没获取到则返回
                        ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 获取到了 执行三级缓存中的lambda表达式
                            singletonObject = singletonFactory.getObject();
                            // 并将结果放入二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 从三级缓存中移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}

属性赋值/注入前

AbstractAutowireCapableBeanFactory.doCreateBean

在这里插入图片描述

DefaultSingletonBeanRegistry.addSingletonFactory

这里就是将一个lambda表达式放入了三级缓存,我们需要去看一下这个表达式是干什么的!!

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized(this.singletonObjects) {
        // 一级缓存中不存在的话 
        if (!this.singletonObjects.containsKey(beanName)) {
            // 将lambda表达式放入三级缓存
            this.singletonFactories.put(beanName, singletonFactory);
            // 清除二级缓存 
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }

    }
}

AbstractAutowireCapableBeanFactory.getEarlyBeanReference

该方法说白了就是会判断该Bean是否需要被动态代理,两种返回结果:

  • 不需要代理,返回未属性注入、未初始化的半成品Bean
  • 需要代理,返回未属性注入、未初始化的半成品Bean的代理对象
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
        Iterator var5 = this.getBeanPostProcessors().iterator();
        // 遍历后置处理器
        while(var5.hasNext()) {
            BeanPostProcessor bp = (BeanPostProcessor)var5.next();
            // 找到实现SmartInstantiationAwareBeanPostProcessor接口的
            // 该接口getEarlyBeanReference方法什么时候会执行?
            // AOP动态代理的时候 该方法执行就是判断该Bean是否需要被代理
            // 需要代理则会创建代理对象返回
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    // 这个Object有两种情况,一是实例化后的半成品Bean,二是半成品Bean动态代理后的代理对象
    return exposedObject;
}

注意:这里只是把lambda表达式放入了三级缓存,如果不从三级缓存中获取,这个表达式是不执行的,一旦执行了,就会把半成品Bean或者半成品Bean的代理对象放入二级缓存中了

初始化后

AbstractBeanFactory.doGetBean

这里注意啊,这个getSingleton方法传参传了个lambda表达式,这个表达式内部就是Bean的实例化过程,初始化完成后,是要需要执行这个getSingleton方法的

在这里插入图片描述

DefaultSingletonBeanRegistry.getSingleton(beanName, singletonFactory)

这个方法与上面那个不一样,重载了

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     
        synchronized(this.singletonObjects) {
            // 第一次进来这里获取肯定为null
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               // 省略................
                try {
                    // 注意啊,这个就是执行外面那个传参的lambda表达式
                    // 所以这里才会跳到createBean方法那里去执行
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                } 
                // 省略................
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    this.afterSingletonCreation(beanName);
                }
                // 到了这说明Bean创建完了
                if (newSingleton) {
                    // 这里就会把Bean放入一级缓存中了 同时清除二、三级缓存
                    this.addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

DefaultSingletonBeanRegistry.addSingleton

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);
  }
}

总结

整个过程就三个地方跟缓存有关,我们假设现在要实例化A这个Bean,看看缓存是怎么变化的:

  1. 实例化前,获取缓存判断(三个缓存中肯定没有A,获取为null,进入实例化流程)
  2. 实例化完成,属性注入前(往三级缓存中放入了一个lambda表达式,一、二级为null)
  3. 初始化完成(将A这个Bean放入一级缓存,清除二、三级缓存)

以上则是单个Bean生成过程中缓存的变化!!

三、怎么解决的循环依赖

上面我们把Bean流程中利用缓存的三个重要的点都找出来了,也分析了会带来什么变化,接下来看看是怎么解决的循环依赖,我们看个图就懂了:

以A注入B,B注入A为例:

A属性注入前就把lambda表达式放入了第三级缓存,所以B再注入A的时候会从第三级缓存中找到A的lambda表达式并执行,然后将半成品Bean放入第二级缓存,所以此时B注入的只是半成品的A对象,B创建完成后返回给A注入,A继续初始化,完成创建。

注意: B注入的半成品A对象只是一个引用,所以之后A初始化完成后,B这个注入的A就随之变成了完整的A

在这里插入图片描述

从上述看第三级缓存是用来提前暴露Bean对象引用的,所以解决了循环依赖,但是第二级缓存的这个半成品Bean对象干嘛的呢?

假设A同时注入了B和C,B和C又都注入了A,这时A注入B,实例化B的过程和上述是一样的,但随后还会注入C,那这个C在注入A的时候还会有第三级缓存用吗?没了吧,所以它就只能用第二级缓存的半成品Bean对象了,同样也是引用而已

四、不用三级缓存不行吗

可能很多小伙伴得到的答案就是不行,而且答案是因为不确定这个Bean是不是代理对象,所以搞了个lambda表达式?答案真的是这样吗??

我们分析一下:AOP动态代理在没有循环依赖的时候是在哪里执行的?Bean初始化后!有循环依赖的时候是在属性赋值前,中间就间隔了一个属性注入对吧,没错,在属性注入的时候注入的是原始对象的引用还是代理对象的引用这个很重要,但是属性注入会影响AOP的结果吗?是否AOP创建代理对象和切面有关,和属性注入无关,所以我们完全可以在属性注入之前就知道这个Bean是代理对象还是非代理对象,就像下面这样,我不将表达式放入第三级缓存了,而是直接执行,将结果放入第二级缓存

在这里插入图片描述

这样可不可以?可以吧,这样用二级缓存就解决了,但是在一个对象没有属性赋值、初始化前就创建代理对象是有风险的!像这么做不管有没有产生循环依赖,只要有AOP动态代理对象的产生就有一分风险,这么做是得不偿失的,所以有了三级缓存,三级缓存是只有在循环依赖以及AOP动态代理同时产生时才会有风险。可以说是因为存在循环依赖所以被迫的导致Bean对象提前的暴露了引用!!! 所以这下懂了吧

至于为什么多例、构造器注入这两种情况解决不了循环依赖就很简单了:

循环依赖的解决原理是在对象实例化后提前暴露了引用,而这两种情况都还没实例化呢

五、总结

  • 一级缓存:用于存储被完整创建了的bean。也就是完成了初始化之后,可以直接被其他对象使用的bean。
  • 二级缓存:用于存储半成品的Bean。也就是刚实例化但是还没有进行初始化的Bean
  • 三级缓存:三级缓存存储的是工厂对象(lambda表达式)。工厂对象可以产生Bean对象提前暴露的引用(半成品的Bean或者半成品的代理Bean对象),执行这个lambda表达式,就会将引用放入二级缓存中

经过以上的分析,现在应该懂了吧:

循环依赖是否一定需要三级缓存来解决? 不一定,但三级缓存会更合适,风险更小

二级缓存能否解决循环依赖? 可以,但风险比三级缓存更大

第二级缓存用来干嘛的? 存放半成品的引用,可能产生多对象循环依赖,第三级缓存产生引用后,后续的就可以直接注入该引用

多例、构造器注入为什么不能解决循环依赖? 因为循环依赖的原理的实例化后提前暴露的引用,这两种情况还没实例化

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

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

相关文章

IPv6进阶:IPv6 过渡技术之 NAT64(IPv6 节点主动访问 IPv4 节点-地址池方式)

实验拓扑 PC1是IPv4网络的一个节点&#xff0c;处于Trust安全域&#xff1b;PC2是IPv6网络的一个节点&#xff0c;处于Untrust安全域。 实验需求 完成防火墙IPv4、IPv6接口的配置&#xff0c;并将接口添加到相应的安全域&#xff1b;在防火墙上配置NAT64的IPv6前缀3001::/64&…

cpu设计和实现(数据访问)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在cpu设计当中&#xff0c;数据访问是比较重要的一个环节。一般认为&#xff0c;数据访问就是内存访问。其实不然。我们都知道&#xff0c;cpu访问…

【微服务】SpringCloud中Ribbon的轮询(RoundRobinRule)与重试(RetryRule)策略

💖 Spring家族及微服务系列文章 ✨【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡 ✨【微服务】SpringCloud轮询拉取注册表及服务发现源码解析 ✨【微服务】SpringCloud微服务续约源码解析 ✨【微服务】SpringCloud微服务注册源码解析 ✨

Nginx的操作

一、什么是nginx。 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器 , 其特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上nginx的并发能力在同类型的网页服务器中表现较好。 Nginx代码完全用C语言从头写成 . 能够支持高达 50,000 个并发连接数的响应. 现在…

【pen200-lab】10.11.1.72

pen200-lab 学习笔记 【pen200-lab】10.11.1.72 &#x1f525;系列专栏&#xff1a;pen200-lab &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月27日&#x1f334; &#x1f36d;作…

aws beanstalk 使用eb cli配置和启动环境

Elastic Beanstalk 不额外收费&#xff0c;只需为存储和运行应用程序所需的 AWS 资源付费 EB CLI 是 Amazon Elastic Beanstalk 的命令行界面&#xff0c;它提供了可简化从本地存储库创建、更新和监控环境的交互式命令 安装eb cli $ pip install virtualenv $ virtualenv ebve…

2023年考研数学测试卷(预测)

2023年考研数学测试卷 原题再现&#xff1a; 多的我也不说了&#xff0c;直接把预测的2023年考研数学卷子分享给大家好吧&#xff0c;准确详细全面是我的宗旨&#xff0c;我的博客创立初衷和发展方向肯定不应该只是"考试有用"&#xff0c;而是面对社会生产生活的有用…

Day815.数据库参数设置优化 -Java 性能调优实战

数据库参数设置优化 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于数据库参数设置优化。 MySQL 是一个灵活性比较强的数据库系统&#xff0c;提供了很多可配置参数&#xff0c;便于根据应用和服务器硬件来做定制化数据库服务。 数据库主要是用来存取数据的&#…

视频编解码 — DCT变换和量化

目录 视频编码流程 DCT变换 Hadamard变换 量化 H264中的DCT变换和量化 H264各模式的DCT变换和量化过程 1、亮度16x16帧内预测块 2&#xff0c;其它模式亮度块 3&#xff0c;色度块 小结 视频编码流程 DCT变换 离散余弦变换 它能将空域信号转换到频率上表示&#xff0…

建造者模式

文章目录定义优点使用场景代码实现定义 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 4个角色&#xff1a; Product产品类&#xff1a;通常是实现了模板方法模式&#xff0c;也就是有模板方法和基本方法Builder抽象建造者&#xf…

PyQt5可视化编程-事件、信号和对话框

一、概述: 所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的&#xff0c;当然也有其他的事件产生方式&#xff0c;比如网络的连接&#xff0c;窗口管理器或者定时器等。调用应用的exec_()方法时&#xff0c;应用会进入主循环&#xff0c;主循环会监听和分发事件。…

算法题:整数除法

一.题目描述以及来源 给定两个整数 a 和 b &#xff0c;求它们的除法的商 a/b &#xff0c;要求不得使用乘号 *、除号 / 以及求余符号 % 。 注意&#xff1a; 整数除法的结果应当截去&#xff08;truncate&#xff09;其小数部分&#xff0c;例如&#xff1a;truncate(8.345…

MP157-2-TF-A移植:

MP157-2-TF-A移植&#xff1a;1. TF-A移植&#xff1a;1.1 新建开发板的设备树1.2 修改设备树电源管理1.3修改TF卡和EMMC设备树1.4 修改USBOTG设备树2 编译测试2.1 Makefile.sdk 修改内容&#xff1a;2.2 编译命令&#xff1a;正点原子第九章内容&#xff1a;自己记的笔记&…

SpringBoot(One·上)

SpringBoot一、简介概述Spring Boot特性SpringBoot四大核心二、SpringBoot项目分析1、创建第一个案例结构目录和pom文件2、Springboot集成mvcSpringboot核心配置文件application.propertiesSpringboot核心配置文件application.yml或者application.yamlapplication.ymlapplicati…

Allegro削铜皮详细操作指导

Allegro削铜皮详细操作指导 Allegro可以编辑任意形状的铜皮,下面介绍几种削铜皮的方式 任意形状,shape-manual Void/cavity-Polygon 鼠标左键点击铜皮,铜皮会被亮起来 画出需要的形状 完成后如下图 方形shape-manual Void/cavity-Rectangular 同样的选择铜皮,画出需要…

通过 js 给元素添加动画样式animation属性 ,以及 perspective 属性探究

学习关键语句: js添加动画效果 js控制元素animation属性 写在前面 在制作组件的过程中呢 , 突然觉得这个动画啊应该由用户来决定到底是个啥样 , 但是怎么让用户操作这一步呢 ? 总不能让用户自己去写 css keyframe 吧 , 所以便有了这篇文章 , 同时 , 这篇文章的下半部分我们会…

Python+Selenium:Google patent数据爬取

准备工作,已搭建Python环境,安装Selenium pip install selenium -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com 步骤1: 根据Chrome版本下载ChromeDriver 下载链接地址: ChromeDriver - WebDriver for Chrome - Downloads 如在帮助——>关于Ch…

车道线检测-lanedet

源码&#xff1a;https://github.com/Turoad/lanedet 这是一个常见的检测网络整合版本&#xff0c;目前包括的检测网络有&#xff1a; 模型论文介绍 SCNN&#xff0c;RESA论文介绍&#xff0c;UFLD介绍&#xff0c;laneNet|其它相关模型&#xff0c;LaneATT介绍 数据集介绍…

机器学习之归一化

机器学习之归一化1.目的1.1损失函数求解问题1.2 归一化目的2. 归一化2.1 最大值最小值归一化2.2 标准化1.目的 1.1损失函数求解问题 线性回归Loss函数梯度公式 参数含义θ\thetaθ函数参数α\alphaα学习率xjix^i_{j}xji​x:数据集&#xff0c;i:样本&#xff0c;j:特征 【数…

OPengl学习(二)——opengl环境搭建

文章目录0、 概念/准备1、VSOpengl快速添加手动编译2、QT中使用opengl1.pro配置文件2.引入头文件 继承QGLWidget3.实现三个主要函数3、引用0、 概念/准备 opengl官网地址 1、OpenGL 函数库相关的 API 有核心库&#xff08;gl&#xff09;&#xff0c;实用库&#xff08;glu&a…