问题描绘
最近项目中遇到了一个Spring
中@ConfigurationProperties
注解的问题,如下:
- 界说了一个注解了
@ConfigurationProperties
的User Bean
。
@ConfigurationProperties(prefix = "my.user")
@Component
@Data
public class User {
private String userName;
}
- 经过
@Autowired
运用User
Bean,没有问题。
@RestController
@RequestMapping("/config")
@EnableConfigurationProperties(User.class)
public class UserConfigController {
@Autowired
private User user;
@GetMapping("/username1")
public String username1() {
return user.getUserName();
}
}
- 但是,有个搭档修正了下变量名为
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呢?
- 因为运用
@Component
注解,创立了一个称号为“user
”的Bean。
- 运用了
@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()
:预先初始化单例Bean -
DefaultListableBeanFactory#getBean()
: 调用getBean()
创立Bean实例 -
AbstractBeanFactory#doGetBean()
:getBean()
最终调用的办法 -
AbstractAutowireCapableBeanFactory#createBean()
: 创立Bean实例入口 -
DefaultListableBeanFactory#determineAutowireCandidate()
:挑选运用哪个候选的Bean
依据类型匹配到Bean有多个的情况,会调用determineAutowireCandidate()
办法进一步去依据name匹配bean。
总结
所以关于装备注解ConfigurationProperties
的类不要运用运用@Component
注解让Spring管理,更推荐的做法是运用@EnableConfigurationProperties
注解进行装载。
欢迎重视个人公众号【JAVA旭阳】交流学习!