Spring 中 @Bean 注解流程分析

news2025/4/15 20:35:55

代码案例

现在 SpringBoot、SpringCloud 基本上都是通过 @Bean 注解来将组件交给 Spring 管理,所以对 @Bean 的流程应该要有所了解。

这里先定义一个 Blue 的实体类,如下:

public class Blue {
}

然后定义一个入口类,通过 @Bean 注解将 Blue 交给 Spring 管理,如下:

@BeansScanner(basePackage = "com.gwm.scan.beans")
public class TestBean {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
		Blue blue = context.getBean(Blue.class);
		System.out.println("blue = " + blue);

	}

	@Bean
	public Blue blue() {
		System.out.println("======>invoke blue...");
		return new Blue();
	}
}

@Bean 扫描解析 BeanDefinition 阶段先搁一边后面补齐。。。。

@BeansScanner 注解不用太在意,只是一个自己模拟 @ComponentScan 注解写的。这主要分析 @Bean 执行流程。

首先看到 Spring 中的一段源码,如下所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意这里的判断条件 mbd.getFactoryMethodName() != nul 当使用了 @Bean 注解或者 xml 中配置了 <bean factory-method=‘…’> 才会成立。

现在我们已经在 TestBean 类中使用了 @Bean 注解,所以这里当加加载到 TestBean 类的时候,源码中的判断条件就成立。直接进入 instantiateUsingFactoryMethod() 方法内部,如下(直接进入核心代码部分):

在这里插入图片描述

这里 mbd.getFactoryBeanName() 获取到的是 @Bean 注解所在的类的名称,然后再通过 beanFactory.getBean() 拿到这个类的对象,因为最终要通过反射区调用这个 @Bean 修饰的方法,所以肯定是要先获取到这个类的对象,才能够调用 @Bean 修饰的方法。

继续追踪源码看下在哪里反射调用

在这里插入图片描述
在这里插入图片描述

可以看到通过 factoryMethod.invoke(factoryBean,args) 反射调用 @Bean 修饰的方法,然后把 result 结果设置到 BeanWrapper 中。至此 Blue 实例就通过 @Bean 注解交给了 Spring 管理。

加入现在 @Bean 修饰的方法中有参数呢?如下所示:

	@Bean
	public Blue blue(Apple apple3) {
		System.out.println("======>invoke blue..." + apple3);
		return new Blue();
	}

现在 blue() 方法中有一个入参 Apple,那么我们先定义好这个 Apple 类,如下所示:

public class Apple {
}

这里依旧通过 @Bean 将 Apple 交给 Spring 去管理,代码如下:

	@Bean
	public Apple apple2() {
		System.out.println("--apple2....");
		return new Apple();
	}

整体代码如下:

@BeansScanner(basePackage = "com.gwm.scan.beans")
public class TestBean {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
		Blue blue = context.getBean(Blue.class);
		System.out.println("blue = " + blue);

	}
	
	@Bean
	public Blue blue(Apple apple3) {
		System.out.println("======>invoke blue..." + apple3);
		return new Blue();
	}
	
	@Bean
	public Apple apple2() {
		System.out.println("--apple2....");
		return new Apple();
	}
}

Apple 的执行流程和不带参数 Blue 的流程解析一样,不过多赘述。这里看到 blue() 方法中有一个参数 Spring 是如何执行的。直接进入到源码如下:

在这里插入图片描述
在这里插入图片描述

这里获取到 factoryMethod 方法,也就是被 @Bean 修饰的方法。然后调用 createArgumentArray() 方法对参数进行赋值操作。可以看到这里获取到所有的了 paramTypes[]、paramNames[]。并且底层会调用到 getBean() 去实例化参数。就是会给参数赋上值。

在这里插入图片描述

继续跟踪源码,如下:

在这里插入图片描述

可以发现是通过 for 循环对获取到的所有 paramTypes 参数类型逐一赋值。所以如果你参数过多的话,也可能导致性能问题,这个得注意下。我们这里目前设置一个参数方便观察执行流程。

在这里插入图片描述

这里着重记忆下 resolveDependency() 方法,因为后面对于属性填充基本都是借助这个方法,这个方法会触发 getBean() 实例化操作。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

findAutowireCandidates() 中有一个非常重要的逻辑,就是通过类型获取到所有的 beanName,beanNamesForTypeIncludingAncestors() 方法可以获取到父子类容器中满足条件的所有 beanName,这里我们在看下 @Bean 修饰的方法

 @BeansScanner(basePackage = "com.gwm.scan.beans")
public class TestBean {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
		Blue blue = context.getBean(Blue.class);
		System.out.println("blue = " + blue);
	}

	@Bean
	public Blue blue(Apple apple3) {
		System.out.println("======>invoke blue..." + apple3);
		return new Blue();
	}

	@Bean
	public Apple apple2() {
		System.out.println("--apple2....");
		return new Apple();
	}
}

可以清楚的看到我们在 blue() 方法中 Apple 类型的参数名称为 apple3,第一眼看过去很容易让人造成一种误解,就是会认为是根据 beanName = apple3 名称去容器中找到对应的 Apple 实例,但是这个理解有点小小的不足。下下说说这个具体的流程:

1、在 Spring 在扫描封装 BeanDefinition 阶段,会把带 @Bean 修饰的方法,封装成一个个的 BeanDefinition,并且以方法名称作为 beanName,就比如 blue() 和 apple2() 两个方法,beanName 分别是 blue、apple2。
 
2、beanName 确定好,在 Spring 实例化完成之后,容器中就可以根据 beanName 去找到对应的实例 bean,在这里可以根据 beanName = blue 找到 Blue 的实例 bean,根据 beanName = apple2 找到 Apple 的实例化 bean,但是你想根据 beanName = apple3 去找到 Apple 的实例,绝对不可能的,因为容器中就没有 beanName = apple3 的实例 bean
 
3、apple3 被 Spring 被封装成在 DependencyDescriptor 对象中,后面会讲到有什么作用。
 
4、既然根据 beanName = apple3 找不到对应的实例 bean,那么怎么给 blue() 方法中的参数赋值呢?所以 Spring 在这里就利用 beanNamesForTypeIncludingAncestors() 方法, 根据 Apple 类型去 BeanDefinitionMap 中对应的 beanName,在 Spring 在扫描封装 BeanDefinition 阶段,已经将 beanName = apple2 封装到了 BeanDefinitionMap 容器,自然而然可以拿到 apple2 对应的实例。
 
5、所以得出结论,如果只配置了一个类型 bean 的话,@Bean 修饰的方法中属性是通过类型进行注入值的。

那么这里扩展下,如果我们配置了两个或者更多相同类型的 bean?那么在 blue() 方法中 Spring 知道该注入哪个实例 bean ? 如下代码:

@BeansScanner(basePackage = "com.gwm.scan.beans")
public class TestBean {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
		Blue blue = context.getBean(Blue.class);
		System.out.println("blue = " + blue);
	}

	@Bean
	public Blue blue(Apple apple3) {
		System.out.println("======>invoke blue..." + apple3);
		return new Blue();
	}

	@Bean
	public Apple apple1() {
		System.out.println("--apple1....");
		return new Apple();
	}

	@Bean
	public Apple apple2() {
		System.out.println("--apple2....");
		return new Apple();
	}
}

首先这里是绝对会报错的,因为在 blue() 方法中 Spring 根本不知道该如何选择注入哪个 Apple 实例 bean。追踪报错的源码如下:

首先 beanNamesForTypeIncludingAncestors() 方法会获取到 beanName=apple1、beanName=apple2 名称

在这里插入图片描述

然后再进入 determineAutowireCandidate() 方法,这个方法要决策出来一个能够注入的候选 beanName,如果这里面没有选择出来结果,就会报错。那么这段逻辑就显得格外重要了,现在进入内部逻辑。

在这里插入图片描述

进入 determineAutowireCandidate() 逻辑,如下所示:

在这里插入图片描述

determineAutowireCandidate() 方法的内部逻辑简述如下:

1、candidates 就是我们上面通过类型从 BeanDefinitionMap 中找到的 beanNames(apple1、apple2),descriptor 就是我们上面说的 DependencyDescriptor 对象,将 blue() 方法中的入参 apple3 进行包装了。
 
2、determinePrimaryCandidate() 方法会在 apple1、apple2 中查找是否标注了 @Primary 注解,如果找到了,Spring 立即就返回,当做是 blue() 方法中入参的值,其他的就都不管了。
 
3、determineHighestPriorityCandidate() 方法会在 apple1、apple2 中查找是否实现了 Comparator 排序接口,如果找到了,Spring 立即就返回,当做是 blue() 方法中入参的值,其他的就都不管了。
 
4、如果上述两个都没有找到,最后兜底的方法,就是从 DependencyDescriptor 对象中取出之前封装的 beanName=apple3 去和 beanNames(apple1、apple2)匹配,如果匹配到了一个就会立即返回,当做是 blue() 方法中入参的值,其他的就都不管了。

所以从 determineAutowireCandidate() 方法我们可以得出几个结论:

在多个相同类型的 @Bean 同时存在时,Spring 优先找 @Primary 注解,找不到在找是否实现了 Comparator 排序接口,还找不到在用 DependencyDescriptor 入参和 BeanDefinitionMap 中的 beanName 匹配,如果匹配也不成,那么就会抛出 NoUniqueBeanDefinitionException 异常,异常如下:

在这里插入图片描述
在这里插入图片描述

所以在相同类型 @Bean 同时存在时,可以加 @Primary 注解,或指定其中一个具体的 beanName,如下所示:

加 @Primary 注解:

@BeansScanner(basePackage = "com.gwm.scan.beans")
public class TestBean {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
		Blue blue = context.getBean(Blue.class);
		System.out.println("blue = " + blue);
	}

	@Bean
	public Blue blue(Apple apple3) {
		System.out.println("======>invoke blue..." + apple3);
		return new Blue();
	}

	@Bean
	@Primary
	public Apple apple1() {
		System.out.println("--apple1....");
		return new Apple();
	}

	@Bean
	public Apple apple2() {
		System.out.println("--apple2....");
		return new Apple();
	}
}

指定具体名称 apple1、或者 apple2:

@BeansScanner(basePackage = "com.gwm.scan.beans")
public class TestBean {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestBean.class);
		Blue blue = context.getBean(Blue.class);
		System.out.println("blue = " + blue);
	}

	@Bean
	public Blue blue(Apple apple1) {
		System.out.println("======>invoke blue..." + apple3);
		return new Blue();
	}

	@Bean
	public Apple apple1() {
		System.out.println("--apple1....");
		return new Apple();
	}

	@Bean
	public Apple apple2() {
		System.out.println("--apple2....");
		return new Apple();
	}
}

所以最终 @Bean 执行流程主要关注 beanNamesForTypeIncludingAncestors() 和 determineAutowireCandidate() 方法即可。

继续回到主线,如下所示:最终发现会调用到 getBean() 去实例化 Apple

在这里插入图片描述
在这里插入图片描述

到此 @Bean 执行流程和 @Bean 带参数的执行流程就先到这。

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

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

相关文章

rocketMq介绍和安装

rocketMq介绍和安装 Mq介绍 MQ&#xff1a;MessageQueue&#xff0c;消息队列。 队列&#xff0c;是一种FIFO 先进先出的数据结构。消息由生产者发送到MQ进行排队&#xff0c;然后按原来的顺序交由消息的消费者进行处理。 QQ和微信就是典型的MQ。 MQ的作用 主要有以下三个…

天翼物联2项成果成功入选“工信部2022年移动物联网应用典型案例库”

近日&#xff0c;工信部公布了2022年移动物联网应用典型案例征集活动入库案例名单&#xff0c;天翼物联牵头申报的“智慧农业——水肥一体化物联网项目”、“智能表计——抄表机器人物联网项目”成功入选。 本次典型应用案例征集由工业和信息化部组织开展&#xff0c;征集范围包…

产品结构设计的技巧和规则?

说到产品&#xff0c;产品结构设计在当今时代非常重要。它不仅感受到用户的青睐&#xff0c;而且影响销售&#xff0c;因此每个人都非常重视产品结构设计。那你知道产品结构设计的技巧和规则吗? 一、选材方面&#xff1a; 1.耐温等塑料制品的使用环境.耐寒.食品卫生.耐磨等; 2…

resnet(3)------卷积层与激活函数与池化层

文章目录一. 卷积层二. 激活函数1. Sigmoid函数2. tanh函数3. Relu函数三. 池化层一. 卷积层 上一篇文章我们讲到过可以通过控制卷积层的个数来提取图像的不同特征&#xff0c;但是事实上卷积是一种线性运算&#xff0c;更准确的说是一种线性加权运算&#xff0c;而线性运算是…

web课程设计网页规划与设计——惊奇漫画网站

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 ⚽精彩专栏推荐&#x1…

基于蚁群优化算法的直流电机模糊PID控制(Matlab实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

基于jsp+mysql+ssm电影视频预约推荐系统-计算机毕业设计

项目介绍 本基于SSM框架的电影预约推荐系统主要包含了等系统用户管理、影视分类管理、电影预约信息管理、预约信息审核管理多个功能模块,系统采用了jsp的mvc框架,SSM(springMvcspringMybatis)框架进行开发,本系统是独立的运行&#xff0c;不依附于其他系统&#xff0c;可移植&…

MaxSite CMS 代码问题漏洞(CVE-2022-25411)

0x01 漏洞介绍 MaxSite CMS是俄国MaxSite CMS开源项目的一款网站内容管理系统。Maxsite CMS存在代码问题漏洞,该漏洞允许攻击者可利用该漏洞通过精心制作的PHP文件执行任意代码。漏洞在/admin/options 处的远程代码执行 (RCE) 漏洞允许攻击者通过构建的 PHP 文件执行任意代码…

能源管理系统主要功能|智慧工厂|瑜岿科技

瑜岿科技综合能源管理系统以物联网LoT平台为底座&#xff0c;并发挥其数据融合和系统集成能力&#xff0c;不断完善和扩充在数字化运维场景下的功能库&#xff0c;系统升级后具有智慧能效、智慧运维、需求响应、碳中和、资产管理、数字展示大屏等功能模块&#xff0c;现可将水、…

【ARM-Trustzone-TEE-ATF-SOC群】周刊 第一期:开篇

背景 很多小伙伴也都知道&#xff0c;学习/探讨Arm/安全/tee是有群的&#xff0c;而且不止一个。在此群中&#xff0c;有很多很多优秀的小伙伴&#xff0c;每天讨论着各种各样的技术&#xff0c;透过事务看本质&#xff0c;直奔主题的讨论。这里聚集着一些SOC大牛、ASIC大牛、…

营销革命5.0—用技术手段推动市场部变革

随着政治、经济、社会环境和消费者的需求与行为的改变&#xff0c;以及日新月异的技术进步&#xff0c;营销的思想和模式在不断迭代。科特勒教授将营销的演进划分为5个阶段&#xff1a; 第一个阶段是营销1.0时代&#xff0c;工业化时代以产品为中心的营销&#xff0c;解决企业…

sql数据库常用函数简单记录

1.参考文献 (388条消息) CAST&#xff08;&#xff09;函数用法_普通网友的博客-CSDN博客_cast函数 (388条消息) oracle生成标准uuid,Oracle 生成uuid方法_Ms 陈的博客-CSDN博客 (388条消息) oracle的exists用法总结_见怪不怪丶的博客-CSDN博客_oracle exists (388条消息) …

Proxmox VE+Openstack超融合私有云建设案例(低成本高价值,拿走不谢,干翻公有云)

目录本文最终实现目标材料准备路由器设置机房静态公网ip设置WAN口宽带动态公网ip设置WAN口和DDNSLAN口设置wifi设置交换机配置服务器配置iBMC密码和ip设置升级固件Bios恢复到出厂设置PVE安装和网卡聚合mode4配置下载PVE iso文件iBMC设置光盘启动登录iBMC的KVM服务器硬盘设置连接…

CANoe-Test Trace Window、Vector设备的以太网端口

1. Test Trace Window Test Trace Window,测试跟踪窗口,是CANoe软件在Test Units的测试用例执行期间,观察和分析测试的所有执行动作的界面 打开测试跟踪窗口的方式有两种: 添加测试跟踪窗口有两种方式: 方法1方法2方法2也可以删除测试跟踪窗口 测试跟踪窗口可以通过Conf…

BOM:浏览器对象模型

BOM的全称browser object mode css不可调整的一般都是浏览器的部分&#xff0c;比如&#xff1a;浏览器的滚动条、地址栏、关闭按钮、刷新按钮。 BOM可以操作浏览器&#xff1a; 1.弹出框 //提示框 // window.alert() //输入框 // window.prompt() //询问框&#xff1a;返回值…

认识一下容器网络接口 CNI

写在最前&#xff0c;周末写到这篇的时候我就发现可能是给自己挖了很大的坑&#xff0c;整个 Kubernetes 网关相关的内容会非常复杂且庞大。 深入探索 Kubernetes 网络模型和网络通信认识一下容器网络接口 CNI&#xff08;本篇&#xff09;源码分析&#xff1a;从 kubelet、容…

Ubuntu StartUML安装教程

1. 前言 开发工作中&#xff0c;要经常画流程图&#xff0c;时序图等&#xff0c;Ubuntu下推荐超级好用的工具StartUML,也用过Dia这款工具&#xff0c;对比使用之后还是觉得StartUML更香一点&#xff0c;本篇文章记录一下安装破解过程。 2. 安装 2.1 官网下载 地址&#xff1a…

SpringSecurity主要流程及扩展实现

解析主流的SpringSecurity安全框架&#xff0c;结合若依框架进行分析。 文章目录概述登录流程分析SecurityConfig配置类设置过滤请求添加过滤器注册认证provider/获取用户详情服务关键过滤器源码分析SpringSecurity实现若依token生成逻辑创建令牌设置用户代理信息刷新令牌有效期…

青春,不过几届世界杯系列1 —— 我经历的2002 ~ 2022年五届世界杯速览

1. 前言&#xff1a; 应 SoftwareTeacher, 邹欣老师在CSDN上的关于世界杯的邀约&#xff0c;在此回顾我目前经历的五届世界杯的点点滴滴。 2. 正文&#xff1a; 我是从2002年韩日世界杯&#xff0c;开始看世界杯的。现在算来&#xff0c;我已经经历了5届世界杯&#xff0c;而…

智慧水务平台建设方案全流程管控方案 智慧水务信息化系统的意义_管理_数据_设备

平升电子智慧水务平台建设方案全流程管控方案/智慧水务信息化系统/水务综合运营管理平台/智慧水务平台&#xff0c;综合水务公司对管网地理信息在线、供水调度SCADA、各环节数据互联互通、工单执行过程监督、运营情况分析等管理需求&#xff0c;建立了一套面向基层执行者、中层…