How should @ConfigurationProperties be loaded into the Spring container?

Problem Description

In a recent project, I encountered a problem with annotations, as follows Spring:@ConfigurationProperties

  1. @ConfigurationPropertiesAn annotation is defined User Bean.
@ConfigurationProperties(prefix = "my.user")
@Component
@Data
public class User {

    private String userName;
}
  1. By @Autowiredusing UserBean, there is no problem.
@RestController
@RequestMapping("/config")
@EnableConfigurationProperties(User.class)
public class UserConfigController {

    @Autowired
    private User user;

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

  1. However, a colleague changed the name of the variable user1and confidently thought there was no problem, so he submitted the test and reported an error directly.
@RestController
@RequestMapping("/config")
@EnableConfigurationProperties(User.class)
public class UserConfigController {

    @Autowired
    private User user1;

    @GetMapping("/username2")
    public String username2() {
        return user1.getUserName();
    }
}

The error is shown in the figure below:

What's going on here, can you report an error even if you modify a variable name?

Cause Analysis

According to the error message, it is not difficult to analyze that the main reason is that there are two objects Userof the class in the container , namely " " and " ".SpringBeanbean nameusermy.user-com.alvinlkk.bean.User

Using @Autwiredassembly is actually not just assembling according to the type. If multiple Bean objects of the same type are matched, it will find the same username as the variable name " " by default Bean, so no error will be reported. If you change the variable name to user1, it will match two Bean objects, and if you bean name=user1can’t find the right one, you will naturally report an error.

So why are there two beans?

  1. Because of @Componentthe annotation, a Bean named " " is created user.

  1. Annotations are used @EnableConfigurationPropertiesto create my.user-com.alvinlkk.bean.Usera Bean named .

Best Practices

When using @ConfigurationPropertiesannotated beans, it is recommended to @EnableConfigurationPropertiescreate beans by using.

Source code analysis

To get to the bottom of it, we continue to understand the root cause of this problem from the Spring source code level. The process of creating a Bean in Spring is actually very simple, roughly divided into two steps:

  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会被扫描到,生成一个名字是userBeanDefinition,然后注册到BeanDefitionRegistry中,如下图所示:

  1. @EnableConfigurationProperties注解

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

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

而且@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旭阳】交流学习!

おすすめ

転載: juejin.im/post/7261126813329063992