@ConfigurationProperties该如何装载到Spring容器中呢?

开发 前端
所以对于配置注解ConfigurationProperties​的类不要使用使用@Component​注解让Spring管理,更推荐的做法是使用@EnableConfigurationProperties注解进行装载。

问题描述

最近项目中遇到了一个Spring中@ConfigurationProperties注解的问题,如下:

  1. 定义了一个注解了@ConfigurationProperties的User Bean。
@ConfigurationProperties(prefix = "my.user")
@Component
@Data
public class User {

    private String userName;
}
  1. 通过@Autowired使用UserBean,没有问题。
@RestController
@RequestMapping("/config")
@EnableConfigurationProperties(User.class)
public class UserConfigController {

    @Autowired
    private User user;

    @GetMapping("/username1")
    public String username1() {
        return user.getUserName();
    }
}

图片图片

  1. 但是,有个同事修改了下变量名为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。

图片图片

  1. 使用了@EnableConfigurationProperties注解创建了名称为my.user-com.alvinlkk.bean.User的Bean。

最佳实践最佳实践

使用@ConfigurationProperties注解的Bean的时候,建议通过使用@EnableConfigurationProperties创建Bean。

源码解析

刨根问底,我们继续从Spring源码层面深入了解下这个问题的产生的根源。Spring创建Bean的过程其实很简单,大致分两个步骤:

  1. 创建Bean的定义信息BeanDefinition,包含Bean的类型,名称等信息,注册到Bean定义工厂中。
  2. 根据Bean定义工厂中的Bean定义信息,创建出Bean实例。

上面的两个过程中在通常在SpringBoot启动的过程中就完成,SpringBoot启动的时候,会调用容器的refresh(), 其中在invokeBeanFactoryPostProcessors(beanFactory)方法中创建并注册BeanDefinition, 在finishBeanFactoryInitialization()方法中创建Bean实例对象。

图片图片

创建注册BeanDefinition

  1. @Component注解

被Compoent注解的的类会被Spring中的ConfigurationClassPostProcessor类处理,创建出对应的BeanDefinition,然后注册到BeanDefinitionRegistry中,具体流程如下图所示。

图片图片

被@Component注解的类User会被扫描到,生成一个名字是user的BeanDefinition,然后注册到BeanDefitionRegistry中,如下图所示:

图片图片

  1. @EnableConfigurationProperties注解

注解@EnableConfigurationProperties源码中import了EnableConfigurationPropertiesRegistrar类,那么它是在什么阶段创建出BeanDefinition呢?

图片图片

最终配置了@EnableConfigurationProperties(User.class)中被获取,创建出name为my.user-com.alvinlkk.bean.User的BeanDefinition,如下图所示。

图片图片

而且@Component的顺序是优先于@EnableConfigurationProperties的。

创建Bean对象

现在BeanDefinitionBean定义信息已经有了,Spring就可以根据这些信息创建出Bean对象实例了,这一个过程是在finishBeanFactoryInitialization()方法中进行的,我们这里重点关注下@Autowird方法是如何进行装配的。

  1. AbstractApplicationContext#refresh(): 初始化容器
  2. AbstractApplicationContext#finishBeanFactoryInitialization(): 初始化Bean入口
  3. DefaultListableBeanFactory#preInstantiateSingletons():预先初始化单例Bean
  4. DefaultListableBeanFactory#getBean(): 调用getBean()创建Bean实例
  5. AbstractBeanFactory#doGetBean():getBean()最终调用的方法
  6. AbstractAutowireCapableBeanFactory#createBean(): 创建Bean实例入口
  7. DefaultListableBeanFactory#determineAutowireCandidate():选择使用哪个候选的Bean

图片图片

根据类型匹配到Bean有多个的情况,会调用determineAutowireCandidate()方法进一步去根据name匹配bean。

总结

所以对于配置注解ConfigurationProperties的类不要使用使用@Component注解让Spring管理,更推荐的做法是使用@EnableConfigurationProperties注解进行装载。

责任编辑:武晓燕 来源: JAVA旭阳
相关推荐

2023-11-19 22:31:00

内存程序

2019-01-03 15:10:40

JVM安全资源

2020-09-02 08:09:10

攻击防御Shiro

2020-05-22 18:16:57

数据泄露网络安全互联网

2021-02-24 14:01:13

微服务开发框架

2010-06-04 10:46:18

同事上司

2023-01-05 08:12:11

分层应用代码

2015-09-30 10:34:39

容器永久性存储Docker

2022-06-23 10:47:57

Spring容器工具

2018-07-27 17:28:37

人工智能机器学习AI

2022-10-27 11:46:48

ConfiguratValuespring

2023-10-16 11:12:29

2020-12-11 08:04:22

SpringAOPBean

2019-07-15 16:00:24

Docker架构容器

2019-07-15 10:00:53

DockerJava容器

2015-05-15 17:29:13

.Netxp系统如何解决

2023-11-03 08:19:18

SpringBean容器

2024-05-28 07:55:31

SpringBean用域

2018-06-19 09:07:57

Linux内核模块

2021-11-26 00:05:56

RabbitMQVirtualHostWeb
点赞
收藏

51CTO技术栈公众号