【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)

目录

  • 前置知识
    • 循环依赖的产生
    • Spring里面的3个Map
  • 课程内容
    • 一、只有一级缓存的推理演进
      • 1.1 直接将实例化后生成的对象放入到单例池里面
      • 1.1 引入一个中间Map存实例化后的早期对象(疑似二级缓存)
      • 1.3 解决1.2需要被代理的问题(疑似二级缓存)
      • 1.4 为什么要三级缓存
  • 学习总结

前置知识

循环依赖的产生

说到循环依赖大家都不陌生,循环依赖的代码,就是如下:

@Component
public class CircularA {@AutowiredCircularB b;
}@Component
public class CircularB {@AutowiredCircularA a;
}

但是大家有没有想过,循环依赖是如何产生的,然后又是怎么解决的呢?这里,我想给大家推演一下,就像咱是Spring作者一样,思考如何循环依赖。

Spring里面的3个Map

在这里,我还是想提前给大家先大概解释一下,在获取单例bean的时候,Spring源码出现的3个Map。分别如下:

  • Map<String, Object> singletonObjects:一级缓存。这个就是我们常说的单例池,这里存放的bean,是经历了完整Spring生命周期的,【走完了Spring所设计的生命周期】(这里的经历完整生命周期不是说非得要经历什么实例化前后、初始化前后。简单说,是:Spring认可的,成熟的Bean)
  • Map<String, Object> earlySingletonObjects:二级缓存。直接直译过来,这里存的是【早期单例Bean】。何为早期?就是相对前面的【成熟Bean】,【还没有走完生命周期】的Bean
  • Map<String, ObjectFactory<?>> singletonFactories:三级缓存。直译过来是【单例bean的工厂】。其实我还是喜欢用一个之前提到过的专有名词去解释:生产Bean的钩子方法缓存

课程内容

一、只有一级缓存的推理演进

我们先来看个图,在没有三级缓存之前,只有一个一级缓存的时候,如果A依赖了B,B依赖了A,那么就会造成下面的现象:
在这里插入图片描述
很显然,在我们刚开创建的过程中,单例池里面是不会有对象B,也不会有对象A的。毕竟它们才走到第二步【注入属性】,它是在最后一步才会把生成好的对象放入单例池中。所以,上图的情况,如果没有外部干预的话,在这两个bean之间就形成了一个闭环,无法解开了。这显然不是我们想要的结果,对吧。那这个问题该如何解决呢?

1.1 直接将实例化后生成的对象放入到单例池里面

这时候一个很正常的想法是,我提前放入到单例池里面不就行了吗,如下所示:
在这里插入图片描述
这样不就打破了吗?嘿嘿嘿
只能说有点道理,但不多。因为,在多线程环境下,可能会把【没初始化完】的bean暴露出去。这时候如果有人来访问单例池,直接拿到了这个BeanA,然后去调用里面的方法,在没有【属性注入】过的情况下,不就G了吗?是的,这就是并发安全问题!这里只能直接pass这个方案了

1.1 引入一个中间Map存实例化后的早期对象(疑似二级缓存)

一个很正常的思考,我新增一个Map,在实例化后即刻存起来不就得了呗。反正都已经实例化了,地址已经固定了,后面再怎么操作都是对这个地址上的对象操作,提前把这个对象暴露出去,完全不影响结果啊。
在这里插入图片描述
如上图所示,那我新增一个中间缓存Map来存储之前实例化后的对象,总可以吧?嗯,从流程图上来看,这个真的好像是最终答案了。
不过,如果这时候我问你【AOP在哪?】,阁下将如何应对呢?很显然啊,这里存放的是原始对象,那如果我需要的是被代理的对象呢?看吧,这样稍微一推敲,又出现问题了。那好,我们继续完善这个方案就是了

1.3 解决1.2需要被代理的问题(疑似二级缓存)

在这里插入图片描述
就这样,多加上一步AOP过程不就行了嘛,嘿嘿嘿。不过按照惯例,我已经【嘿嘿嘿】了,所以肯定得问一句:真的行吗?哈,真的行!确实没问题了。那为什么,还要三级缓存呢?

1.4 为什么要三级缓存

讲到这里,我就要开始装逼了。(我甚至怀疑Spring这么写也是在【装逼】,哈哈,开个玩笑)
在这里插入图片描述
其实这个网上挺多论调的,我也是总结了百家之长,再结合我课堂上老师说的,总结出了以下结论:(算是个人之见,大家参考一下)

  1. 生命周期被打破这个我认为是最重要的原因,但是也比较难被理解的一点。怎么理解呢?大家还记得我以开始怎么形容Spring的吗?Spring的核心是什么?大家知道AOP的实现是在一块吗?
    • 第一个问题:Spring是实现了AOP技术的IOC容器
    • 第二个问题:Spring的核心是IOC跟AOP,但是,所有的基础都来自于IOC
    • 第三个问题:AOP的实现,是在bean生命周期的【初始化后】阶段。因为,AOP技术目前的实现,也是基于Spring提供的众多拓展点里面的某些个而已。比如AOP的实现就使用了:BeanPostProcessor。这里透露出来的意思是什么呢?我认为,它的意思是:在Spring内部,都只是把AOP当作额外拓展而已。就好像是我们基于Spring的拓展点实现了Mybatis,实现了SpringMVC一样的道理。

PS:所以到了这里大家伙知道这个生命周期被打破如何理解了吗?如果我们在实例化后就做判断是否需要做AOP的话,等于,还没【属性注入】,还没做【初始化前】、【初始化】、【初始化后】等等生命周期呢,就要开始了。并且呀,在实现这个AOP的过程中,你还得调用类似如下的方法:

for(BeanPostProcessor bp : this.beanPostProcessorsCache) {bp.postProcessAfterInitialization(bean);
}

但是这个代码,其实在后面的【初始化后】也会被调用的。我猜有的朋友会这么说:那我循环遍历实现了AOP的那几个指定的,实现了AOP的BeanPostProcessor不就行了吗?嗯,说实在确实行。不过,如果我们站在Spring的角度来看:AOP不过也是我IOC的一个拓展内容而已。这么来看的话,这么实现就侵入有点大了,而且语义上也稍微变了。

  1. 循环依赖出现频率。我想我呢问大家,你在实际使用场景中,循环依赖出现的多吗?弟弟我写Java代码4年多,我印象中就几次而已。so,你看看上面的解决方案如何?它每一次实例化生成Bean之后都做了判断!是否有点多余呢?
  2. 代码风格!说这个就很抽象了,但是对于Spring这种优秀的源码来说又有点情有可原。这句话怎么理解呢?

其实2、3要结合起来,一起建立在【1】最后的挣扎上,即我还是要在【实例化】完成后就开始判断是否需要AOP。

学习总结

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

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

相关文章

李沐pytorch学习-经典CNN的原理及代码实现

一、LeNet 1.1 模型结构 LeNet结构如图1所示&#xff0c;汇聚层即池化层&#xff0c;这里池化Stride&#xff08;步幅&#xff09;与池化层长宽一致&#xff0c;因此使得池化后大小减半。 图1. LeNet结构 1.2 代码实现 代码实现如下&#xff1a; import torch from torch imp…

评测凯迪仕K70「千里眼」智能锁:不忘安全初心,便捷体验更上一层

能打败凯迪仕的&#xff0c;只有它自己。这是我们在体验过凯迪仕最新旗舰产品K70「千里眼」智能锁之后的感受。作为凯迪仕2023年最新旗舰机型&#xff0c;K70「千里眼」智能锁在配置上可以说是「机皇」般的存在。3K超高清智能锁猫眼、车规级24GHz雷达、大小双屏设计、三方可视对…

k8s部署prometheus

1、prometheus部署yml文件地址 github地址 2、下载yml文件 rootiZj6cd9joygowsf7am5hryZ:~# git clone https://github.com/redhatxl/k8s-prometheus-grafana.git Cloning into k8s-prometheus-grafana... remote: Enumerating objects: 21, done. remote: Total 21 (delta 0)…

table表头颜色 element plus

原图 预期 css :deep(.el-table__header) {background-color: #F5F7FA;} :deep(.el-table tr) {background-color: rgba(0,0,0,0);} :deep(.el-table th.el-table__cell) {background-color: rgba(0,0,0,0);}

<指针进阶>指针数组和数组指针傻傻分不清?

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

Stable Diffusion训练Lora模型

以下内容参考:https://www.bilibili.com/video/BV1Qk4y1E7nv/?spm_id_from333.337.search-card.all.click&vd_source3969f30b089463e19db0cc5e8fe4583a 1、训练Lora的2个重点步骤 第一步&#xff0c;准备训练要使用的图片&#xff0c;即优质的图片 第二部&#xff0c;为…

在Qt窗口中添加右键菜单

在Qt窗口中添加右键菜单 基于鼠标的事件实现流程demo 基于窗口的菜单策略实现Qt::DefaultContextMenuQt::ActionsContextMenuQt::CustomContextMenu信号API 基于鼠标的事件实现 流程 需要使用:事件处理器函数(回调函数) 在当前窗口类中重写鼠标操作相关的的事件处理器函数&a…

【论文阅读】HOLMES:通过关联可疑信息流进行实时 APT 检测(SP-2019)

HOLMES: Real-time APT Detection through Correlation of Suspicious Information Flows S&P-2019 伊利诺伊大学芝加哥分校、密歇根大学迪尔伯恩分校、石溪大学 Milajerdi S M, Gjomemo R, Eshete B, et al. Holmes: real-time apt detection through correlation of susp…

飞天使-kubeadm安装一主一从集群

文章目录 安装前准备安装前准备配置yum源等安装前准备docker安装 安装kubeadm配置kubeadm验证集群 参考链接 安装前准备 cat >> /etc/hosts <<EOF 192.168.100.30 k8s-01 192.168.100.31 k8s-02 EOF hostnamectl set-hostname k8s-01 #所有机器按照要求修改 ho…

IDEA创建Mybatis格式XML文件

设置位置&#xff1a;File | Settings | Editor | File and Code Templates 选择Files&#xff0c;点击号 Name中输入xml模板名&#xff08;名称自行决定&#xff09;&#xff0c;后缀名extension输入xml&#xff08;固定&#xff09; 内容处输入Mybatis的xml文件模板内容&…

【Python爬虫案例】爬取大麦网任意城市的近期演出!

老规矩&#xff0c;先上结果&#xff1a; 含10个字段&#xff1a; 页码&#xff0c;演出标题&#xff0c;链接地址&#xff0c;演出时间&#xff0c;演出城市&#xff0c;演出地点&#xff0c;售价&#xff0c;演出类别&#xff0c;演出子类别&#xff0c;售票状态。 代码演示…

稳定扩散ControlNet v1.1 权威指南

ControlNet 是一种稳定扩散模型&#xff0c;可让你从参考图像中复制构图或人体姿势。 经验丰富的稳定扩散用户知道生成想要的确切成分有多难。图像有点随机。你所能做的就是玩数字游戏&#xff1a;生成大量图像并选择你喜欢的图片。 借助 ControlNet&#xff0c;稳定扩散用户…

Android Studio 接入OpenCV最简单的例子 : 实现灰度图效果

1. 前言 上文 我们在Windows电脑上实现了人脸功能&#xff0c;接下来我们要把人脸识别的功能移植到Android上。 那么首先第一步&#xff0c;就是要创建一个Native的Android项目&#xff0c;并且配置好OpenGL&#xff0c;并能够调用成功。 这里我们使用的是openCV-4.8.0&#x…

(三)Linux中卸载docker(非常详细)

docker 卸载 使用yum安装docker 如需卸载docker可以按下面步骤操作&#xff1a; 1、停止docker服务 systemctl stop docker 2、查看yum安装的docker文件包 yum list installed |grep docker 3、查看docker相关的rpm源文件 rpm -qa |grep docker 4、删除所有安装的docke…

redis 哨兵模式

目录 一、什么是哨兵模式 二、配置哨兵 三、启动哨兵 四、验证哨兵 五、复制延时 六、选举策略 一、什么是哨兵模式 哨兵也叫 sentinel&#xff0c;它的作用是能够在后台监控主机是否故障&#xff0c;如果故障了根据投票数自动将从库转换为主库。 二、配置哨兵 首先停止…

【后端速成 Vue】第一个 Vue 程序

1、为什么要学习 Vue&#xff1f; 为什么使用 Vue? 回想之前&#xff0c;前后端交互的时候&#xff0c;前端收到后端响应的数据&#xff0c;接着将数据渲染到页面上&#xff0c;之前使用的是 JavaScript 或者 基于 JavaScript 的 Jquery&#xff0c;但是这两个用起来还是不太…

使用在 Web 浏览器中运行的 VSCode 实现 ROS2 测程法

一、说明 Hadabot是软件工程师学习ROS2和机器人技术的机器人套件。我们距离Hadabot套件的测试版还有一周左右的时间。我们将在本文末尾披露有关如何注册的更多信息。 新的Hadabot套件完全支持ROS2。除了硬件套件外&#xff0c;Hadabot软件环境将主要基于Web浏览器&#xff0c;以…

基于JJWT理解JWT,JWS,JWE

JWT &#xff0c; 全写JSON Web Token, 是开放的行业标准RFC7591&#xff0c;用来实现端到端安全验证. 从开发者理解的角度来说&#xff1a; JWT 是一个定义传递信息的标准JWT 的Token是一个Base64编码的字符串&#xff0c; 类似 eyJhbGciOiJIUzI1NyJ9.eyJzdWIiOiJvc2NhciJ9.…

HTTP和HTTPS的区别及通信原理

文章目录 HTTP特性http解决无状态的问题&#x1f36a;cookiesessiontoken 常见状态码报文和字段方法 HTTPS补充知识常见的加密方式数字摘要 &#xff08;数字指纹&#xff09; && 数字签名 加密过程 HTTP 何为http&#xff1f; http是超文本传输协议&#xff0c;Hyper…

linux的http服务

Web通信基本概念 基于B/S&#xff08;Browser/Server&#xff09;架构的网页服务 服务端提供网页 浏览器下载并显示网页 Hyper Text Markup Lanuage,超文本标记语言 Hyper Text Transfer Protocol&#xff0c;超文本传输协议 虚拟机A&#xff1a;构建基本的Web服务 [root…