前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?

关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?

作者头像
青石路
发布2021-10-09 15:53:22
9560
发布2021-10-09 15:53:22
举报
文章被收录于专栏:开发技术

开心一刻

  今天放学回家,气愤愤地找到我妈

  我:妈,我们班同学都说我五官长得特别平

  妈:你小时候爱趴着睡觉

  我:你怎么不把我翻过来呢

  妈:那你不是凌晨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 其实给出了提示

代码语言:javascript
复制
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 也给出了提示

代码语言:javascript
复制
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 ,将主动权交给了开发者,由开发者自己决定是否允许覆盖

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-10-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开心一刻
  • 疑虑背景
    •   疑虑描述
      •   Spring Boot 版本
      • 结果验证
      • 源码解析
      • Spring 升级优化
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档