当前位置: 首页 > news >正文

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

我们接着上一节已经准备开始分析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。

相关文章:

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

我们接着上一节已经准备开始分析AbstractAutowireCapableBeanFactory#doCreateBean方法,该方法是spring真正开始创建bean实例并初始化bean的入口方法,属于核心逻辑,所以我们新开一节开始分析。 图12 图12-530到536行 这几行的主要就是创建b…...

如果我的发明有修改,需要如何处理?

如果我的发明有修改,需要如何处理?...

java:File与MultipartFile互转

1 概述 当我们在处理文件上传的功能时,通常会使用MultipartFile对象来表示上传的文件数据。然而,有时候我们可能已经有了一个File对象,而不是MultipartFile对象,需要将File对象转换为MultipartFile对象进行进一步处理。 在Java中…...

高级java每日一道面试题-2024年8月04日-web篇-如果客户端禁止cookie能实现session还能用吗?

如果有遗漏,评论区告诉我进行补充 面试官: 如果客户端禁止cookie能实现session还能用吗? 我回答: 当客户端禁用了Cookie时,传统的基于Cookie的Session机制会受到影响,因为Session ID通常是通过Cookie在客户端和服务器之间传递的。然而,尽…...

leetcode 107.二叉树的层序遍||

1.题目要求: 给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)2.此题步骤: 1.先创建好队列,出队和入队函数: //创建队列 typedef struct que…...

C++在网络安全领域的应用

前言: 在当今的数字化时代,网络安全已经成为一个至关重要的领域。随着网络威胁和攻击手段的不断演变,开发高效、安全的系统和工具变得尤为重要。C作为一种功能强大且高性能的编程语言,在网络安全领域发挥着不可替代的作用。本文简…...

Chapter 26 Python魔术方法

欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能! 文章目录 前言一、什么是魔术方法?二、常见的魔术方法① __init__构造方法② __str__字符串方法③ __lt__比较方法④ __le__比较方法⑤ __eq__比较方法 前言 本章将详细讲…...

基于Transformer的语音识别与音频分类

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…...

leetcode数论(1362. 最接近的因数)

前言 经过前期的基础训练以及部分实战练习,粗略掌握了各种题型的解题思路。现阶段开始专项练习。 数论包含最大公约数(>2个数)、最大公约数性质、最小公倍数、区间范围质因素计数(最下间隔)、质因素分解、判断质数、平方根、立方根、互质、同余等等。 描述 给…...

sqli-labs-master less1-less6

目录 通关前必看 1、判断是否存在sql注入以及是字符型还是数值型: 2、各种注入方式以及方法 有回显型: 报错注入(只有ok和no的提示以及报错提示): 详细思路,后面的题都可以这样去思考 关卡实操 less…...

力扣287【寻找重复数】

给定一个包含 n 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。 你设计的解决方案必须 不修改 数组 nums 且只用常…...

【2024蓝桥杯/C++/B组/传送阵】

题目 问题代码 #include<bits/stdc.h> using namespace std;const int N 1e610; int n; int porter[N]; int ans; int sign[N]; bool used;void dfs(int now, int cnt) {if(sign[now] && used){ans max(ans, cnt);return;}if(!sign[now]){cnt, sign[now] 1; …...

(四十一)大数据实战——spark的yarn模式生产环境部署

前言 Spark 是一个开源的分布式计算系统。它提供了高效的数据处理能力&#xff0c;支持复杂的数据分析和处理任务&#xff0c;是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。Spark Core&#xff1a;实现了Spark的基本功能&#xff0c;包含任务调度、内存管理、错误…...

【深度学习实战(53)】classification_report()

classification_report()是python在机器学习中常用的输出模型评估报告的方法。 classification_report()函数介绍 classification_report()语法如下&#xff1a;classification_report(          y_true,          y_pred,          labelsNone,  …...

计算机网络基础之网络套接字socket编程(初步认识UDP、TCP协议)

绪论​ “宿命论是那些缺乏意志力的弱者的借口。 ——罗曼&#xff0e;罗兰”&#xff0c;本章是为应用层打基础&#xff0c;因为在写应用层时将直接通过文本和代码的形式来更加可视化的理解网络&#xff0c;本章主要写的是如何使用网络套接字和udp、tcp初步认识。 话不多说安…...

手撕Python!模块、包、库,傻傻分不清?一分钟带你弄明白!

哈喽&#xff0c;各位小伙伴们&#xff01;今天咱们来聊聊Python中的模块、包和库&#xff0c;很多新手小白经常搞混&#xff0c;别担心&#xff0c;看完这篇&#xff0c;保证你一分钟就能搞定&#xff01; 打个比方&#xff1a; 模块 (Module): 就好比是一块块乐高积木&#…...

Linux--序列化与反序列化

序列化 序列化是指将数据结构或对象状态转换成可以存储或传输的格式的过程。在序列化过程中&#xff0c;对象的状态信息被转换为可以保持或传输的格式&#xff08;如二进制、XML、JSON等&#xff09;。序列化后的数据可以被写入到文件、数据库、内存缓冲区中&#xff0c;或者通…...

使用C#和 aspose.total 实现替换pdf中的文字(外语:捷克语言的pdf),并生成新的pdf导出到指定路径

程序主入口&#xff1a; Program.cs &#xfeff;using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks;namespace PdfEditor {public class Progra…...

【Material-UI】Autocomplete中的高亮功能(Highlights)详解

文章目录 一、简介二、实现高亮功能示例代码代码解释 三、实际应用场景1. 搜索功能2. 表单自动完成 四、总结 在现代Web开发中&#xff0c;提供清晰的用户反馈是提升用户体验的重要组成部分。Material-UI的Autocomplete组件通过高亮功能&#xff0c;帮助用户快速识别搜索结果中…...

Android 11(R)启动流程 初版

启动流程 bootloader会去启动android第一个进程Idle&#xff0c;pid为0&#xff0c;会对进程 内存管理等进行初始化。Idle还被称作swapper。Idle会去创建两个进程&#xff0c;一个是init&#xff0c;另外一个是kthread。 kthread会去启动内核&#xff0c;用户是由init进行启动。…...

从零安装pytorch

背景介绍 目前主流使用的工具有Facebook搞的pythorch和谷歌开发的tensorflow两种&#xff0c;二者在实现理念上有一定区别&#xff0c;pytorch和人的思维模式与变成习惯更像&#xff0c;而tensorflow则是先构建整体结构&#xff0c;然后整体运行&#xff0c;开发调试过程较为繁…...

2024.07.28 校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、自动驾驶一周资讯 - 特斯拉FSD年底入华&#xff1f;理想成立“端到端”实体组织&#xff1b;小马智行或最快于今年9月赴美IPO 自动驾驶一周资讯 - 特斯拉FSD年底入华&#xff1f;理想…...

python实现小游戏——植物大战僵尸(魔改版本)

制作一款DIY的‘植物大战僵尸’游戏引起了很多人的兴趣。在这里&#xff0c;我将分享一个使用Python语言在PyCharm环境中开发的初始状态版本。这个版本主要应用了pygame库来完成&#xff0c;是一个充满创意和趣味的魔改版本。 文章目录 前言一、开发环境准备二、代码1.main方法…...

基于K210智能人脸识别+车牌识别系统(完整工程资料源码)

运行效果&#xff1a; 基于K210的智能人脸与车牌识别系统工程 目录&#xff1a; 运行效果&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、国内外研究现状与发展趋势 二、相关技术基础 2.1 人脸识别技术 2.2 车牌识别技术 三、智能小区门禁系统设计 3.1 系统设计方案 3.2 …...

8.怎么配嵌套子路由,以及它的作用

作用 配嵌套子路由,就是可以通过同一个页面,让不同的位置发生变化,其他的位置不会发生变化,而做到一个局部刷新 例子 红线框住的部分,头部和导航栏是不会发生变化的,变化的只有中间的内容 子路由的操作步骤 将这个页面的头部和导航栏部分的样式和风格,移到主路由上(<tem…...

【海贼王航海日志:前端技术探索】HTML你学会了吗?(二)

目录 1 -> HTML常见标签 1.1 -> 表格标签 1.1.1 -> 基本使用 1.1.2 -> 合并单元格 1.2 -> 列表标签 1.3 -> 表单标签 1.3.1 -> form标签 1.3.2 -> input标签 1.4 -> label标签 1.5 -> select标签 1.6 -> textarea标签 1.7 -> …...

体系结构论文导读(三十一)(下):Soft errors in DNN accelerators: A comprehensive review

第五部分&#xff1a;DNN加速器中的软错误 本部分回顾和分析了有关人工神经网络&#xff08;ANN&#xff09;可靠性的研究。特别是关注通过DNN加速器解决DNN可靠性的研究&#xff0c;从软错误的角度进行探讨。许多前期工作声称ANN本身对故障具有固有的容错能力。然而&#xff…...

Python在指定文件夹下创建虚拟环境

基于不同python版本和第三方包版本开发的项目&#xff0c;为了方便学习和管理python环境&#xff0c;可以在指定的文件夹里创建项目所需的虚拟环境。具体流程如下&#xff1a; (1) 以管理员身份打开Ananconda Prompt&#xff0c;查看当前虚拟环境&#xff0c;输入命令如下&…...

【SpringBoot】 定时任务之任务执行和调度及使用指南

【SpringBoot】 定时任务之任务执行和调度及使用指南 Spring框架分别通过TaskExecutor和TaskScheduler接口为任务的异步执行和调度提供了抽象。Spring还提供了支持应用程序服务器环境中的线程池或CommonJ委托的那些接口的实现。最终&#xff0c;在公共接口后面使用这些实现&…...

理解 Objective-C 中 +load 方法的执行顺序

在 Objective-C 中&#xff0c;load 方法是在类或分类&#xff08;category&#xff09;被加载到内存时调用的。它的执行顺序非常严格&#xff0c;并且在应用启动过程中可能会导致一些令人困惑的行为。理解 load 方法的执行顺序对调试和控制应用的初始化过程非常重要。 load 方…...