问题描绘

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

  1. 界说了一个注解了@ConfigurationPropertiesUser 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();
    }
}

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

  1. 但是,有个搭档修正了下变量名为user1,自信的认为没有问题,就提交测试了,然后直接报错了。
@RestController
@RequestMapping("/config")
@EnableConfigurationProperties(User.class)
public class UserConfigController {
    @Autowired
    private User user1;
    @GetMapping("/username2")
    public String username2() {
        return user1.getUserName();
    }
}

报错如下图所示:

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

这是怎么一回事呢,修正个变量名都能报错?

原因剖析

依据报错信息不难剖析出来主要原因在于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。

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

  1. 运用了@EnableConfigurationProperties注解创立了称号为my.user-com.alvinlkk.bean.User的Bean。

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

最佳实践

运用@ConfigurationProperties注解的Bean的时候,建议经过运用@EnableConfigurationProperties创立Bean。

源码解析

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

  1. 创立Bean的界说信息BeanDefinition,包含Bean的类型,称号等信息,注册到Bean界说工厂中。
  2. 依据Bean界说工厂中的Bean界说信息,创立出Bean实例。

上面的两个进程中在通常在SpringBoot发动的进程中就完成,SpringBoot发动的时候,会调用容器的refresh(), 其间在invokeBeanFactoryPostProcessors(beanFactory)办法中创立并注册BeanDefinition, 在finishBeanFactoryInitialization()办法中创立Bean实例目标。

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

创立注册BeanDefinition

  1. @Component注解

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

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

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

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

  1. @EnableConfigurationProperties注解

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

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

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

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

而且@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

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

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

总结

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

欢迎重视个人公众号【JAVA旭阳】交流学习!