Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

疑虑背景

  疑虑描述

  最近,在进行开发的过程中,发现之前的一个写法,类似如下

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  以我的理解,@Configuration 加 @Bean 会创建一个 userName 不为 null 的 UserManager 对象,而 @Component 也会创建一个 userName 为 null 的 UserManager 对象

  那么我们在其他对象中注入 UserManager 对象时,到底注入的是哪个对象

  因为项目已经上线了很长一段时间了,所以这种写法没有编译报错,运行也没有出问题

  后面去找同事了解下,实际是想让

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  生效,而实际也确实是它生效了

  那么问题来了: Spring 容器中到底有几个 UserManager 类型的对象?

  Spring Boot 版本

  项目中用的 Spring Boot 版本是: 2.0.3.RELEASE

  对象的 scope 是默认值,也就是 singleton

结果验证

  验证方式有很多,可以 debug 跟源码,看看 Spring 容器中到底有几个 UserManager 对象,也可以直接从 UserManager 构造方法下手,看看哪几个构造方法被调用,等等

  我们从构造方法下手,看看 UserManager 到底实例化了几次

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  只有有参构造方法被调用了,无参构造方法岿然不动(根本没被调用)

  如果想了解的更深一点,可以读读鄙人的:Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

  既然 UserManager 构造方法只被调用了一次,那么前面的问题: 到底注入的是哪个对象

  答案也就清晰了,没得选了呀,只能是 @Configuration 加 @Bean 创建的 userName 不为 null 的 UserManager 对象

  问题又来了:为什么不是 @Component 创建的 userName 为 null 的 UserManager 对象?

源码解析

   @Configuration 与 @Component 关系很紧密

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  所以 @Configuration 能够被 component scan

  在spring-boot-2.0.3源码篇 - @Configuration、Condition与@Conditional中讲到了 @Configuration 的实现原理

  其中
ConfigurationClassPostProcessor 与 @Configuration 息息相关,其类继承结构图如下:

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  它实现了 BeanFactoryPostProcessor 接口和 PriorityOrdered 接口,关于 BeanFactoryPostProcessor ,可以看看鄙人的Spring拓展接口之BeanFactoryPostProcessor,占位符与敏感信息解密原理

  那么我们从
AbstractApplicationContext 的 refresh 方法调用的
invokeBeanFactoryPostProcessors(beanFactory) 开始,来跟下源码

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  此时完成了 com.lee.qsl 包下的 component scan , com.lee.qsl 包及子包下的 UserConfig 、 UserController 和 UserManager 都被扫描出来

  注意,此刻 @Bean 的处理还未开始, UserManager 是通过 @Component 而被扫描出来的;此时 Spring 容器中 beanDefinitionMap 中的 UserManager 是这样的

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  接下来一步很重要,与我们想要的答案息息相关

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  循环递归处理 UserConfig 、 UserController 和 UserManager ,把它们都封装成 ConfigurationClass ,递归扫描 BeanDefinition

  循环完之后,我们来看看 configClasses

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

   UserConfig bean 定义信息中 beanMethods 中有一个元素 [BeanMethod:name=userManager,declaringClass=
com.lee.qsl.config.UserConfig]

  然后我们接着往下走,来仔细看看答案出现的环节

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  是不是有什么发现? @Component 修饰的 UserManager 定义直接被覆盖成了 @Configuration + @Bean 修饰的 UserManager 定义

  Bean 定义类型也由
ScannedGenericBeanDefinition 替换成了
ConfigurationClassBeanDefinition

  后续通过 BeanDefinition 创建实例的时候,创建的自然就是 @Configuration + @Bean 修饰的 UserManager ,也就是会反射调用 UserManager 的有参构造方法

  自此,答案也就清楚了

  Spring 其实给出了提示

View Code

  只是日志级别是 info ,太不显眼了

Spring 升级优化

  可能 Spring 团队意识到了 info 级别太不显眼的问题,或者说意识到了直接覆盖的处理方式不太合理

  所以在 Spring 5.1.2.RELEASE (Spring Boot 则是 2.1.0.RELEASE )做出了优化处理

  我们来具体看看

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  启动直接报错,Spring 也给出了提示

View Code

  我们来跟下源码,主要看看与 Spring 5.0.7.RELEASE 的区别

Spring Boot 中创建对象的疑虑 Bean 与 Component 同一类,会怎么样?

  新增了配置项
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 ,将主动权交给了开发者,由开发者自己决定是否允许覆盖

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值