El resultado de usar @Bean y @Component en la misma clase

 Fondo de duda

Descripción de las preocupaciones

Recientemente, en proceso de desarrollo, encontré un escrito anterior, similar al siguiente

Según tengo entendido, @Configuration Plus  @Bean creará un objeto UserManager cuyo nombre de usuario no sea nulo, pero  @Component también creará un objeto UserManager cuyo nombre de usuario sea nulo

Entonces, cuando inyectamos el objeto UserManager en otros objetos, ¿qué objeto se inyecta?

Debido a que el proyecto ha estado en línea durante mucho tiempo, no hay ningún error de compilación en esta forma de escribir y no hay ningún problema en la operación.

Ir a mis colegas más tarde para averiguarlo, en realidad quiero dejar

hace efecto, y de hecho hace efecto

Entonces aquí viene la pregunta: ¿ Cuántos objetos de tipo UserManager hay en el contenedor Spring?

Versión Spring Boot

La versión de Spring Boot utilizada en el proyecto es:2.0.3.RELEASE

El alcance del objeto es el valor predeterminado, que es un singleton

Verificación de resultados

Hay muchos métodos de verificación. Puede depurar y código fuente para ver cuántos objetos UserManager hay en el contenedor Spring. También puede comenzar directamente desde el constructor UserManager para ver qué constructores se llaman, etc.

Comencemos con el constructor y veamos cuántas veces se crea una instancia de UserManager

Solo se llama al constructor parametrizado, y el constructor sin parámetros permanece intacto (no se llama en absoluto)

Dado que el constructor UserManager se llama solo una vez, la pregunta anterior: qué objeto se inyecta

La respuesta también es clara, no hay opción, solo se puede  @Configuration agregar al  @Bean objeto UserManager creado cuyo nombre de usuario no es nulo

问题又来了:为什么不是 @Component 创建的 userName 为 null 的 UserManager 对象? 搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!

源码解析

@Configuration 与 @Component 关系很紧密

所以@Configuration 能够被 component scan

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

它实现了 BeanFactoryPostProcessor 接口和 PriorityOrdered 接口,关于 BeanFactoryPostProcessor,可以看看:

www.cnblogs.com/youzhibing/…

那么我们从 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 的有参构造方法

自此,答案也就清楚了。搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!

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 同时作用于同一个类

Al inicio, se dará un indicador de registro del nivel de información, y @Configuration + @Bean la BeanDefinition decorada se sobrescribirá con @Component la BeanDefinition decorada.

Tal vez el equipo de Spring se dio cuenta de que el procesamiento anterior no es adecuado, por lo que  Spring 5.1.2.RELEASE hizo un procesamiento de optimización

Se agregó el elemento de configuración: allowBeanDefinitionOverriding , la iniciativa se entrega al desarrollador y el desarrollador decide si permite sobrescribir

Reponer

Con respecto a  allowBeanDefinitionOverriding , lo que dije anteriormente está mal, revisé deliberadamente el código fuente más tarde y agregué lo siguiente

DefaultListableBeanFactory Cuando se  introdujo Spring 1.2  private boolean allowBeanDefinitionOverriding = true;, el valor predeterminado es permitir la  BeanDefinition anulación

Spring 4.1.2  isAllowBeanDefinitionOverriding()métodos introducidos

Se ha permitido que Spring se anule de forma predeterminada de principio a fin  BeanDefinition , pero Spring Boot ha cambiado. Spring Boot 2.1.0 no anuló el  allowBeanDefinitionOverriding valor predeterminado de Spring, pero aún se permite  BeanDefinition anular

SpringApplication en Spring Boot 2.1.0 define propiedades privadas:allowBeanDefinitionOverriding

Si no se muestra ningún valor especificado, entonces el valor predeterminado es falso y luego, durante el proceso de inicio de Spring Boot, este valor se usará para anular  allowBeanDefinitionOverriding el valor predeterminado en Spring.

Acerca  allowBeanDefinitionOverriding  de, creo que todos deberían ser claros

Supongo que te gusta

Origin juejin.im/post/7086260976294576158
Recomendado
Clasificación