Sprng依赖注入(三):构造方法注入是如何工作的?
前言
这是Spring依赖注入系列的第三篇,前两篇主要分析了Spring bean依赖属性注入的两种方式,是字段注入和setter方法注入,单独比较这两种方式,会发现其过程和工作原理非常类似,那么构造方法注入会不会也和前两种比较类似呢?本篇文章将会揭晓答案。
构造方法注入方法
以@Autowired注解为例,即把@Autowired注解标记在目标bean的构造方法上,而构造方法的入参数是引用bean类型;
构造方法注入示例
示例主要内容:1、定义Teachert类; 2、定义Student类;3、在Student类中依赖Teacher;4、使用@Autowired注解标记在Student(Teacher teacher)上,即在Student对象中以构造方法注入的方式注入Teacher对象;
@Slf4j
@Component
public class Student {private String name="小明";private Teacher teacher;public Student() {log.info("----student的无参数构造方法被执行");}@Autowiredpublic Student(Teacher teacher) {this.teacher = teacher;log.info("----student的有参数构造方法(teacher)被执行");}public Student(String name, Teacher teacher) {this.name = name;this.teacher = teacher;log.info("----student的有参数构造方法(name,teacher)被执行");}public String getName() {return name;}public void setName(String name) {this.name = name;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;log.info("----student中的setTeacher方法被调用");}}@Slf4j
@Component
public class Teacher {private String name="李老师";private Student student;public Teacher() {log.info("----teacher的无参数构造方法被执行");}public Teacher(Student student) {this.student = student;log.info("----teacher的有参数构造方法被执行");}public String getName() {return name;}public void setName(String name) {this.name = name;}public Student getStudent() {return student;}public void setStudent(Student student) {log.info("----teacher中的setStudent方法被调用");this.student = student;}
}@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("student")) {Student student = (Student) bean;log.info("----student属性注入完成,student.name:" + student.getName());}return bean;}
}@Component
@Slf4j
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {if (beanName.equals("student")||beanName.equals("teacher")) {log.info("----bean实例化完成,beanName:" + beanName);}return true;}
} @Testpublic void test4() {log.info("----单元测试执行开始");AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");Student bean = context.getBean(Student.class);log.info("----单元测试执行完毕");}单元测试执行日志

从单元调试执行日志结果来看,teacher先被实例化,然后再调用student的有参数构造方法进行student对象的实例化,同时把teacher对象注入到了student对象里,这和字段注入、setter方法注入的过程明显不同,字段注入、setter方法注入都是student先实例化,然后实例化teacher对象,再把teacher对象注入到student对象里。这是一个非常明显的区别,要加深印象。
构造方法注入的工作原理
这里从工作原理上,深入分析一下构造方法注入和字段注入、setter方法注入的过程的区别在哪里?
1、通过之前的分析,已经知道bean实例化时机是在AbstractAutowireCapableBeanFactory#doCreateBean--->createBeanInstance(),那么先执行createBeanInstance()并观察其返回值,发现student对象实例化好的同时teacher对象也注入到student对象内,这和单元测试的执行日志结果是一致的;

2、那就直接着进入到AbstractAutowireCapableBeanFactory#createBeanInstance(),方法内第一行resolveBeanClass(mbd, beanName),从名字看意思是解析出bean的Class实例对象,接着往下走调用determineConstructorsFromBeanPostProcessors(beanClass, beanName),又引起了我的注意,看过我的这篇文章的小伙伴Springboot扩展点之SmartInstantiationAwareBeanPostProcessor,一定对determineCandidateConstructors()不陌生,这个方法可以决定使用哪个构造器构造 bean,执行结果就是student的有参数构造方法Student(Teacher teacher);再来看下面的autowireConstructor(beanName, mbd, ctors, args)调用,从名字就能看出来是根据构造器自动装配bean;

3、那还等什么,进入AbstractAutowireCapableBeanFactory#autowireConstructor(),里面太简单了,new一个构造方法解析器对象(ConstructorResolver),调用ConstructorResolver#autowireConstructor();
4、ConstructorResolver#autowireConstructor()内的解析过程比较复杂,各种的判断,让人眼花缭乱,但没关系,谁让我是一机灵鬼呢,我发现第一句就是BeanWrapperImpl bw = new BeanWrapperImpl(),这太熟悉了,Spring容器刚实例化好的bean都包装成一个BeanWrapper类型对象,我只需要牢牢看好这个对象,肯定能找到在什么时候注入student对象。后面果然让我抓到了,getUserDeclaredConstructor(candidate)拿到了有参数的构造方法对象,然后调用createArgumentArray(),入参数有bean的Class对象实例、bean构造方法形参名称,这个方法十有八九有我想要的结果;

5、进入到ConstructorResolver#createArgumentArray(),里面又是很多的判断,判断完之后又调用了ConstructorResolver#resolveAutowiredArgument(); 这一块判断比较多,一定要耐心;
这个方法要加印象,一会记得看一下返回值(在第10步);

6、进入到ConstructorResolver#resolveAutowiredArgument(),眼前一亮发现了 this.beanFactory.resolveDependency(...),而这里的this.beanFactory是DefaultListableBeanFactory,有看过Spring依赖注入(一):字段注入的方式是如何工作的?Sprng依赖注入(二):setter注入是如何工作的?的一对这个方法也不会陌生,从名字也能看出来是bean的依赖解析;

7、进入到DefaultListableBeanFactory#resolveDependency(),又调用了doResolveDependency();

8、doResolveDependency()中,又调用了findAutowireCandidates(beanName, type, descriptor)查找Student类依赖属性Teacher类的Class对象;

9、doResolveDependency()中,继续向下会调用descriptor.resolveCandidate(...),而这个方法就是根据上一步得到Teacher类的Class对象实例化Teacher;进入到descriptor.resolveCandidate(...),就一句beanFactory.getBean(beanName),是不是熟悉的配方熟悉的味道?

10、跳过teacher对象的创建过程,继续往下,直到ConstructorResolver#createArgumentArray()执行完,返回值是teacher对象,还记得开始是准备实例化student,但是student还没实例化,其依赖的引用属性teacher已经完成了实例化了;

, java.lang.Object[])实例化Student,拿到student对象再包装进BeanWrapperImpl对象,这里着重看一下instantiate()的入参数是:构造方法对象、teacher对象;

12、至此teacher、student对象都已经实例化了,并且student对象的依赖属性teacher也注入了;继续往下走,直到AbstractAutowireCapableBeanFactory#doCreateBean--->createBeanInstance()执行完;还记得在Spring依赖注入(一):字段注入的方式是如何工作的?是如何分析的吗?如下:
Spring中bean创建核心逻辑都在AbstractAutowireCapableBeanFactory#doCreateBean()中,这个方法主要干了这几件事:第一,bean的实例化;第二,bean的后置处理方法执行;第三,把实例化完成、未完成属性注入的bean提前暴露到Spring的第三级缓存中去;第四,bean依赖属性注入;第五,bean的初始化;student对象在完成这几步后,就可以作为Spring容器中正式的bean开始被使用了。
但是事实上,createBeanInstance()执行完的时候,依赖属性注入已经完成了,第三步注入到Spring第三级缓存里的bean已经是一个完成属性注入的bean了,第四步也不会再执行注入操作了;
构造方法注入总结
Spring bean使用构造方法注入依赖属性的注入时机,要早于字段注入和、setter方法注入,而且注入到Spring第三级缓存里的bean已经是一个完成依赖属性注入的bean。得到这个结论,后面再来分析Spring的bean的循环依赖为什么有的是可以解决的?有的是解决不了的?其实答案已经明显了,下面一篇文章将重点分析。
相关文章:
Sprng依赖注入(三):构造方法注入是如何工作的?
前言这是Spring依赖注入系列的第三篇,前两篇主要分析了Spring bean依赖属性注入的两种方式,是字段注入和setter方法注入,单独比较这两种方式,会发现其过程和工作原理非常类似,那么构造方法注入会不会也和前两种比较类似…...
「1」指针进阶——详解
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀 目录 🐰指针的回顾 🐰字符指针 🐰指针数组 🌸模…...
JS语法让人困惑的点 “==与===”
在JS中有很多神奇的语法,非常让人困惑,我们就先一一道来,相信你在开发中或多或少都踩过这些坑,或者让人无法理解。 今天我们就来说下【】和【】 这题对于很多没有系统学过前端开发的技术人员来说,算个重点,…...
《狂飙》壁纸大嫂如此惊艳,做成日历壁纸天天看
兄弟们,今年的反腐大剧狂飙都有看吗 ? 话说,名字虽然叫狂飙,但是全剧只有有田一个人在狂飙! 当然,有田虽然亮眼,但是毕竟是个糟老头子,正经人谁看有田啊,当然是看大嫂了…...
手机照片删除了怎么恢复
手机照片删除了怎么恢复?喜欢拍照的小伙伴,都会不定期删除手机上的照片,因为这些爱拍照的人,手机中会存储着很多照片,删除照片是必然的,但在手机删除照片时,如果是一张一张删除太麻烦了,就直接…...
maven pom.xml 依赖的scope属性
maven pom.xml 依赖的scope属性 compile 适用范围 编译期、测试期、运行期 作用 从中央仓库拉取依赖到本地,并编译 打包到结果包中 runtime 适用范围 测试期、运行期 作用 runtime 用在 Class.forName(“com.mysql.jdbc.Driver”) 时,compile 编…...
git 的使用方法 (下 - 远程仓库和图形化)
目录前言:一、什么是协同开发二、Gitee 使用协同开发1. 首先注册一个码云账号2. 新建一个仓库3. 根据下图把新建仓库设置为开源4. 在远端合并分支的方法5. 链接 git 远程6. 提交(同步)远程7. 远程拉取至本地8. 远程分支三、git 图形化的使用1…...
Java基础:拼图小游戏
涉及到的知识: 1.图形用户接口GUI(Graphical User Interface)用图形化的方式显示操作界面 两个体系: AWT包和Swing包 2.界面会用到JFrame类 3.界面中的菜单会用到JMenuBar, JMenu, JMenuItem 4.添加图片 在设置完JLabel的location之后还需要获得展示内容的窗体, 通过setLay…...
一个跟蘑菇结缘的企业老板
记得那是一个很久以前的一家公司了董事长办公室里中的大型盆栽里面长了一个蘑菇董事长认为是祥瑞每天都会浇水后来一个新来的保洁阿姨以为杂草啥的给他掰掉扔垃圾桶了董事长第二天来浇水的时候发现没了就问谁动了他的蘑菇问道之后就跑到楼道大垃圾桶那里把蘑菇找回来种在花盆里…...
【Leetcode 剑指Offer】第 4 天 查找算法(简单)
查找剑指 Offer 03. 数组中重复的数字剑指 Offer 53 - I. 在排序数组中查找数字 I二分法题目链接剑指 Offer 03. 数组中重复的数字 题:在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数…...
Jenkins利用docker部署vue项目
Jenkins利用docker部署vue项目一、环境准备1、安装docker2、安装nodejs3、安装cnpm与配置淘宝镜像4、jenkins安装nodejs插件二、jenkins以vue项目1、全局参数配置2、源码配置3、构建环境4、构建三、构建项目四、访问一、环境准备 本次jenkins与部署vue项目在同一台机器&#x…...
【Linux】如何将ntfs硬盘挂载到home目录下并具有读写权限
步骤1. 查看当前挂载的硬盘及其挂载点2. 查看需要挂载到home下的磁盘类型信息3. 在home下新建一个空的文件夹作为该磁盘的新挂载点4. 以ntfs类型的硬盘为例,使用mount命令进行挂载5. 问题1:进程占用了磁盘6. 问题2:磁盘权限为只读的7. 永久挂…...
拖拽删除元素、拖拽排序、拖拽预览图片和拖拽移动元素
介绍 HTML5 提供了专门的拖拽与拖放的 API,目前各浏览器都已支持,包括 IE。HTML 拖放(Drag and Drop)接口使应用程序能够在浏览器中使用拖放功能。例如,用户可使用鼠标选择可拖拽(draggable)元素…...
yarn的global安装命令不生效
问题 yarn全局安装某个依赖完成之后,但依赖没有生效,一般有两种情况导致的。 解决思路 1.yarn命令问题 yarn在全局安装某个依赖时,global要紧接在yarn之后,然后才是add yarn global add xxxx如果出现global在add之后ÿ…...
如何发布自己的npm包?
1 文件组成 package.json文件components文件css样式文件index.js文件 2 package.json配置 description:描述title:题目keywords:搜索关键词typings:指定TypeScript的入口文件main:加载的入口文件module:…...
达梦数据库 闪回查询
当用户操作不慎导致错误的删改数据时,非常希望有一种简单快捷的方式可以恢复数据。闪回技术,就是为了用户可以迅速处理这种数据逻辑损坏的情况而产生的。 闪回技术主要是通过回滚段存储的 UNDO 记录来完成历史记录的还原。如果提交了,还没有…...
java基础学习 day44(多态的优点和劣势)
1. 多态的优势 在多态形式下,右边对象可以实现解耦合(即之后的代码与右边的子类对象不绑定,在更改子类对象后,之后的代码仍可以使用),便于扩展和维护在定义方法的时候,使用父类型作为参数&…...
Guna UI WinForms 2.0.4.4 Crack
Guna.UI2 WinForms is the suite for creating groundbreaking desktop app UI. It is for developers targeting the .NET Windows Forms platform. 50 多个 UI 控件 具有广泛功能的综合组件可帮助您开发任何东西。 无尽的定制 只需拖放即可创建视觉效果命令和体验。 出色的…...
零售航母沃尔玛公布业绩:喜忧参半
2月21日美股盘前,零售巨无霸沃尔玛公布了截至1月的2023财年第四季度业绩报告。财报中不乏可圈可点之处,但是利润迎来六年首降,新财年的利润指引要也比预期低很多,可以说喜忧参半。 一、Q4业绩可圈可点 营收方面:在本…...
Python学习笔记丨while、for、if循环结构基础知识与易错点
Python流程控制 本篇笔记的主要内容是:条件控制和循环控制,包括if语句、while语句、for语句等。 Python条件控制 if (m : 1) > 0: # :是海象运算符,用于在函数内部为变量赋值 print("ok")ok 通过if语句来判断条件是否成立&am…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
