简单易懂又非常牛逼的Spring源码解析,推断构造与bean的实例化
简单易懂又非常牛逼的Spring源码解析,推断构造与bean的实例化
- 原理解析
- 实例化bean的入口
- 工厂方法实例化
- 推断构造
- 初次筛选
- 二次筛选
- bean的实例化
- 代码走读
- 实例化bean的入口
- createBeanInstance方法内部的流程
- 推断构造
- 初次筛选
- 二次筛选
- bean的实例化
- 总结
往期文章:
- 人人都能看懂的Spring底层原理,看完绝对不会懵逼
- 简单易懂的Spring扩展点详细解析,看不懂你来打我
- 人人都能看懂的Spring源码解析,配置解析与BeanDefinition加载注册
- 简单易懂又非常牛逼的Spring源码解析,ConfigurationClassPostProcessor的具体逻辑
上一篇文章讲到了ConfigurationClassPostProcessor的具体逻辑,至此所有的配置信息已经被解析成BeanDefinition,注册到容器中了。
下一步就是根据BeanDefinition进行非懒加载的单例bean的实例化。因此,本篇文件就要介绍非懒加载单例bean的实例化过程。
原理解析
实例化bean的入口
要研究bean的实例化,首先就要找到bean的实例化的入口。

- Spring首先会进行配置文件或者配置类的解析,然后生成BeanDefinition并注册到容器中
- 待所有的BeanDefinition都注册完毕后,会遍历每一个BeanDefinition,判断是否是非抽象、单例、非懒加载,如果是,则调用getBean(beanName)方法进行bean的预加载
- 然后会尝试从容器中获取,看是否已经初始化完成,如果没有,则进行bean的实例化
- 而bean的实例化的入口,就是createBeanInstance方法
这个createBeanInstance方法,就是我们这篇文件研究的主体。
工厂方法实例化
bean的实例化有两种方式,一种是工厂方法实例化,一种是构造器实例化。
createBeanInstance方法,首先会检查BeanDefinition中的factoryMethodName属性是否不为空,如果不为空,就会使用工厂方法进行实例化。
我们上一篇文章分析的@Bean注解修饰的方法,生成的BeanDefinition就带有factoryMethodName属性。

如果BeanDefinition中的factoryMethodName属性为空,就要通过构造器进行实例化。
推断构造
构造器实例化,先要进行构造器的挑选。
如果要通过构造器进行实例化的话,就要先挑选一个合适的构造器,因为一个类里面构造器可能有多个。

推断构造:
- 第一步初次筛选,返回一个构造器数组
- 初次筛选返回的构造器数组不为空,或者自动注入模型为构造器注入,或者我们指定了构造器参数,则进行二次筛选,挑出一个最合适的
- 否则走最简单的逻辑,使用无参构造器进行实例化

初次筛选
初次筛选的条件如下,符合条件的构造器,会放到一个数组里面返回。

二次筛选
二次筛选就是从初次筛选的结果中,再筛出一个最合适的。
这里主要就是筛选出要使用的构造器,以及构造器要使用的参数,筛选出的构造器会保存到constructorToUse变量,而构造器要使用的参数则保存到argsToUse变量。

上面这张图就是推断构造二次筛选的流程,比较复杂,这里看不懂的可以忽略,非重点。
- 首先会判断我们是否手动传入了参数,如果是,则使用我们手动传的参数
- 如果我们没有手动传参,则看BeanDefinition中是否缓存了曾经筛选出的构造器和参数,如果是,则使用缓存的
- 如果BeanDefinition没有缓存,看是否只有一个无参构造器,如果是,则使用该构造器,并缓存到BeanDefinition中
- 如果有多个构造器,先确定最少需要的参数个数minNrOfArgs(取值情况看上图)
- 然后对构造器进行排序(排序条件看上图)
- 遍历构造器数组
- 如果已经挑选出最合适的构造器,当前遍历到的构造器需要的参数又更少,则break跳出循环
- 如果当前遍历到的构造器需要的参数个数,少于minNrOfArgs,continue跳过该构造器
- 没有break,也没有continue,那么就要获取当前构造器需要的参数(如何获取看上图)
- 然后计算取得的参数个数和 构造器所需参数间的差值
- 跟之前选出的构造器进行比较,保留差值更小的构造器
- 遍历结束
- 如果不是主动传参的情况,则把筛选结果缓存到BeanDefinition
bean的实例化
经过上面的步骤,现在已经挑选出合适的构造器,或者使用无参构造器。接下来就是要通过构造器,进行反射实例化。

Spring通过InstantiationStrategy(实例化策略),进行bean实例化的。
也就是说,不是直接通过构造器进行反射实例化的,而是再包了一层,提供了一定的可扩展性。我们可以实现自己的InstantiationStrategy,在bean实例化的时候做一些特殊处理。
如果我们没有做特殊处理,那么Spring就会使用SimpleInstantiationStrategy(简单实例化策略),里面才是通过构造器进行反射实例化。
然后会把实例化的bean包装在一个BeanWrapper中返回。
代码走读
上面已经对bean实例化的整个过程介绍完毕,下面是通过代码走读对上面的原理分析进行验证。
实例化bean的入口
首先是在代码中找到bean实例化的入口

验证上面的路径一路跟进去,就到了实例化bean的入口,createBeanInstance方法。

createBeanInstance方法内部的流程
进入createBeanInstance方法内部,可以看到推断构造和实例化的流程。

判断BeanDefinition的factoryMethodName属性是否不为空,如果不为空,则使用工厂方法进行实例化。

如果我们没有主动传参,并且BeanDefinition中的resolvedConstructorOrFactoryMethod为true,resolved变量置为true,表示之前已经处理过,下面不需要再进行推断构造的处理。autowireNecessary变量表示是否需要进行构造器的自动注入,也就是是否是一个有参构造。

之前已经处理过,就直接进行实例化,不需要再次进行构造器的推断。上面是带参构造的实例化,下面是无参构造的实例化。

如果之前没有处理过,就要进行推断构造。determineConstructorsFromBeanPostProcessors方法就是推断构造的初次筛选,返回一个构造器数组。

初次筛选返回构造器数组后,如果构造器数组不为空,或者当前BeanDefinition的自动注入模型是构造器注入,或者我们在配置中指定了构造器参数,或者我们主动传递了参数,只有满足这四个条件中的一个,就会调用autowireConstructor方法,里面就包含了二次筛选的逻辑。

四个条件没一个满足,就走最简单的逻辑,使用无参构造器进行实例化。

推断构造
再来看一下推断构造的代码。
初次筛选
determineConstructorsFromBeanPostProcessors方法,就是初次筛选的逻辑。

推断构造的初次筛选,是通过bean后置处理器进行的。如果我们打断点的话,会发现进入到AutowiredAnnotationBeanPostProcessor里面。

双重检测锁,检测缓存中有没有。

缓存中没有,就继续往下走。先取出所有构造器。

声明四个变量:
- candidates,候选构造器集合,除无参构造外,遍历到的构造器,只要不报错,都会往里面放
- requiredConstructor,@Autowired或@Autowired(required = true)注解修饰的构造器,@Autowired注解的required属性默认为true
- defaultConstructor,无参构造器
- primaryConstructor,直接忽略它,非Kotlin代码就是个null

遍历所有的构造器,寻找构造器上的@Autowired注解。

如果构造器带有@Autowired注解,但是之前已经有一个@Autowired修饰的构造器,那么直接报错。

如果构造器上的@Autowired注解的required属性是true(默认就是true),然后又有其他构造器,也直接报错。

都没有报错,代表目前为止只有为一个并且是@Autowired注解修饰的构造器,赋值给requiredConstructor遍历,并且添加到candidates中。

如果构造器没有@Autowired注解,并且是无参构造器,赋值给defaultConstructor。
然后所有构造器都遍历完毕后,进入下面的逻辑。

如果candidates集合不为空,requiredConstructor为空,defaultConstructor不为空,把defaultConstructor添加到candidates集合。也就是没有@Autowired或者@Autowired(required = true)注解修饰的构造器,只有不带@Autowired注解的构造器或者@Autowired(required = false)注解修饰的构造器,那么把无参构造器添加到candidates集合中,作为返回集。

如果只有一个构造器,并且是一个带参构造,则返回这个构造器。
下面两个else if分支必须primaryConstructor不为null才会进去,但是primaryConstructor是不可能不为null的,所以直接忽略。

剩下一个else分支,返回一个空的构造器数组。

二次筛选
autowireConstructor方法里面,包含了二次筛选的逻辑,也就是从构造器数组中,挑出一个最合适的构造器。

然后里面会new一个ConstructorResolver并调用它的autowireConstructor方法。

两个重要变量,constructorToUse(准备使用的构造器)和argsToUse(准备使用的参数)。

如果我们传递了参数,argsToUse赋值为我们传递的参数。

尝试从缓存中获取之前处理过的构造器和参数。

如果缓存中没有,看是否只有一个构造器,并且我们没有传递参数,而且配置中也没有指定构造参数。如果是,那么把无参构造和空参缓存到BeanDefinition中,然后通过无参构造器进行实例化。

minNrOfArgs,最少需要的参数个数。如果我们传递了参数,就是我们传递的参数个数,如果我们没有传递参数,那么看我们是否在配置中指定了构造器参数,如果指定了,那么就是我们指定的构造器参数个数,否则就是0。

对构造器进行排序。

遍历构造器,如果constructorToUse(准备使用的构造器)和argsToUse(准备使用的参数)不为空,而且当前遍历到的构造器参数个数parameterCount少于argsToUse的长度,那么直接break,后面的都不用再遍历了。
如果当前构造器参数个数parameterCount少于minNrOfArgs(最少需要的参数个数),那么continue忽略当前构造器。

获取构造器所需要的参数。

计算取得的参数个数和构造器所需要的参数个数的差值typeDiffWeight,如果差值比之前的还小,就选择当前的构造器,更新constructorToUse和argsToUse,以及minTypeDiffWeight(目前找到的最小参数个数差值)。

如果我们没有传递参数,则把处理结果缓存到BeanDefinition中。然后使用推断出的构造器constructorToUse和构造参数argsToUse进行反射实例化。

bean的实例化
坚持就是胜利,剩下的就是实例化bean的逻辑!

instantiate(beanName, mbd, constructorToUse, argsToUse),使用推断出的构造器constructorToUse和构造参数argsToUse进行反射实例化。

strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse),调用了InstantiationStrategy(实例化策略)的instantiate方法。

调用了BeanUtils工具类的instantiateClass方法。

最终通过构造器反射实例化。

还有一个使用无参构造进行实例化的方法,也是差不多的逻辑。

getInstantiationStrategy().instantiate(mbd, beanName, parent)也是通过InstantiationStrategy(实例化策略)的instantiate方法进行实例化。
然后可以看到包装成一个BeanWrapper返回。

getInstantiationStrategy().instantiate(mbd, beanName, parent)里面,也是通过BeanUtils工具类的instantiateClass方法进行实例化。

总结
以上就是推断构造与bean的实例化源码解析的所有内容,下面做一个总结。
- 推断构造首先会经过第一次筛选,筛出一个构造器数组
- 然后构造器数组不为空,或者当前BeanDefinition指定为构造器注入,或者我们在配置中指定了构造器参数,或者我们getBean的时候传递了参数,那么就会经历第二次筛选,选出一个最合适的构造器以及构造器参数
- 否则就会使用无参构造器进行实例化
- 然后会通过InstantiationStrategy(实例化策略)进行实例化,里面调用BeanUtils工具类,最终通过构造器进行反射实例化
相关文章:
简单易懂又非常牛逼的Spring源码解析,推断构造与bean的实例化
简单易懂又非常牛逼的Spring源码解析,推断构造与bean的实例化原理解析实例化bean的入口工厂方法实例化推断构造初次筛选二次筛选bean的实例化代码走读实例化bean的入口createBeanInstance方法内部的流程推断构造初次筛选二次筛选bean的实例化总结往期文章࿱…...
Win11的两个实用技巧系列清理磁盘碎片、设置系统还原点的方法
Win11如何清理磁盘碎片?Win11清理磁盘碎片的方法磁盘碎片过多,会影响电脑的运行速度,所以需要定期清理,这篇文章将以Win11为例,给大家分享的整理磁盘碎片方法相信很多用户都会发现,随着电脑使用时间的增加,…...
嵌入式 STM32 红外遥控
目录 红外遥控 NEC码的位定义 硬件设计 软件设计 源码程序 红外遥控 红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,容易实现等显著的特点,被诸多电子设备特别…...
【java web篇】使用JDBC操作数据库
📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言Ὅ…...
华为OD机试题,用 Java 解【最小步骤数】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...
JAVA中 throw 和 throws 的区别含案例
JAVA中 throw 和 throws 的区别含案例 在 Java 中,throw 和 throws 是两个关键字,它们用于处理异常。 throw 关键字用于抛出一个异常对象。一旦抛出异常,程序将停止执行当前方法的剩余代码,并尝试寻找与该异常匹配的 catch 块来…...
基于SpringCloud的可靠消息最终一致性05:保存并发送事务消息
在有了分布式事务的解决方案、项目的需求、骨架代码和基础代码,做好了所有的准备工作之后,接下来就可以继续深入了解「核心业务」了。 在前面了解分布式事务时,可靠消息最终一致性方案的流程图是这样的: 图三十一:可靠消息最终一致性 整个的流程是: 1、业务处理服务在事务…...
SQL语句大全(详解)
SQL前言1 DDL1.1 显示所包含的数据库1.2 创建数据库1.3 删除数据库1.4 使用数据库1.4.1 创建表1.4.2 查看表的结构1.4.3 查看当前数据库下的所有表1.4.4 基础的增删改查1.4.4.1 删除表1.4.4.2 添加列1.4.4.3 修改表名1.4.4.4 修改数据类型1.4.4.5 修改列名和数据类型2 DML2.1 给…...
视频营销活动中7个常见的错误
如今,越来越多的企业在社交媒体平台上开展视频营销活动。与其他传统营销策略不同,视频营销可以为企业带来更多的销售机会。随着越来越多的视频社交媒体平台的出现,营销人员更应该抓住这个机会。但在开始视频创作之前,您需要有一个…...
MapReduce小试牛刀
部署完hadoop单机版后,试下mapreduce是怎么分析处理数据的 Word Count Word Count 就是"词语统计",这是 MapReduce 工作程序中最经典的一种。它的主要任务是对一个文本文件中的词语作归纳统计,统计出每个出现过的词语一共出现的次…...
2023年全国最新工会考试精选真题及答案7
百分百题库提供工会考试试题、工会考试预测题、工会考试真题、工会证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 21.会员大会或会员代表大会与职工代表大会或职工大会须分别行使职权,()…...
13-mvc框架原理与实现方式
1、mvc原理 # mvc 与框架## 1.mvc 是什么1. m:model,模型(即数据来源),主要是针对数据库操作 2. v:view,视图,html 页面。视图由一个一个模板构成(模板是视图的一个具体展现或载体,视图是模板的一个抽象) 3. c:controller,控制器,用于mv之间的数据交互## 2.最简单的 mvc 就是一…...
弹性盒子布局
目录一、弹性盒子属性二、认识flex的坐标轴三、简单学习父级盒子属性三、属性说明3.1、flex-grow一、弹性盒子属性 说明: div的默认样式:display:block 块盒子 display:flex弹性盒子(可以控制下级盒子的位置) 当两种盒子单独出现…...
C# Sqlite数据库加密
sqlite官方的数据库加密是收费的,而且比较贵。 幸亏微软提供了一种免费的方法。 1 sqlite加密demo 这里我做了一个小的demo演示如下: 在界面中拖入数据库名、密码、以及保存的路径 比如我选择保存路径桌面的sqlite目录,数据库名guigutool…...
高压放大器在声波谐振电小天线收发测试系统中的应用
实验名称:高压放大器在声波谐振电小天线收发测试系统中的应用研究方向:信号传输测试目的:声波谐振电小天线颠覆了传统电小天线以电磁波谐振作为理论基础的天线发射和接收模式,它借助声波谐振实现电磁信号的辐射或接收。因为同频的…...
锁相环的组成和原理及应用
一.锁相环的基本组成 许多电子设备要正常工作,通常需要外部的输入信号与内部的振荡信号同步,利用锁相环路就可以实现这个目的。 锁相环路是一种反馈控制电路,简称锁相环(PLL)。锁相环的特点是:利用外部输入的参考信号控制环路内…...
[C++]string类模拟实现
目录 前言: 1. string框架构造 2. 默认函数 2.1 构造函数 2.2 析构函数 2.3 拷贝构造 2.4 赋值重载 3. 迭代器 4. 整体程序 前言: 本篇文章模拟实现了C中string的部分功能,有助于大家了解和熟悉string类,虽然这个类不难实…...
一个更适合Java初学者的轻量级开发工具:BlueJ
Java是世界上最流行的编程语言之一,它被广泛用于从Web开发到移动应用的各种应用程序。大部分Java工程师主要是用IDEA、Eclipse为主,这两个开发工具由于有强大的能力,所以复杂度上就更高一些。如果您刚刚开始使用Java,或者您更适合…...
从程序员到项目组长,要经历六重修炼
最近和粉丝朋友们交流时发现,有很多刚刚开始做项目组长的朋友自我认可度非常低,感觉做组长之后天天打杂,技术也荒废了。领导天天找你要成果,下属天天找你说困难,你在中间受领导和下属的夹板气。时间久了,你…...
我的 System Verilog 学习记录(5)
、 引言 本文简单介绍 System Verilog 语言的 控制流。 前文链接: 我的 System Verilog 学习记录(1) 我的 System Verilog 学习记录(2) 我的 System Verilog 学习记录(3) 我的 System Ver…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
数据库——redis
一、Redis 介绍 1. 概述 Redis(Remote Dictionary Server)是一个开源的、高性能的内存键值数据库系统,具有以下核心特点: 内存存储架构:数据主要存储在内存中,提供微秒级的读写响应 多数据结构支持&…...
【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...
js 设置3秒后执行
如何在JavaScript中延迟3秒执行操作 在JavaScript中,要设置一个操作在指定延迟后(例如3秒)执行,可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法,它接受两个参数: 要执行的函数&…...
【Ftrace 专栏】Ftrace 参考博文
ftrace、perf、bcc、bpftrace、ply、simple_perf的使用Ftrace 基本用法Linux 利用 ftrace 分析内核调用如何利用ftrace精确跟踪特定进程调度信息使用 ftrace 进行追踪延迟Linux-培训笔记-ftracehttps://www.kernel.org/doc/html/v4.18/trace/events.htmlhttps://blog.csdn.net/…...
