从根儿上学习spring 十 之run方法启动第四段(4)

news2024/9/28 9:19:54

我们接着上一节已经准备开始分析AbstractAutowireCapableBeanFactory#doCreateBean方法,该方法是spring真正开始创建bean实例并初始化bean的入口方法,属于核心逻辑,所以我们新开一节开始分析。

图12

图12-530到536行

这几行的主要就是创建bean实例,也就是new或者反射创建一个对象出来。beanWrapper大家可以理解成刚创建的bean实例的存放容器,就像刚出生的人类宝宝需要放到一个专业容器中一样,后面用到我们再细说。这几行没什么逻辑,有些逻辑的就是535行的createBeanInstance(beanName, mbd, args)方法。我们来看下该方法的逻辑

图13-AbstractAutowireCapableBeanFactory#createBeanInstance方法

从该方法的注释可知,该方法可以使用合适的策略来创建bean实例,如工厂方法,构造器自动注入,和简单的实例化。

图13-1090到1093行

这几行的意思是如果该beanDefinition中提供了创建bean的Supplier接口,则调用该接口的get方法来创建bean对象。那么什么时候可以向beanDefinition中注册该接口呢?我们还没讲过ConfigurationClassPostProcessor类的逻辑,该类是扫描获取我们需要被spring管理的所有beanDefinition的入口,但是这个阶段spring并没有提供给我们扩展点去修改beanDefinition,似乎只有在我们前面讲beanFactoryProcessor接口的子接口BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法才可以通过registry参数获取到需要操作的beanDefiniction去注册Supplier。

1902行很简单就是调用Supplier接口get方法创建对象,不再多说。

图13-1095到1097行

1095行先判断beanDefinition的factoryMethodName属性是否为空,那么factoryMethodName属性是怎么来的呢?又是干嘛用的呢?

factoryMethodName是方法上加了@Bean注解的方法名。通过该方法spring会通过反射来调用该方法创建bean实例。instantiateUsingFactoryMethod(beanName, mbd, args);方法的具体逻辑就不带大家看了,就是通过beanDefiniton里的factoryBeanName(拥有factoryMethodName的bean名称)获取对应的bean实例,然后获取factoryMethodName方法参数,最终调用反射创建bean实例对象。

图14-AbstractAutowireCapableBeanFactory#createBeanInstance方法

图14-1100到1109行

这几行主要作用是针对prototype类型的bean才有作用,因为这几行是针对bean的多次创建场景,校验之前是否已经解析出需要用的构造器进行创建bean对象。这样就可以把解析出来的构造器缓存起来,不需要每次创建bean实例时都去解析一遍所要用的构造器。

通过1102行可以看出能走缓存是有前提的,就是参数args必须为空,这个args是getBean(String name, Object... args)方法的参数,也就是spring对用户传构造器参数的情况下每次都会走一遍构造器解析,而没有传构造器参数的情况下spring会根据构造器的参数类型自动解析并注入。这种情况下,下次创建时才会走构造器缓存。至于这么设计的原因,我猜测是因为走缓存的情况下spring也会把构造器的参数缓存起来,而用户动态传的参数是没法缓存的毕竟可能每次都不一样,所以针对这种情况才没走构造器缓存逻辑吧。

这里的resolved变量代表的就是能不能走构造器缓存逻辑,如果mbd.resolvedConstructorOrFactoryMethod不为空说明构造器之前已经解析过。这里的autowireNecessary表示是否可以走构造器的自动注入,如果mbd.constructorArgumentsResolved为true表示构造器的参数已经解析过了可以使用自动注入,否则就会调用1115行的instantiateBean(beanName, mbd)方法使用默认构造器创建bean实例。

图14-1120到1125行

这几行就是spring找合适的构造器并且自动为我们自动注入构造器参数,从if判断语句可知,至于能找到构造器且args不为空都会走自动注入逻辑

1125行--前面都不满足的情况下使用默认构造器创建bean对象

图12-537到541行

通过前面我们对createBeanInstance(beanName, mbd, args)方法spring已经为我们把bean的实例给创建出来了,537--541这几行逻辑很简单就是将bean的class类型设置到beanDefinition的resolvedTargetType属性中

图12-544到555行

这几行的核心就是调用了applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName)方法,点进这个方法可知也就是对当前的beanDefinition执行所有的MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法。MergedBeanDefinitionPostProcessor接口作为一个扩展接口,允许我们在postProcessMergedBeanDefinition方法中对beanDefinition进行操作。

或许大家会疑惑,这时候bean的实例已经创建出来了还能对beanDefinition做什么操作呢?这就得结合bean的初始化生命周期来看,这时候的bean实例是创建出来了,相当于new出来一个完全新的对象,而对象的属性还没设值,所以这时候对beanDefinition的操作也最多是影响bean的属性初始化。spring内部提供的MergedBeanDefinitionPostProcessor也确实大多都是跟bean的属性设值有关,如果大家有需求在bean实例化后操作beanDefinition也可以实现MergedBeanDefinitionPostProcessor接口。

图15-AbstractAutowireCapableBeanFactory#doCreateBean方法

接下来我们继续看AbstractAutowireCapableBeanFactory#doCreateBean方法的剩余部分。

图15-559到583行

这几行比较核心,是用来解决spring的循环依赖的。相信大家对spring循环依赖并不陌生,我就不赘述了。只是希望大家看完全部文章之后自己可以看源码真正了解哪些情况下spring才会有循环依赖问题,并加以总结。

559到560行:主要是判断当前的bean实例是否允许提前暴露,所谓的提前暴露就是这个bean在没有完全初始化好之后被其它bean引用。大家一定要对实例化和初始化有个基本了解,实例化是创建对象,而初始化对对创建好的对象就行设置属性等一些其它的初始化动作。从这里的判断条件我们可知允许提前暴露必须满足3个条件。1.当前bean是单例 2.spring容器被配置为允许循环依赖 3.当前bean正处于创建中时。

第2点很好理解,如果容器都不支持循环依赖都没必要提前暴露,因为提前暴露的前提就是出现了循环依赖。

第3点如果大家还记得调用链路前面的DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory)方法逻辑的话也很容易理解,这个方法会调用DefaultSingletonBeanRegistry#beforeSingletonCreation方法将beanNamt添加到singletonsCurrentlyInCreation容器,所以这里的第3点肯定是true。

第1点也不难理解,只有单例的bean才支持循环依赖,因为单例的bean一旦被引用就不会变,提前暴露出去才有意义。如果被引用的bean会变会生成不同的对象,那提前暴露出去后后面又生成新的对象,之前的对象不再被使用,那暴露出去就没有任何意义。

561到567行:在满足提前暴露的情况下,调用DefaultSingletonBeanRegistry#addSingletonFactory方法添加实例工厂,这个方法的核心逻辑就是向单例工厂map容器singletonFactories中以beanName作为key添加一个由lambda表达式构成的单例工厂singletonFactory,最终会调用AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法,等后面调用时我们再说。

之所以说这里是为了解决循环依赖问题,我这里提前透露下,因为接下来要执行下面的populateBean(beanName, mbd, instanceWrapper)方法就是给我们的bean属性进行设值,这时候有循环依赖的话会再次走到当前bean的初始化过程(比如A依赖B,B依赖A。当前bean是A,在初始化属性B的时候又会尝试去获取A,所以会再次走到A的初始化过程)

为了故事的完整我们假设当前的beanName叫beanA吧,暂先不看下面的populateBean(beanName, mbd, instanceWrapper)且假设有循环依赖的情况,这时beanA又将被spring调用getBean(beanName)方法尝试获取beanA实例,这时再次走到AbstractBeanFactory#doGetBean调用getSingleton(beanName)方法获取单例对象时就可以从单例工厂singletonFactories中获取beanA的单例工厂并调用getObject方法获取实例,获取到的实例会放到earlySingletonObjects容器中以便后面有其它循环依赖时无需再次调用工厂获取beanA,而是直接通过earlySingletonObjects容器返回可以提前暴露的beanA实例。大家这时候一定要配合着源码理解。

上面已经说到spring的所谓三级缓存在循环依赖中使用场景,大概总结下就是,spring在没有循环依赖时bean的初始化过程完完整整且不会被打断的话,初始化好完整的bean实例会放进singletonObjects容器中(一级缓存);在实例化好对象初始化bean属性前为了防止有循环依赖,会提前向单例工厂容器(二级缓存)中添加单例工厂,以便在发生循环依赖时可以通过单例工厂提前暴露bean实例;在发生循环依赖使用单例工厂获取提前暴露的bean实例后,会将bean实例放入earlySingletonObjects容器中以便后面有其它循环依赖时直接从该容器中获取已经创建好的提前暴露对象。

上面我们已经了解了三级缓存的使用场景,那么大家想下为什么需要使用三级缓存,只保留一级缓存或者两级缓存不行吗?比如为了防止发生循环依赖,我们不向单例工厂容器中添加单例工厂而是直接向singletonObjects容器中添加未初始化好的bean,后面有了循环依赖也直接从singletonObjects容器里拿未初始化好的bean不就行了吗?

我说些自己的看法,首先如果只使用一个缓存,那么势必将出现这个缓存里既有提前暴露的bean实例也有初始化好的bean实例情况,从单一原则设计原则考虑这是不合适的。那单例工厂SingletonFactory缓存是否有可以省略呢?在出现循环依赖时直接从缓存中获取提前暴露的bean实例不就好了吗?毕竟在添加单例工厂后初始化bean前已经实例化好bean了,如果出现循环依赖把这个实例化好的bean暴露出去不就好了,为什么还要从单例工厂获取呢?这其实主要是为了防止这个bean存在动态代理的情况,如果存在动态代理那这个bean的引用就会更改,从普通的bean变成动态代理的bean,如果这时候简单的把刚实例化好的普通bean暴露出去那就会有问题。而通过单例工厂可以对这个bean进行检查和包装,如果这个bean被动态代理了就会暴露动态代理的bean。

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

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

相关文章

先天亏钱圣体!谢瑜、陈梦、全红婵夺冠后,我看到了最残酷的社交真相——早读(逆天打工人爬取热门微信文章解读)

我怎么寻思这是普通事故 引言Python 代码第一篇 洞见 谢瑜、陈梦、全红婵夺冠后,我看到了最残酷的社交真相第二篇 亏麻了结尾 没想到是辆切糕车 引言 昨天文章的数据不错呀 200 的阅读 20的收藏 10:1已经是很高的比例了 再来干货吧 以后大家要是做视频 心中看到这…

Spring Cloud微服务项目聚合Swagger文档

在微服务架构中,每个服务通常都有自己独立的 API 文档。为了方便管理和查看所有服务的接口文档,我们需要将这些文档进行聚合。Spring Cloud 与 Swagger 的结合可以帮助我们实现这一目标。本文将介绍如何在 Spring Cloud 微服务项目中聚合 Swagger 文档&a…

72 成员方法、类方法、静态方法、抽象方法

在面向对象程序设计中,函数和方法这两个概念是有本质区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数自动传递过去,普通函数并不具备这个特点。 class Demo:passt Demo()def test(self,…

html+css网页设计公司网站模版3个页面 无js 静态页面

htmlcss网页设计公司网站模版3个页面 无js 静态页面 网页作品代码简单,可使用任意HTML编辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作)。 获取源…

推送给女朋友让她自己学习打光去(Stable Diffusion进阶篇:Imposing Consistent Light)

大家好我是极客菌!!! 对于学过stable diffusion的小伙伴来说,forge UI和Comfy UI会更加熟悉一些。在IC-Light发布后,Openpose editor的开发者将其制作成了一个Forge UI上的插件。 **https://github.com/huchenlei/sd-…

国内有哪些可以交易上证50etf期权的平台?

随着期权交易的普及,越来越多的投资者开始关注期权交易app平台。期权开通方式有券商和期权分仓平台两种,目前期权交易费用是7元左右一张,期权佣金是可以根据券商的证券范围进行调整的,下文为大家科普国内有哪些可以交易上证50etf期…

接口入门(企业常见使用,一分钟搞定版)

目录 1、接口的定义 定义位置 接口内容 2、接口的使用 正常实现接口 接口当做函数参数 匿名实现接口 3、OPPO便签接口具体分析 总结一下: 1、接口的定义 定义位置 可以写在类中,但注意现在接口名字是 类名.接口名 可以单独写在一个文件 接口内…

Linux系统使用Docker安装RStudio服务并实现任意浏览器远程访问

文章目录 前言1. 安装RStudio Server2. 本地访问3. Linux 安装cpolar4. 配置RStudio server公网访问地址5. 公网远程访问RStudio6. 固定RStudio公网地址 前言 RStudio Server 使你能够在 Linux 服务器上运行你所熟悉和喜爱的 RStudio IDE,并通过 Web 浏览器进行访问…

OpenCV图像滤波(8)getGaborKernel()函数的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 该函数返回 Gabor 滤波器系数。 Gabor 滤波器在图像处理中非常有用,特别是在纹理分析、特征提取和边缘检测等领域。 函数原型 Mat c…

借助树状数组的思想实现cuda版前缀和

昨天面试快手,面试官出了一个cuda编程题–实现前缀和。当时没有做出来,一直在思考是否有类似于规约树这样的解法,感觉好难……面试结束后搜了一下cuda前缀和的介绍,发现该问题是一个经典的cuda编程问题,NVIDIA很早之前…

论文解读,神经网络全梯度表示《Full-Gradient Representation for Neural Network Visualization》

导语 这篇论文介绍了一种新的工具,称为全梯度,用于解释神经网络的响应。这个全梯度的概念将神经网络的响应分解为两个部分:输入灵敏度和每个神经元的灵敏度分量。 输入灵敏度:输入灵敏度指的是对于神经网络输出的影响程度。它反…

node中使用http创建web服务器

1.案例代码 // 1.导入http模块 const http require(http)// 2.创建web服务器实例 const server http.createServer()// 3.为服务器实例绑定request事件,监听客户的请求 server.on(request,function(req,res){console.log(欢迎来到服务器);// req.url是客户端请求…

【Material-UI】Checkbox组件:标签使用详解

文章目录 一、Checkbox 组件与标签概述1. 组件介绍2. 基本用法 二、Checkbox 标签的关键特性1. 标签与复选框的结合2. 必填项3. 禁用状态4. 带有图标的复选框5. 多行标签 三、Checkbox 标签的实际应用场景1. 表单选择项2. 设置选项3. 同意条款 四、注意事项1. 无障碍支持2. 样式…

windows环境编译ffmpeg +visual studio 2022

最近在配置ffmpeg环境,记录一下坑点。 系统环境 visual stdio 2022 安装c桌面开发人员版 大概8g 实际下载2g左右,配置齐全其余不选。 然后环境配置,这里我使用别人的图,路劲都差不多。找到VS即可 PATH配置: 编译 …

Spring 三级缓存解决循环依赖源码分析

什么是循环依赖? ServiceA依赖ServiceB,ServiceB依赖ServiceA。 启动Spring项目时,如果想实例化ServiceA,创建完ServiceA对象后,需要依赖注入ServiceB的对象,而ServiceB实例化时,需要ServiceA&…

大模型场景应用全集:持续更新中

一、应用场景 1.办公场景 智能办公:文案生成(协助构建大纲优化表达内容生成)、PPT美化(自动排版演讲备注生成PPT)、数据分析(生成公式数据处理表格生成)。 智能会议:会议策划&…

C++之 bind 绑定器深入学习:从入门到精通!

简介 本文详细阐述了 C 中关于 bind 绑定器技术的基本概念和常用技巧。 引入动机 在标准算法库中&#xff0c;有一个算法叫 remove_if&#xff0c;其基本使用如下&#xff1a; #include <iostream> #include <string> #include <algorithm> #include &l…

FANUC发那科模块 A03B-0823-C003 I/0 EXT

IO模块接线 在FANUC系统中IO模块的种类比较多&#xff0c;每种IO模块的使用场合也不相同&#xff0c;每种IO模块的接线脚位也有很大区别&#xff0c;对于电气设计人员来说&#xff0c;清楚知道常用IO模块的接线脚位&#xff0c;才能更好的规划地址、设计图纸&#xff0c;对于设…

MySQL多表

表关系 1.一对多 应用场景 班级和学生 部门和员工 建表原则 设置&#xff08;ForeginKey&#xff09;外键连接 一个表的外键即为另外一张表的主键,以此简历两张表的关系 因此需要再学生表中新增一列&#xff0c;命名为 班级表_id&#xff0c;即班级表的主键&#xff0c;又叫…

【力扣】572.另一棵树的子树

题目描述 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看…