详解@ConfigurationProperties如何装载到Spring容器中
作者:JAVA旭阳
问题描述
最近项目中遇到了一个Spring
中@ConfigurationProperties
注解的问题,如下:
1.定义了一个注解了@ConfigurationProperties
的User Bean
。
@ConfigurationProperties(prefix = "my.user") @Component @Data public class User { private String userName; }
2.通过@Autowired
使用User
Bean,没有问题。
@RestController @RequestMapping("/config") @EnableConfigurationProperties(User.class) public class UserConfigController { @Autowired private User user; @GetMapping("/username1") public String username1() { return user.getUserName(); } }
3.但是,有个同事修改了下变量名为user1
,自信的以为没有问题,就提交测试了,然后直接报错了。
@RestController @RequestMapping("/config") @EnableConfigurationProperties(User.class) public class UserConfigController { @Autowired private User user1; @GetMapping("/username2") public String username2() { return user1.getUserName(); } }
报错如下图所示:
这是怎么一回事呢,修改个变量名都能报错?
原因分析
根据报错信息不难分析出来主要原因在于User
类在Spring
容器中两个Bean
对象,bean name
分别是“user
”和“my.user-com.alvinlkk.bean.User
”。
使用@Autwired
装配,实际上不只是根据类型装配,如果匹配到同类型有多个Bean对象,默认会去找和变量名“user
”同名的Bean
,所以不会报错。如果修改变量名改成user1
, 它就匹配到两个Bean对象,然后用bean name=user1
无法找到合适的,自然就报错了。
那么为什么会出现两个Bean呢?
1.因为使用@Component
注解,创建了一个名称为“user
”的Bean。
2.使用了@EnableConfigurationProperties
注解创建了名称为my.user-com.alvinlkk.bean.User
的Bean。
最佳实践
使用@ConfigurationProperties
注解的Bean的时候,建议通过使用@EnableConfigurationProperties
创建Bean。
源码解析
刨根问底,我们继续从Spring源码层面深入了解下这个问题的产生的根源。Spring创建Bean的过程其实很简单,大致分两个步骤:
- 创建Bean的定义信息
BeanDefinition
,包含Bean的类型,名称等信息,注册到Bean定义工厂中。 - 根据Bean定义工厂中的Bean定义信息,创建出Bean实例。
上面的两个过程中在通常在SpringBoot启动的过程中就完成,SpringBoot启动的时候,会调用容器的refresh()
, 其中在invokeBeanFactoryPostProcessors(beanFactory)
方法中创建并注册BeanDefinition
, 在finishBeanFactoryInitialization()
方法中创建Bean实例对象。
创建注册BeanDefinition
@Component
注解
被Compoent
注解的的类会被Spring中的ConfigurationClassPostProcessor
类处理,创建出对应的BeanDefinition
,然后注册到BeanDefinitionRegistry
中,具体流程如下图所示。
被@Component
注解的类User会被扫描到,生成一个名字是user
的BeanDefinition
,然后注册到BeanDefitionRegistry中,如下图所示:
@EnableConfigurationProperties
注解
注解@EnableConfigurationProperties
源码中import
了EnableConfigurationPropertiesRegistrar
类,那么它是在什么阶段创建出BeanDefinition
呢?
最终配置了@EnableConfigurationProperties(User.class)
中被获取,创建出name为my.user-com.alvinlkk.bean.User
的BeanDefinition
,如下图所示。
而且@Component
的顺序是优先于@EnableConfigurationProperties
的。
创建Bean对象
现在BeanDefinition
Bean定义信息已经有了,Spring就可以根据这些信息创建出Bean对象实例了,这一个过程是在finishBeanFactoryInitialization()
方法中进行的,我们这里重点关注下@Autowird
方法是如何进行装配的。
AbstractApplicationContext#refresh()
: 初始化容器AbstractApplicationContext#finishBeanFactoryInitialization()
: 初始化Bean入口DefaultListableBeanFactory#preInstantiateSingletons()
:预先初始化单例BeanDefaultListableBeanFactory#getBean()
: 调用getBean()
创建Bean实例AbstractBeanFactory#doGetBean()
:getBean()
最终调用的方法AbstractAutowireCapableBeanFactory#createBean()
: 创建Bean实例入口DefaultListableBeanFactory#determineAutowireCandidate()
:选择使用哪个候选的Bean
根据类型匹配到Bean有多个的情况,会调用determineAutowireCandidate()
方法进一步去根据name匹配bean。
总结
所以对于配置注解ConfigurationProperties
的类不要使用使用@Component
注解让Spring管理,更推荐的做法是使用@EnableConfigurationProperties
注解进行装载。
以上就是详解@ConfigurationProperties如何装载到Spring容器中的详细内容,更多关于ConfigurationProperties装载到Spring容器的资料请关注脚本之家其它相关文章!