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

2023年 7月 31日 121.3k 0

问题描述

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

  • 定义了一个注解了@ConfigurationPropertiesUser Bean
  • @ConfigurationProperties(prefix = "my.user")
    @Component
    @Data
    public class User {
    
        private String userName;
    }
    
  • 通过@Autowired使用UserBean,没有问题。
  • @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会被扫描到,生成一个名字是userBeanDefinition,然后注册到BeanDefitionRegistry中,如下图所示:

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

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

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

    创建Bean对象

    现在BeanDefinitionBean定义信息已经有了,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旭阳】交流学习!

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论