2020-11-19 | Java | Unlock

SpringBoot源码分析(5)-SpringBoot 的配置属性值的绑定流程

上一篇分析了SpringBoot的自动配置的相关源码,自动配置主要有以下几个重要的步骤:

  1. 从spring.factories配置文件中加载自动配置类;

  2. 加载的自动配置类中排除掉@EnableAutoConfiguration注解的exclude属性指定的自动配置类;

  3. 然后再用AutoConfigurationImportFilter接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass,@ConditionalOnBean@ConditionalOnWebApplication的条件,若都符合的话则返回匹配结果;

  4. 然后触发AutoConfigurationImportEvent事件,告诉ConditionEvaluationReport条件评估报告器对象来分别记录符合条件和exclude的自动配置类。

  5. 最后spring再将最后筛选后的自动配置类导入IOC容器中

继续来分析SpringBoot的自动配置的相关源码,我们来分析下@EnableConfigurationProperties@ConfigurationProperties这两个注解,来探究下外部配置属性值是如何被绑定到@ConfigurationProperties注解的类属性中的?

Example:以配置web项目的服务器端口为例,若我们要将服务器端口配置为9999,那么需要在application.properties配置文件中配置server.port=9999,此时该配置值9999就将会绑定到被@ConfigurationProperties注解的类ServerProperties的属性port上,从而使得配置生效。

1.@EnableConfigurationProperties

以前面的设置服务器端口的例子来分析,先直接来看看ServerProperties的源码,应该能找到源码的入口:

1
2
3
4
5
6
7
8
9
10
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

/**
* Server HTTP port.
*/
private Integer port;

...
}

可以看到,ServerProperties类上标注了@ConfigurationProperties这个注解,服务器属性配置前缀为server,是否忽略未知的配置值(ignoreUnknownFields)设置为true

那么再来看下@ConfigurationProperties这个注解的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {

/**
* The name prefix of the properties that are valid to bind to this object. Synonym
* for {@link #prefix()}. A valid prefix is defined by one or more words separated
* with dots (e.g. {@code "acme.system.feature"}).
* @return the name prefix of the properties to bind
*/
// 前缀别名
@AliasFor("prefix")
String value() default "";

/**
* The name prefix of the properties that are valid to bind to this object. Synonym
* for {@link #value()}. A valid prefix is defined by one or more words separated with
* dots (e.g. {@code "acme.system.feature"}).
* @return the name prefix of the properties to bind
*/
// 前缀
@AliasFor("value")
String prefix() default "";

/**
* Flag to indicate that when binding to this object invalid fields should be ignored.
* Invalid means invalid according to the binder that is used, and usually this means
* fields of the wrong type (or that cannot be coerced into the correct type).
* @return the flag value (default false)
*/
// 忽略无效的配置属性
boolean ignoreInvalidFields() default false;

/**
* Flag to indicate that when binding to this object unknown fields should be ignored.
* An unknown field could be a sign of a mistake in the Properties.
* @return the flag value (default true)
*/
// 忽略未知的配置属性
boolean ignoreUnknownFields() default true;

}

@ConfigurationProperties这个注解的作用就是将外部配置的配置值绑定到其注解的类的属性上,可以作用于配置类或配置类的方法上。可以看到@ConfigurationProperties注解除了有设置前缀,是否忽略一些不存在或无效的配置等属性等外,这个注解没有其他任何的处理逻辑,可以看到@ConfigurationProperties是一个标志性的注解,源码入口不在这里。

这里讲的是服务器的自动配置,来看下自动配置类ServletWebServerFactoryAutoConfiguration的源码:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
}

可以看到,ServletWebServerFactoryAutoConfiguration自动配置类中有一个@EnableConfigurationProperties注解,且注解值是前面讲的ServerProperties.class,因此@EnableConfigurationProperties注解肯定就是关注的重点了。

再来看下@EnableConfigurationProperties注解的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

/**
* Convenient way to quickly register {@link ConfigurationProperties} annotated beans
* with Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@link ConfigurationProperties} annotated beans to register
*/
// 这个值指定的类就是@ConfigurationProperties注解标注的类,其将会被注册到spring容器中
Class<?>[] value() default {};

}

@EnableConfigurationProperties注解的主要作用就是为@ConfigurationProperties注解标注的类提供支持,即对将外部配置属性值(比如application.properties配置值)绑定到@ConfigurationProperties标注的类的属性中。

SpringBoot源码中还存在了ConfigurationPropertiesAutoConfiguration这个自动配置类,同时spring.factories配置文件中的EnableAutoConfiguration接口也配置了ConfigurationPropertiesAutoConfiguration,这个自动配置类上也有@EnableConfigurationProperties这个注解,堆属性绑定进行了默认开启。

那么,@EnableConfigurationProperties这个注解对属性绑定提供怎样的支持呢?

可以看到@EnableConfigurationProperties这个注解上还标注了@Import(EnableConfigurationPropertiesImportSelector.class),其导入了EnableConfigurationPropertiesImportSelector,因此可以肯定的是@EnableConfigurationProperties这个注解对属性绑定提供的支持必定跟EnableConfigurationPropertiesImportSelector有关。

接下来分析EnableConfigurationPropertiesImportSelector,是如何承担将外部配置属性值绑定到@ConfigurationProperties标注的类的属性中的。

2.EnableConfigurationPropertiesImportSelector

EnableConfigurationPropertiesImportSelector类的作用主要用来处理外部属性绑定的相关逻辑,其实现了ImportSelector接口的selectImports方法可以向容器中注册bean。

那么,看下EnableConfigurationPropertiesImportSelector覆写的selectImports方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class EnableConfigurationPropertiesImportSelector implements ImportSelector {

// IMPORTS数组即是要向spring容器中注册的bean
private static final String[] IMPORTS = {
ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 返回ConfigurationPropertiesBeanRegistrar和 ConfigurationPropertiesBindingPostProcessorRegistrar的全限定名
// 即上面两个类将会被注册到Spring容器中
return IMPORTS;
}
}

可以看到EnableConfigurationPropertiesImportSelector类中的selectImports方法中返回的是IMPORTS数组,而这个IMPORTS是一个常量数组,值是ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar。即EnableConfigurationPropertiesImportSelector的作用是向Spring容器中注册了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar这两个bean

EnableConfigurationPropertiesImportSelector类中没看到处理外部属性绑定的相关逻辑,其只是注册了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar这两个bean,接下来我们再看下注册的这两个bean类。

2.1 ConfigurationPropertiesBeanRegistrar

先来看下ConfigurationPropertiesBeanRegistrar这个类的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {

// metadata是 AnnotationMetadata 对象,存储了某个配置类的元数据
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// (1)得到 @EnableConfigurationProperties注解的所有属性值,
// 比如 @EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
// (2)然后再将得到的@EnableConfigurationProperties注解的所有属性值注册到容器中
getTypes(metadata).forEach((type) -> register(registry,
(ConfigurableListableBeanFactory) registry, type));
}

...
}

ConfigurationPropertiesBeanRegistrarEnableConfigurationPropertiesImportSelector的内部类,其实现了ImportBeanDefinitionRegistrar接口,覆写了registerBeanDefinitions方法。可见,ConfigurationPropertiesBeanRegistrar又是用来注册一些bean definition的,即也是向Spring容器中注册一些bean。

ConfigurationPropertiesBeanRegistrar实现的registerBeanDefinitions中,可以看到主要做了两件事:

  1. 调用getTypes方法获取@EnableConfigurationProperties注解的属性值XxxProperties
  2. 调用register方法将获取的属性值XxxProperties注册到Spring容器中,用于以后和外部属性绑定时使用。

来看下getTypes方法的源码:

1
2
3
4
5
6
7
8
9
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
// 得到 @EnableConfigurationProperties注解的所有属性值,
// 比如 @EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
EnableConfigurationProperties.class.getName(), false);
// 将属性值取出装进List集合并返回
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
}

getTypes方法里面的逻辑很简单,即将@EnableConfigurationProperties注解里面的属性值XxxProperties(比如ServerProperties.class)取出并装进List集合并返回。

getTypes方法拿到@EnableConfigurationProperties注解里面的属性值XxxProperties(比如ServerProperties.class)后,此时再遍历将XxxProperties逐个注册进Spring容器中,来看下register方法:

1
2
3
4
5
6
7
8
9
10
private void register(BeanDefinitionRegistry registry,
ConfigurableListableBeanFactory beanFactory, Class<?> type) {
// 得到type的名字,一般用类的全限定名作为bean name
String name = getName(type);
// 根据bean name判断beanFactory容器中是否包含该bean
if (!containsBeanDefinition(beanFactory, name)) {
// 若不包含,那么注册bean definition
registerBeanDefinition(registry, name, type);
}
}

2.2 ConfigurationPropertiesBindingPostProcessorRegistrar

可以看到ConfigurationPropertiesBindingPostProcessorRegistrar类名字又是以Registrar单词为结尾,说明其肯定又是导入一些bean definition的。直接看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class ConfigurationPropertiesBindingPostProcessorRegistrar
implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 若容器中没有注册ConfigurationPropertiesBindingPostProcessor这个处理属性绑定的后置处理器,
// 那么将注册ConfigurationPropertiesBindingPostProcessor和ConfigurationBeanFactoryMetadata这两个bean
// 注意onApplicationEnvironmentPreparedEvent事件加载配置属性在先,然后再注册一些后置处理器用来处理这些配置属性
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
// (1)注册ConfigurationPropertiesBindingPostProcessor后置处理器,用来对配置属性进行后置处理
registerConfigurationPropertiesBindingPostProcessor(registry);
// (2)注册一个ConfigurationBeanFactoryMetadata类型的bean,
// 注意ConfigurationBeanFactoryMetadata实现了BeanFactoryPostProcessor,然后其会在postProcessBeanFactory中注册一些元数据
registerConfigurationBeanFactoryMetadata(registry);
}
}

// 注册ConfigurationPropertiesBindingPostProcessor后置处理器
private void registerConfigurationPropertiesBindingPostProcessor(
BeanDefinitionRegistry registry) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(
ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);

}

// 注册ConfigurationBeanFactoryMetadata后置处理器
private void registerConfigurationBeanFactoryMetadata(
BeanDefinitionRegistry registry) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME,
definition);
}

}

ConfigurationPropertiesBindingPostProcessorRegistrar类的逻辑非常简单,主要用来注册外部配置属性绑定相关的后置处理器即ConfigurationBeanFactoryMetadataConfigurationPropertiesBindingPostProcessor

那么接下来再来探究下注册的这两个后置处理器又是执行怎样的后置处理逻辑呢?

2.2.1 ConfigurationBeanFactoryMetadata

先来看下ConfigurationBeanFactoryMetadata类源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor {

/**
* The bean name that this class is registered with.
*/
public static final String BEAN_NAME = ConfigurationBeanFactoryMetadata.class.getName();

private ConfigurableListableBeanFactory beanFactory;

/**
* beansFactoryMetadata集合存储beansFactory的元数据
* key:某个bean的名字 value:FactoryMetadata对象(封装了工厂bean名和工厂方法名)
* 比如下面这个配置类:
*
* @Configuration
* public class ConfigA {
* @Bean
* public BeanXXX methodB(configA, ) {
* return new BeanXXX();
* }
* }
*
* 那么:key值为"methodB",value为FactoryMetadata(configA, methodB)对象,其bean属性值为"configA",method属性值为"methodB"
*/
private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.beanFactory = beanFactory;
// 遍历beanFactory的beanDefinitionName,即每个bean的名字(比如工厂方法对应的bean名字)
for (String name : beanFactory.getBeanDefinitionNames()) {
// 根据name得到beanDefinition
BeanDefinition definition = beanFactory.getBeanDefinition(name);
// 工厂方法名:一般是注解@Bean的方法名
String method = definition.getFactoryMethodName();
// 工厂bean名:一般是注解@Configuration的类名
String bean = definition.getFactoryBeanName();
if (method != null && bean != null) {
// 将beanDefinitionName作为Key,封装了工厂bean名和工厂方法名的FactoryMetadata对象作为value装入beansFactoryMetadata中
this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));
}
}
}
}

ConfigurationBeanFactoryMetadata这个后置处理器,其实现了BeanFactoryPostProcessor接口的postProcessBeanFactory方法,在初始化bean factory时将@Bean注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用。

从上面代码可以看到ConfigurationBeanFactoryMetadata类覆写的postProcessBeanFactory方法做的事情就是将工厂Bean(可以理解为@Configuration注解的类)及其@Bean注解的工厂方法的一些元数据缓存到beansFactoryMetadata集合中,以便后续使用。

由上代码中我们看到了ConfigurationBeanFactoryMetadata类的beansFactoryMetadata集合类型是Map<String, FactoryMetadata>,那么我们再来看下封装相关工厂元数据的FactoryMetadata类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static class FactoryMetadata {

// @Configuration注解的配置类的类名
private final String bean;
// @Bean注解的方法名
private final String method;

FactoryMetadata(String bean, String method) {
this.bean = bean;
this.method = method;
}

public String getBean() {
return this.bean;
}

public String getMethod() {
return this.method;
}

}

FactoryMetadata仅有两个属性beanmethod,分别表示@Configuration注解的工厂bean@Bean注解的工厂方法。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* beansFactoryMetadata集合存储beansFactory的元数据
* key:某个bean的名字 valueFactoryMetadata对象(封装了工厂bean名和工厂方法名)
* 比如下面这个配置类:
*
* @Configuration
* public class ConfigA {
* @Bean
* public BeanXXX methodB(configA, ) {
* return new BeanXXX();
* }
* }
*
* 那么:key值为"methodB"valueFactoryMetadata(configA, methodB)对象,其bean属性值为"configA",method属性值为"methodB"
*/
private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

为了更好理解上面beansFactoryMetadata集合存储的数据是啥,最好动手调试看看里面装的是什么。总之这里记住一点就好了:ConfigurationBeanFactoryMetadata类的beansFactoryMetadata集合存储的是工厂bean的相关元数据,以便在ConfigurationPropertiesBindingPostProcessor后置处理器中使用。

2.2.2 ConfigurationPropertiesBindingPostProcessor

再来看下ConfigurationPropertiesBindingPostProcessorRegistrar类注册的另外一个后置处理器ConfigurationPropertiesBindingPostProcessor,这个后置处理器主要承担了将外部配置属性绑定到@ConfigurationProperties注解标注的XxxProperties类的属性中(比如application.properties配置文件中设置了server.port=9999,那么9999将会绑定到ServerProperties类的port属性中)的实现逻辑。

先来看下ConfigurationPropertiesBindingPostProcessor的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
PriorityOrdered, ApplicationContextAware, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// ...这里省略实现代码先
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// ...这里省略实现代码先
}

// ...省略非关键代码
}

可以看到ConfigurationPropertiesBindingPostProcessor后置处理器实现了两个重要的接口InitializingBeanBeanPostProcessor

  1. InitializingBean接口的afterPropertiesSet方法会在bean属性赋值后调用,用来执行一些自定义的初始化逻辑比如检查某些强制的属性是否有被赋值,校验某些配置或给一些未被赋值的属性赋值。
  2. BeanPostProcessor接口是bean的后置处理器,其有postProcessBeforeInitializationpostProcessAfterInitialization两个勾子方法,分别会在bean初始化前后被调用来执行一些后置处理逻辑,比如检查标记接口或是否用代理包装了bean

同时以上代码可以看到ConfigurationPropertiesBindingPostProcessor后置处理器覆写了InitializingBeanafterPropertiesSet方法和BeanPostProcessorpostProcessBeforeInitialization方法。

接下来再来探究ConfigurationPropertiesBindingPostProcessor后置处理器覆写的两个方法的源码。

2.2.2.1 在执行外部属性绑定逻辑前先准备好相关元数据和配置属性绑定器

先来分析下ConfigurationPropertiesBindingPostProcessor覆写InitializingBean接口的afterPropertiesSet方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这里主要是给 beanFactoryMetadata 和 configurationPropertiesBinder 的属性赋值,用于后面的后置处理器方法处理属性绑定的时候用
@Override
public void afterPropertiesSet() throws Exception {
// We can't use constructor injection of the application context because
// it causes eager factory bean initialization
// 【1】利用afterPropertiesSet这个勾子方法从容器中获取之前注册的ConfigurationBeanFactoryMetadata对象赋给beanFactoryMetadata属性
// (问1)beanFactoryMetadata这个bean是什么时候注册到容器中的?
// (答1)在ConfigurationPropertiesBindingPostProcessorRegistrar类的registerBeanDefinitions方法中将beanFactoryMetadata这个bean注册到容器中
// (问2)从容器中获取beanFactoryMetadata对象后,什么时候会被用到?
// (答2)beanFactoryMetadata对象的beansFactoryMetadata集合保存的工厂bean相关的元数据,在ConfigurationPropertiesBindingPostProcessor类
// 要判断某个bean是否有FactoryAnnotation或FactoryMethod时会根据这个beanFactoryMetadata对象的beansFactoryMetadata集合的元数据来查找
this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME, ConfigurationBeanFactoryMetadata.class);
// 【2】new一个ConfigurationPropertiesBinder,用于后面的外部属性绑定时使用
// VALIDATOR_BEAN_NAME="configurationPropertiesValidator"
this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(this.applicationContext, VALIDATOR_BEAN_NAME);
}

可以看到以上代码主要逻辑就是在执行外部属性绑定逻辑前先准备好相关元数据和配置属性绑定器,即从Spring容器中获取到之前注册的ConfigurationBeanFactoryMetadata对象赋给ConfigurationPropertiesBindingPostProcessor后置处理器的beanFactoryMetadata属性,还有就是新建一个ConfigurationPropertiesBinder配置属性绑定器对象并赋值给configurationPropertiesBinder属性。

再来看下ConfigurationPropertiesBinder这个配置属性绑定器对象是如何构造的。

1
2
3
4
5
6
7
8
9
10
ConfigurationPropertiesBinder(ApplicationContext applicationContext, String validatorBeanName) {
this.applicationContext = applicationContext;
// 将applicationContext封装到PropertySourcesDeducer对象中并返回
// 获取属性源,主要用于在ConfigurableListableBeanFactory的后置处理方法postProcessBeanFactory中处理
this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
// 如果没有配置validator的话,这里一般返回的是null
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext, validatorBeanName);
// 检查实现JSR-303规范的bean校验器相关类在classpath中是否存在
this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
}

可以看到在构造ConfigurationPropertiesBinder对象时主要给其相关属性赋值(一般构造器逻辑都是这样):

  1. applicationContext属性赋值注入上下文对象;
  2. propertySources属性赋值,属性源即外部配置值比如application.properties配置的属性值,注意这里的属性源是由ConfigFileApplicationListener这个监听器负责读取的。
  3. configurationPropertiesValidator属性赋值,值来自Spring容器中名为configurationPropertiesValidatorbean
  4. jsr303Present属性赋值,当javax.validation.Validator,javax.validation.ValidatorFactoryjavax.validation.bootstrap.GenericBootstrap"这三个类同时存在于classpathjsr303Present属性值才为true

关于JSR303JSR-303是JAVA EE 6中的一项子规范,叫做Bean ValidationHibernate ValidatorBean Validation的参考实现 。Hibernate Validator提供了JSR 303规范中所有内置constraint 的实现,除此之外还有一些附加的constraint

2.2.2.2 执行真正的外部属性绑定逻辑【主线】

前面分析了那么多,发现都还没到外部属性绑定的真正处理逻辑,前面步骤都是在做一些准备性工作,为外部属性绑定做铺垫。

在执行外部属性绑定逻辑前,准备好了相关元数据和配置属性绑定器后,此时再来看看ConfigurationPropertiesBindingPostProcessor实现BeanPostProcessor接口的postProcessBeforeInitialization后置处理方法了,外部属性绑定逻辑都是在这个后置处理方法里实现。

直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 因为是外部配置属性后置处理器,因此这里对@ConfigurationProperties注解标注的XxxProperties类进行后置处理完成属性绑定
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 注意,BeanPostProcessor后置处理器默认会对所有的bean进行处理,因此需要根据bean的一些条件进行过滤得到最终要处理的目的bean,
// 这里的过滤条件就是判断某个bean是否有@ConfigurationProperties注解
//1】从bean上获取@ConfigurationProperties注解,若bean有标注,那么返回该注解;若没有,则返回Null。
// 比如ServerProperty上标注了@ConfigurationProperties注解
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
//2】若标注有@ConfigurationProperties注解的bean,那么则进行进一步处理:将配置文件的配置注入到bean的属性值中
if (annotation != null) {
/********主线,重点关注】********/
bind(bean, beanName, annotation);
}
//3】返回外部配置属性值绑定后的bean(一般是XxxProperties对象)
return bean;
}

ConfigurationPropertiesBindingPostProcessor类覆写的postProcessBeforeInitialization方法的做的事情就是将外部属性配置绑定到@ConfigurationProperties注解标注的XxxProperties类上,关键步骤总结如下:

  1. bean上获取@ConfigurationProperties注解;
  2. 若标注有@ConfigurationProperties注解的bean,那么则进行进一步的处理:将外部配置属性值绑定到bean的属性值中后再返回bean;若没有标注有@ConfigurationProperties注解的bean,那么将直接原样返回bean

注意:后置处理器默认会对每个容器中的bean进行后置处理,因为这里只针对标注有@ConfigurationProperties注解的bean进行外部属性绑定,因此没有标注@ConfigurationProperties注解的bean将不会被处理。

接下来再来看看外部配置属性是如何绑定到@ConfigurationProperties注解的XxxProperties类属性上的呢?

直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
// 【1】得到bean的类型,比如ServerPropertie这个bean得到的类型是:org.springframework.boot.autoconfigure.web.ServerProperties
ResolvableType type = getBeanType(bean, beanName);
// 【2】获取bean上标注的@Validated注解
Validated validated = getAnnotation(bean, beanName, Validated.class);
// 若标注有@Validated注解的话则跟@ConfigurationProperties注解一起组成一个Annotation数组
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated } : new Annotation[] { annotation };
// 【3】返回一个绑定了XxxProperties类的Bindable对象target,这个target对象即被外部属性值注入的目标对象
// (比如封装了标注有@ConfigurationProperties注解的ServerProperties对象的Bindable对象)
// 设置annotations属性数组
Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
try {
// 【4】执行外部配置属性绑定逻辑
/********【主线,重点关注】********/
this.configurationPropertiesBinder.bind(target);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
}
}

前面分析了解到 ConfigurationBeanFactoryMetadata覆写的postProcessBeanFactory方法里已经将相关工厂bean的元数据封装到ConfigurationBeanFactoryMetadata类的beansFactoryMetadata集合。

再来看下上面代码中的【1】getBeanType【2】getAnnotation方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private ResolvableType getBeanType(Object bean, String beanName) {
// 首先获取有没有工厂方法
Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
// 若有工厂方法
if (factoryMethod != null) {
return ResolvableType.forMethodReturnType(factoryMethod);
}
// 没有工厂方法,则说明是普通的配置类
return ResolvableType.forClass(bean.getClass());
}

private <A extends Annotation> A getAnnotation(Object bean, String beanName, Class<A> type) {
A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);
if (annotation == null) {
annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);
}
return annotation;
}

注意到上面代码中的beanFactoryMetadata对象没,ConfigurationPropertiesBindingPostProcessor后置处理器的getBeanTypegetAnnotation方法分别会调用ConfigurationBeanFactoryMetadatafindFactoryMethodfindFactoryAnnotation方法,而ConfigurationBeanFactoryMetadatafindFactoryMethodfindFactoryAnnotation方法又会依赖存储工厂bean元数据的beansFactoryMetadata集合来寻找是否有FactoryMethodFactoryAnnotation。因此,到这里就知道之前ConfigurationBeanFactoryMetadatabeansFactoryMetadata集合存储工厂bean元数据的作用了。

3.ConfigurationPropertiesBinder

分析this.configurationPropertiesBinder.bind(target);这行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void bind(Bindable<?> target) {
//【1】得到@ConfigurationProperties注解
ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);
Assert.state(annotation != null, () -> "Missing @ConfigurationProperties on " + target);
// 【2】得到Validator对象集合,用于属性校验
List<Validator> validators = getValidators(target);
// 【3】得到BindHandler对象(默认是IgnoreTopLevelConverterNotFoundBindHandler对象),
// 用于对ConfigurationProperties注解的ignoreUnknownFields等属性的处理
BindHandler bindHandler = getBindHandler(annotation, validators);
// 【4】得到一个Binder对象,并利用其bind方法执行外部属性绑定逻辑
/********************【主线,重点关注】********************/
getBinder().bind(annotation.prefix(), target, bindHandler);
}

上面代码的主要逻辑是:

  1. 先获取target对象(对应XxxProperties类)上的@ConfigurationProperties注解和校验器(若有);
  2. 然后再根据获取的的@ConfigurationProperties注解和校验器来获得BindHandler对象,BindHandler的作用是用于在属性绑定时来处理一些附件逻辑;
  3. 最后再获取一个Binder对象,调用其bind方法来执行外部属性绑定的逻辑。

3.1 获取BindHandler对象以便在属性绑定时来处理一些附件逻辑

在看getBindHandler方法的逻辑前先来认识下BindHandler是干什么的。

BindHandler是一个父类接口,用于在属性绑定时来处理一些附件逻辑。先看下BindHandler的类图,好有一个整体的认识:

sb21.png

可以看到AbstractBindHandler作为抽象基类实现了BindHandler接口,其有四个具体的子类分别是IgnoreTopLevelConverterNotFoundBindHandler,NoUnboundElementsBindHandler,IgnoreErrorsBindHandlerValidationBindHandler

  1. IgnoreTopLevelConverterNotFoundBindHandler:在处理外部属性绑定时的默认BindHandler,当属性绑定失败时会忽略最顶层的ConverterNotFoundException
  2. NoUnboundElementsBindHandler:用来处理配置文件配置的未知的属性;
  3. IgnoreErrorsBindHandler:用来忽略无效的配置属性例如类型错误;
  4. ValidationBindHandler:利用校验器对绑定的结果值进行校验。

分析完类关系后,再来看下BindHandler接口提供了哪些方法在外部属性绑定时提供一些额外的处理逻辑,直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public interface BindHandler {

/**
* Default no-op bind handler.
*/
BindHandler DEFAULT = new BindHandler() {

};

/**
* Called when binding of an element starts but before any result has been determined.
* @param <T> the bindable source type
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @return the actual item that should be used for binding (may be {@code null})
*/
// onStart方法在外部属性绑定前被调用
default <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
BindContext context) {
return target;
}

/**
* Called when binding of an element ends with a successful result. Implementations
* may change the ultimately returned result or perform addition validation.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the bound result (never {@code null})
* @return the actual result that should be used (may be {@code null})
*/
// onSuccess方法在外部属性成功绑定时被调用,该方法能够改变最终返回的属性值或对属性值进行校验
default Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
return result;
}

/**
* Called when binding fails for any reason (including failures from
* {@link #onSuccess} calls). Implementations may choose to swallow exceptions and
* return an alternative result.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param error the cause of the error (if the exception stands it may be re-thrown)
* @return the actual result that should be used (may be {@code null}).
* @throws Exception if the binding isn't valid
*/
// onFailure方法在外部属性绑定失败(包括onSuccess方法里的逻辑执行失败)时被调用,
// 该方法可以用来catch住相关异常或者返回一个替代的结果(跟微服务的降级结果有点类似)
default Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
throw error;
}

/**
* Called when binding finishes, regardless of whether the property was bound or not.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the bound result (may be {@code null})
* @throws Exception if the binding isn't valid
*/
// 当外部属性绑定结束时(不管绑定成功还是失败)被调用
default void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
}

}

可以看到BindHandler接口定义了onStart,onSuccess,onFailureonFinish方法,这四个方法分别会在执行外部属性绑定时的不同时段被调用,在属性绑定时用来添加一些额外的处理逻辑,比如在onSuccess方法改变最终绑定的属性值或对属性值进行校验,在onFailure方法catch住相关异常或者返回一个替代的绑定的属性值。

知道了BindHandler是在属性绑定时添加一些额外的处理逻辑后,再来看下getBindHandler方法的逻辑,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 注意BindHandler的设计技巧,应该是责任链模式,非常巧妙,值得借鉴
private BindHandler getBindHandler(ConfigurationProperties annotation, List<Validator> validators) {
// 新建一个IgnoreTopLevelConverterNotFoundBindHandler对象,这是个默认的BindHandler对象
BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
// 若注解@ConfigurationProperties的ignoreInvalidFields属性设置为true,
// 则说明可以忽略无效的配置属性例如类型错误,此时新建一个IgnoreErrorsBindHandler对象
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
// 若注解@ConfigurationProperties的ignoreUnknownFields属性设置为true,
// 则说明配置文件配置了一些未知的属性配置,此时新建一个ignoreUnknownFields对象
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
// 如果@Valid注解不为空,则创建一个ValidationBindHandler对象
if (!validators.isEmpty()) {
handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
}
// 遍历获取的ConfigurationPropertiesBindHandlerAdvisor集合,
// ConfigurationPropertiesBindHandlerAdvisor目前只在测试类中有用到
for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
// 对handler进一步处理
handler = advisor.apply(handler);
}
// 返回handler
return handler;
}

getBindHandler方法的逻辑很简单,主要是根据传入的@ConfigurationProperties注解和validators校验器来创建不同的BindHandler具体实现类:

  1. 首先new一个IgnoreTopLevelConverterNotFoundBindHandler作为默认的BindHandler;
  2. @ConfigurationProperties注解的属性ignoreInvalidFields值为true,那么再new一个IgnoreErrorsBindHandler对象,把刚才新建的IgnoreTopLevelConverterNotFoundBindHandler对象作为构造参数传入赋值给AbstractBindHandler父类的parent属性;
  3. @ConfigurationProperties注解的属性ignoreUnknownFields值为false,那么再new一个UnboundElementsSourceFilter对象,把之前构造的BindHandler对象作为构造参数传入赋值给AbstractBindHandler父类的parent属性;
  4. ……以此类推,前一个handler对象作为后一个hangdler对象的构造参数,就这样利用AbstractBindHandler父类的parent属性将每一个handler链起来,最后再得到最终构造的handler

GET技巧:上面的这个设计模式是不是很熟悉,这个就是责任链模式。学习源码,同时也是学习别人怎么熟练运用设计模式。责任链模式的应用案例有很多,比如Dubbo的各种Filter们(比如AccessLogFilter是用来记录服务的访问日志的,ExceptionFilter是用来处理异常的…),一开始学习java web时的ServletFilter,MyBatisPlugin们以及NettyPipeline都采用了责任链模式。

了解了BindHandler的作用后,再来紧跟主线,看属性绑定是如何绑定的?

3.2 获取Binder对象用于进行属性绑定【主线】

这里分析 ConfigurationPropertiesBinder代码中标注【4】的主线代码getBinder().bind(annotation.prefix(), target, bindHandler);

可以看到这句代码主要做了两件事:

  1. 调用getBinder方法获取用于属性绑定的Binder对象;
  2. 调用Binder对象的bind方法进行外部属性绑定到@ConfigurationProperties注解的XxxProperties类的属性上。

那么我们先看下getBinder方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Binder getBinder() {
// Binder是一个能绑定ConfigurationPropertySource的容器对象
if (this.binder == null) {
// 新建一个Binder对象,这个binder对象封装了ConfigurationPropertySources,
// PropertySourcesPlaceholdersResolver,ConversionService和PropertyEditorInitializer对象
// getConfigurationPropertySources() 将PropertySources对象封装成SpringConfigurationPropertySources对象并返回
// getPropertySourcesPlaceholdersResolver(), getConversionService() 将PropertySources对象封装成PropertySourcesPlaceholdersResolver对象并返回,从容器中获取到ConversionService对象
// getPropertyEditorInitializer() 得到Consumer<PropertyEditorRegistry>对象,这些初始化器用来配置property editors,property editors通常可以用来转换值
this.binder = new Binder(getConfigurationPropertySources(),
getPropertySourcesPlaceholdersResolver(), getConversionService(),
getPropertyEditorInitializer());
}
// 返回binder
return this.binder;
}

可以看到Binder对象封装了ConfigurationPropertySources,PropertySourcesPlaceholdersResolver,ConversionServicePropertyEditorInitializer这四个对象,Binder对象封装了这四个肯定是在后面属性绑定逻辑中会用到,先看下这四个对象是干嘛的:

  1. ConfigurationPropertySources:外部配置文件的属性源,由ConfigFileApplicationListener监听器负责触发读取;
  2. PropertySourcesPlaceholdersResolver:解析属性源中的占位符${}
  3. ConversionService:对属性类型进行转换
  4. PropertyEditorInitializer:用来配置property editors

获取了Binder属性绑定器后,再来看下它的bind方法是如何执行属性绑定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) {
// ConfigurationPropertyName.of(name):将name(这里指属性前缀名)封装到ConfigurationPropertyName对象中
// 将外部配置属性绑定到目标对象target中
return bind(ConfigurationPropertyName.of(name), target, handler);
}

public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(target, "Target must not be null");
handler = (handler != null) ? handler : BindHandler.DEFAULT;
// Context是Binder的内部类,实现了BindContext,Context可以理解为Binder的上下文,可以用来获取binder的属性比如Binder的sources属性
Context context = new Context();
// 进行属性绑定,并返回绑定属性后的对象bound,注意bound的对象类型是T,T就是@ConfigurationProperties注解的类比如ServerProperties
/********【主线,重点关注】************/
T bound = bind(name, target, handler, context, false);
// 将刚才返回的bound对象封装到BindResult对象中并返回
return BindResult.of(bound);
}

上面代码中首先创建了一个Context对象,ContextBinder的内部类,为Binder的上下文,利用Context上下文可以获取Binder的属性比如获取Bindersources属性值并绑定到XxxProperties属性中。然后再紧跟主线看下 bind(name, target, handler, context, false)方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) {
// 清空Binder的configurationProperty属性值
context.clearConfigurationProperty();
try {
// 【1】调用BindHandler的onStart方法,执行一系列的责任链对象的该方法
target = handler.onStart(name, target, context);
if (target == null) {
return null;
}
// 【2】调用bindObject方法对Bindable对象target的属性进行绑定外部配置的值,并返回赋值给bound对象。
// 例如:比如设置了server.port=9999,那么该方法最终会调用Binder.bindProperty方法,最终返回的bound的value值为9999
/************【主线:重点关注】***********/
Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
// 【3】封装handleBindResult对象并返回,注意在handleBindResult的构造函数中会调用BindHandler的onSucess,onFinish方法
return handleBindResult(name, target, handler, context, bound);
}
catch (Exception ex) {
return handleBindError(name, target, handler, context, ex);
}
}

接着紧跟主线来看看bindObject方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) {
// 从propertySource中的配置属性,获取ConfigurationProperty对象property即application.properties配置文件中若有相关的配置的话,
// 那么property将不会为null。例如:假如你在配置文件中配置了spring.profiles.active=dev,那么相应property值为dev;否则为null
ConfigurationProperty property = findProperty(name, context);
// 若property为null,则不会执行后续的属性绑定相关逻辑
if (property == null && containsNoDescendantOf(context.getSources(), name)) {
// 如果property == null,则返回null
return null;
}
// 根据target类型获取不同的Binder,可以是null(普通的类型一般是Null),MapBinder,CollectionBinder或ArrayBinder
AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
// 若aggregateBinder不为null比如配置了spring.profiles属性(当然包括其子属性比如spring.profiles.active等)
if (aggregateBinder != null) {
// 若aggregateBinder不为null,则调用bindAggregate并返回绑定后的对象
return bindAggregate(name, target, handler, context, aggregateBinder);
}
// 若property不为null
if (property != null) {
try {
// 绑定属性到对象中,比如配置文件中设置了server.port=8888,那么将会最终调用bindProperty方法进行属性设置
return bindProperty(target, context, property);
}
catch (ConverterNotFoundException ex) {
// We might still be able to bind it as a bean
Object bean = bindBean(name, target, handler, context, allowRecursiveBinding);
if (bean != null) {
return bean;
}
throw ex;
}
}
// 只有@ConfigurationProperties注解的类进行外部属性绑定才会走这里
/***********************【主线,重点关注】****************************/
return bindBean(name, target, handler, context, allowRecursiveBinding);
}

由上代码中可以看到bindObject中执行属性绑定的逻辑会根据不同的属性类型进入不同的绑定逻辑中,例如:

  1. application.properties配置文件中配置了spring.profiles.active=dev的话,那么将会进入return bindAggregate(name, target, handler, context, aggregateBinder);这个属性绑定的代码逻辑;
  2. application.properties配置文件中配置了server.port=9999的话,那么将会进入return bindBean(name, target, handler, context, allowRecursiveBinding);的属性绑定的逻辑。

再次紧跟主线,进入@ConfigurationProperties注解的XxxProperties类的属性绑定逻辑中的bindBean方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// name指的是ConfigurationProperties的前缀名
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
BindHandler handler, Context context, boolean allowRecursiveBinding) {
// 这里做一些ConfigurationPropertyState的相关检查
if (containsNoDescendantOf(context.getSources(), name)
|| isUnbindableBean(name, target, context)) {
return null;
}
// 这里新建一个BeanPropertyBinder的实现类对象,注意这个对象实现了bindProperty方法
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
name.append(propertyName), propertyTarget, handler, context, false);
/**
* (propertyName, propertyTarget) -> bind(
* name.append(propertyName), propertyTarget, handler, context, false);
* 等价于
* new BeanPropertyBinder() {
* Object bindProperty(String propertyName, Bindable<?> target){
* bind(name.append(propertyName), propertyTarget, handler, context, false);
* }
* }
*/
// type类型即@ConfigurationProperties注解标注的XxxProperties类
Class<?> type = target.getType().resolve(Object.class);
if (!allowRecursiveBinding && context.hasBoundBean(type)) {
return null;
}
// 这里应用了java8的lambda语法,作为没怎么学习java8的lambda语法的我,不怎么好理解下面的逻辑,哈哈
// 真正实现将外部配置属性绑定到@ConfigurationProperties注解的XxxProperties类的属性中的逻辑应该就是在这句lambda代码了
/*******************【主线】***************************/
return context.withBean(type, () -> {
Stream<?> boundBeans = BEAN_BINDERS.stream()
.map((b) -> b.bind(name, target, context, propertyBinder));
return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
});
// 根据上面的lambda语句翻译如下:
/** 这里的T指的是各种属性绑定对象,比如ServerProperties
* return context.withBean(type, new Supplier<T>() {
* T get() {
* Stream<?> boundBeans = BEAN_BINDERS.stream()
* .map((b) -> b.bind(name, target, context, propertyBinder));
* return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
* }
* });
*/
}

从上面代码中,追根究底来到了外部配置属性绑定到XxxProperties类属性中的比较底层的代码了,可以看到属性绑定的逻辑应该就在上面代码标注【主线】lambda代码处了。这个属于SpringBoot的属性绑定Binder的范畴,Binder相关类是SpringBoot2.0才出现的,即对之前的属性绑定相关代码进行推翻重写了。

总结

好了,外部配置属性值是如何被绑定到XxxProperties类属性上的源码分析就到此结束了,重要步骤总结:

  1. 首先是@EnableConfigurationProperties注解importEnableConfigurationPropertiesImportSelector后置处理器;
  2. EnableConfigurationPropertiesImportSelector后置处理器又向Spring容器中注册了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar这两个bean
  3. 其中ConfigurationPropertiesBeanRegistrarSpring容器中注册了XxxProperties类型的beanConfigurationPropertiesBindingPostProcessorRegistrarSpring容器中注册了ConfigurationBeanFactoryMetadataConfigurationPropertiesBindingPostProcessor两个后置处理器;
  4. ConfigurationBeanFactoryMetadata后置处理器在初始化bean factory时将@Bean注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用;
  5. ConfigurationPropertiesBindingPostProcessor后置处理器将外部配置属性值绑定到XxxProperties类属性的逻辑委托给ConfigurationPropertiesBinder对象,然后ConfigurationPropertiesBinder对象又最终将属性绑定的逻辑委托给Binder对象来完成。

可见,重要的是上面的第5步

参考资料

  1. https://www.jianshu.com/p/554533f88370

评论加载中