关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?
开心一刻
今天放学回家,气愤愤地找到我妈
我:妈,我们班同学都说我五官长得特别平
妈:你小时候爱趴着睡觉
我:你怎么不把我翻过来呢
妈:那你不是凌晨2点时候出生的吗
我:嗯,凌晨2点出生就爱趴着睡觉呗
爸:凌晨 2 点是丑时,丑!
妈:我把你翻过来,我看着你,我害怕呀
我内心一咯噔:敢情我不是天生的五官平呀,哎,虽不是天生,但胜似天生了
疑虑背景
疑虑描述
最近,在进行开发的过程中,发现之前的一个写法,类似如下
以我的理解,@Configuration 加 @Bean 会创建一个 userName 不为 null 的 UserManager 对象,而 @Component 也会创建一个 userName 为 null 的 UserManager 对象
那么我们在其他对象中注入 UserManager 对象时,到底注入的是哪个对象?
因为项目已经上线了很长一段时间了,所以这种写法没有编译报错,运行也没有出问题
后面去找同事了解下,实际是想让
生效,而实际也确实是它生效了
那么问题来了: Spring 容器中到底有几个 UserManager 类型的对象?
Spring Boot 版本
项目中用的 Spring Boot 版本是: 2.0.3.RELEASE
对象的 scope 是默认值,也就是 singleton
结果验证
验证方式有很多,可以 debug 跟源码,看看 Spring 容器中到底有几个 UserManager 对象,也可以直接从 UserManager 构造方法下手,看看哪几个构造方法被调用,等等
我们从构造方法下手,看看 UserManager 到底实例化了几次
只有有参构造方法被调用了,无参构造方法岿然不动(根本没被调用)
如果想了解的更深一点,可以读读鄙人的:Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗
既然 UserManager 构造方法只被调用了一次,那么前面的问题: 到底注入的是哪个对象
答案也就清晰了,没得选了呀,只能是 @Configuration 加 @Bean 创建的 userName 不为 null 的 UserManager 对象
问题又来了:为什么不是 @Component 创建的 userName 为 null 的 UserManager 对象?
源码解析
@Configuration 与 @Component 关系很紧密
所以 @Configuration 能够被 component scan
在spring-boot-2.0.3源码篇 - @Configuration、Condition与@Conditional中讲到了 @Configuration 的实现原理
其中 ConfigurationClassPostProcessor 与 @Configuration 息息相关,其类继承结构图如下:
它实现了 BeanFactoryPostProcessor 接口和 PriorityOrdered 接口,关于 BeanFactoryPostProcessor ,可以看看鄙人的Spring拓展接口之BeanFactoryPostProcessor,占位符与敏感信息解密原理
那么我们从 AbstractApplicationContext 的 refresh 方法调用的 invokeBeanFactoryPostProcessors(beanFactory) 开始,来跟下源码
此时完成了 com.lee.qsl 包下的 component scan , com.lee.qsl 包及子包下的 UserConfig 、 UserController 和 UserManager 都被扫描出来
注意,此刻 @Bean 的处理还未开始, UserManager 是通过 @Component 而被扫描出来的;此时 Spring 容器中 beanDefinitionMap 中的 UserManager 是这样的
接下来一步很重要,与我们想要的答案息息相关
循环递归处理 UserConfig 、 UserController 和 UserManager ,把它们都封装成 ConfigurationClass ,递归扫描 BeanDefinition
循环完之后,我们来看看 configClasses
UserConfig bean 定义信息中 beanMethods 中有一个元素 [BeanMethod:name=userManager,declaringClass=com.lee.qsl.config.UserConfig]
然后我们接着往下走,来仔细看看答案出现的环节
是不是有什么发现? @Component 修饰的 UserManager 定义直接被覆盖成了 @Configuration + @Bean 修饰的 UserManager 定义
Bean 定义类型也由 ScannedGenericBeanDefinition 替换成了 ConfigurationClassBeanDefinition
后续通过 BeanDefinition 创建实例的时候,创建的自然就是 @Configuration + @Bean 修饰的 UserManager ,也就是会反射调用 UserManager 的有参构造方法
自此,答案也就清楚了
Spring 其实给出了提示
2021-10-03 20:37:33.697 INFO 13600 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'userManager' with a different definition: replacing [Generic bean: class [com.lee.qsl.manager.UserManager]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=userConfig; factoryMethodName=userManager; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/lee/qsl/config/UserConfig.class]]
只是日志级别是 info ,太不显眼了
Spring 升级优化
可能 Spring 团队意识到了 info 级别太不显眼的问题,或者说意识到了直接覆盖的处理方式不太合理
所以在 Spring 5.1.2.RELEASE (Spring Boot 则是 2.1.0.RELEASE )做出了优化处理
我们来具体看看
启动直接报错,Spring 也给出了提示
The bean 'userManager', defined in class path resource [com/lee/qsl/config/UserConfig.class], could not be registered. A bean with that name has already been defined in file [D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class] and overriding is disabled.
我们来跟下源码,主要看看与 Spring 5.0.7.RELEASE 的区别
新增了配置项 allowBeanDefinitionOverriding 来控制是否允许 BeanDefinition 覆盖,默认情况下是不允许的
我们可以在配置文件中配置: spring.main.allow-bean-definition-overriding=true ,允许 BeanDefinition 覆盖
这种处理方式是更优的,将选择权交给开发人员,而不是自己偷偷的处理,已达到开发者想要的效果
总结
Spring 5.0.7.RELEASE ( Spring Boot 2.0.3.RELEASE ) 支持 @Configuration + @Bean 与 @Component 同时作用于同一个类
启动时会给 info 级别的日志提示,同时会将 @Configuration + @Bean 修饰的 BeanDefinition 覆盖掉 @Component 修饰的 BeanDefinition
也许 Spring 团队意识到了上述处理不太合适,于是在 Spring 5.1.2.RELEASE 做出了优化处理
增加了配置项: allowBeanDefinitionOverriding ,将主动权交给了开发者,由开发者自己决定是否允许覆盖
关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?的更多相关文章
- 记一次spring boot中MongoDB Prematurely reached end of stream的异常解决
在spring boot项目中使用了mongodb,当一段时间没有操作mongodb,下次操作mongodb时就会出现异常.异常如下: org.springframework.data.mongodb ...
- Spring Boot中只能有一个WebMvcConfigurationSupport配置类
首先将结论写文章的最前面,一个项目中只能有一个继承WebMvcConfigurationSupport的@Configuration类(使用@EnableMvc效果相同),如果存在多个这样的类,只有一 ...
- 在Spring Boot中输出REST资源
前面我们我们已经看了Spring Boot中的很多知识点了,也见识到Spring Boot带给我们的各种便利了,今天我们来看看针对在Spring Boot中输出REST资源这一需求,Spring Bo ...
- Spring boot中的定时任务(计划任务)
从Spring3.1开始,计划任务在Spring中实现变得异常的简单.首先通过配置类注解@EnableScheduling来开启对计划任务的支持,然后再要执行的计划任务的方法上注释@Scheduled ...
- 记录Spring Boot大坑一个,在bean中如果有@Test单元测试,不会注入成功
记录Spring Boot大坑一个,在bean中如果有@Test单元测试,不会注入成功 记录Spring Boot大坑一个,在bean中如果有@Test单元测试,不会注入成功 记录Spring Boo ...
- (41)Spring Boot 使用Java代码创建Bean并注册到Spring中【从零开始学Spring Boot】
已经好久没有讲一些基础的知识了,这一小节来点简单的,这也是为下节的在Spring Boot中使用多数据源做准备. 从Spring 3.0开始,增加了一种新的途径来配置Bean Definition,这 ...
- Spring Boot中普通类获取Spring容器中的Bean
我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器进行管理,但是在实际当中,我们往往会碰到在一个普通的Java类中,自己动手n ...
- springboot(十一):Spring boot中mongodb的使用
mongodb是最早热门非关系数据库的之一,使用也比较普遍,一般会用做离线数据分析来使用,放到内网的居多.由于很多公司使用了云服务,服务器默认都开放了外网地址,导致前一阵子大批 MongoDB 因配置 ...
- (转)Spring Boot(十一):Spring Boot 中 MongoDB 的使用
http://www.ityouknow.com/springboot/2017/05/08/spring-boot-mongodb.html MongoDB 是最早热门非关系数据库的之一,使用也比较 ...
随机推荐
- LeetCoded第21题题解--合并两个有序链表
21. 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1->2->4, 1->3->4 输出 ...
- Jmeter的初体验--安装
准备工作 安装JMeter前需要安装配置好Java 一.安装 1.直接在官网下载安装即可,下载地址:http://jmeter.apache.org/download_jmeter.cgi,(Wind ...
- Mybatis源码解析3——核心类SqlSessionFactory,看完我悟了
这是昨晚的武汉,晚上九点钟拍的,疫情又一次来袭,曾经熙熙攘攘的夜市也变得冷冷清清,但比前几周要好很多了.希望大家都能保护好自己,保护好身边的人,生活不可能像你想象的那么好,但也不会像你想象的那么糟. ...
- Python之smtplib模块
工作中难免会出现自动发送电子邮件的需求,比如说做完自动化测试之后通过电子邮件的形式将结果反馈出来.Python中提供了标准库smtplib来解决这一问题,该模块定义了一个smtp客户端会话对象,能够将 ...
- 代码保留格式(高亮)复制到Word(转载)
将代码保持高亮复制粘贴到word上,一些方法如下: 方法一:借助网站http://www.planetb.ca/syntax-highlight-word/(代码有编号,整体排版精美令人舒适,但语言有 ...
- VSCode——滚动鼠标控制字体大小
第一步:找到设置 文件-->首选项-->设置 第二步:打开settings.json文件 第三步:在settings.json文件中添加 "editor.mouseWheelZo ...
- vue 路由视图,router-view嵌套跳转
实现功能:制作一个登录页面,跳转到首页,首页包含菜单栏.顶部导航栏.主体,标准的后台网页格式.菜单栏点击不同菜单控制主体展示不同的组件(不同的页面). 配置router-view嵌套跳转需要准备两个主 ...
- pluto中监听各个网口的500端口处理逻辑
1. pluto中监听各个网口的500端口处理逻辑 whack_handle() find_ifaces() find_raw_ifaces4() socket.setsockopt.bind.ioc ...
- 原子操作cas
一.概念, 基于处理器指令,把比较和交换合成一个指令完成,保证了原子性: 因为是针对一个内存地址值的,一个内存地址指向一个变量,所以只对一个共享变量能保证原子性: 二.原子操作类 锁只有synchro ...
- Lambda表达式——注重过程的编程思想
一.使用匿名内部类的匿名对象创建线程和Lambda表达式写法 Lambda表达式写法不用去定义一个Runable接口的实现类: 二.方法入参是一个接口或者接口的实现类 三.对某个类的一些对象实例进行排 ...