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
,可以看看:
那么我们从 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