该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读

Spring Boot 版本:2.2.x

最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章

如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》

概述

我们的 Spring Boot 应用经常会在 application.yml 配置文件里面配置一些自定义的配置,对于不同环境设置不同的值,然后可以通过 @ConfigurationProperties 注解将这些配置作为 Spring Bean 的属性值进行注入,那么本文来简单分析一下这个注解是如何将配置自动设置到 Spring Bean 的。

在开始之前,结合我前面的这么多 Spring 相关的源码分析文章,想必你会知道原理的,无非就是在 Spring Bean 的加载过程的某个阶段(大概率是初始化的时候)通过 BeanPostProcessor 解析该注解,并获取对应的属性值设置到其中。

先来看看这个注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
/**
* 指定的配置项前缀
*/
@AliasFor("prefix")
String value() default ""; /**
* 指定的配置项前缀
*/
@AliasFor("value")
String prefix() default ""; /**
* 是否忽略无效的字段
*/
boolean ignoreInvalidFields() default false; /**
* 是否忽略不知道的字段
*/
boolean ignoreUnknownFields() default true;
}

使用方式有两种:

  • @ConfigurationProperties + @Component 注解(一个类)
  • @EnableConfigurationProperties(某个 Bean)+ @ConfigurationProperties 注解(另一个普通类)

第二种方式和第一种原理都是一样的,不过第二种方式会注册一个 BeanPostProcessor 用于处理带有 @ConfigurationProperties 注解的 Spring Bean,同时会将指定的 Class 们解析出 BeanDefinition(Bean 的前身)并注册,这也就是为什么第二种不用标注 @Component 注解

那么第一种方式在哪注册的 BeanPostProcessor 呢?因为 Spring Boot 有一个 ConfigurationPropertiesAutoConfiguration 自动配置类,如下:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration { }

很简单,也是通过 @EnableConfigurationProperties 注解注册的这个 BeanPostProcessor 对象

这里有一个疑问,为什么 @ConfigurationProperties 注解上面不直接加一个 @Component 注解呢?

可能是因为这个注解的作用就是让 配置类 外部化配置吧

@EnableConfigurationProperties

org.springframework.boot.context.properties.EnableConfigurationProperties,支持将指定的带有 @ConfigurationProperties 注解的类解析出 BeanDefinition(Bean 的前身)并注册,同时注册一个 BeanPostProcessor 去处理带有 @ConfigurationProperties 注解的 Bean

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties { /**
* The bean name of the configuration properties validator.
* @since 2.2.0
*/
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator"; /**
* 指定的 Class 类对象们
*/
Class<?>[] value() default {};
}

可以看到这个注解也是通过 @Import 注解来驱动某个功能的,是不是发现 @EnableXxx 驱动注解都是以这样的方式来实现的

那么关于 @Import 注解的实现原理我在很多地方都提到过,这里再提一下,模块驱动注解通常需要结合 @Configuration 注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @Import 注解后则进行处理,对于 @Import 注解的值有三种情况:

  1. 该 Class 对象实现了 ImportSelector 接口,调用它的 selectImports(..) 方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理

    • 该 Class 对象实现了 DeferredImportSelector 接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持 @Order 排序
  2. 该 Class 对象实现了 ImportBeanDefinitionRegistrar 接口,会调用它的 registerBeanDefinitions(..) 方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)

  3. 该 Class 对象是一个 @Configuration 配置类,会将这个类作为一个 Bean 被 Spring IoC 管理

对于 @Import 注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章

这里的 @EnableConfigurationProperties 注解,通过 @Import 导入 EnableConfigurationPropertiesRegistrar 这个类(实现了 ImportBeanDefinitionRegistrar 接口)来实现该功能的,下面会进行分析

EnableConfigurationPropertiesRegistrar

org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar,实现了 ImportBeanDefinitionRegistrar 接口,是 @EnableConfigurationProperties 注解的核心类

class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// <1> 先注册两个内部 Bean
registerInfrastructureBeans(registry);
// <2> 创建一个 ConfigurationPropertiesBeanRegistrar 对象
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
// <3> 获取 `@EnableConfigurationProperties` 注解指定的 Class 类对象们
// <4> 依次注册指定的 Class 类对应的 BeanDefinition
// 这样一来这个 Class 不用标注 `@Component` 就可以注入这个配置属性对象了
getTypes(metadata).forEach(beanRegistrar::register);
} private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
.filter((type) -> void.class != type).collect(Collectors.toSet());
} /**
* 可参考 ConfigurationPropertiesAutoConfiguration 自动配置类
*/
@SuppressWarnings("deprecation")
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
// 注册一个 ConfigurationPropertiesBindingPostProcessor 类型的 BeanDefinition(内部角色),如果不存在的话
// 同时也会注册 ConfigurationPropertiesBinder 和 ConfigurationPropertiesBinder.Factory 两个 Bean,如果不存在的话
ConfigurationPropertiesBindingPostProcessor.register(registry);
// 注册一个 ConfigurationBeanFactoryMetadata 类型的 BeanDefinition(内部角色)
// 这个 Bean 从 Spring 2.2.0 开始就被废弃了
ConfigurationBeanFactoryMetadata.register(registry);
}
}

注册 BeanDefinition(Bean 的前身)的过程如下:

  1. 先注册两个内部 Bean

    • 注册一个 ConfigurationPropertiesBindingPostProcessor 类型的 BeanDefinition(内部角色),如果不存在的话

      public static void register(BeanDefinitionRegistry registry) {
      Assert.notNull(registry, "Registry must not be null");
      // 注册 ConfigurationPropertiesBindingPostProcessor 类型的 BeanDefinition(内部角色)
      if (!registry.containsBeanDefinition(BEAN_NAME)) {
      GenericBeanDefinition definition = new GenericBeanDefinition();
      definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
      definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      registry.registerBeanDefinition(BEAN_NAME, definition);
      }
      // 注册 ConfigurationPropertiesBinder 和 ConfigurationPropertiesBinder.Factory 两个 BeanDefinition(内部角色)
      ConfigurationPropertiesBinder.register(registry);
      }
    • 注册一个 ConfigurationBeanFactoryMetadata 类型的 BeanDefinition(内部角色),从 Spring 2.2.0 开始就被废弃了,忽略掉

  2. 创建一个 ConfigurationPropertiesBeanRegistrar 对象

  3. 获取 @EnableConfigurationProperties 注解指定的 Class 类对象们

  4. 调用 ConfigurationPropertiesBeanRegistrarregister(Class<?> type) 方法,依次注册指定的 Class 类对应的 BeanDefinition,这样一来这个 Class 不用标注 @Component 就可以注入这个配置属性对象了

ConfigurationPropertiesBeanRegistrar

org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrar,是 EnableConfigurationPropertiesRegistrar 的辅助类

final class ConfigurationPropertiesBeanRegistrar {

	private final BeanDefinitionRegistry registry;

	private final BeanFactory beanFactory;

	ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
this.registry = registry;
this.beanFactory = (BeanFactory) this.registry;
} void register(Class<?> type) {
// <1> 先获取这个 Class 类对象的 `@ConfigurationProperties` 注解
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
// <2> 为这个 Class 对象注册一个 BeanDefinition
register(type, annotation);
}
}

过程如下:

  1. 先获取这个 Class 类对象的 @ConfigurationProperties 注解

  2. 调用 register(..) 方法,为这个 Class 对象注册一个 BeanDefinition

    void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
    // <1> 生成一个 Bean 的名称,为 `@ConfigurationProperties` 注解的 `${prefix}-类全面`,或者`类全名`
    String name = getName(type, annotation);
    if (!containsBeanDefinition(name)) {
    // <2> 如果没有该名称的 Bean,则注册一个 `type` 类型的 BeanDefinition
    registerBeanDefinition(name, type, annotation);
    }
    } private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
    String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
    return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
    }
    1. 生成一个 Bean 的名称,为 @ConfigurationProperties 注解的 ${prefix}-类全面,或者类全名
    2. 如果没有该名称的 Bean,则注册一个 type 类型的 BeanDefinition

registerBeanDefinition 方法

注册带有 @ConfigurationProperties 注解的 Class 对象

private void registerBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation) {
// 这个 Class 对象必须有 `@ConfigurationProperties` 注解
Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'.");
// 注册一个 `beanClass` 为 `type` 的 GenericBeanDefinition
this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
} private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
}
// 创建一个 GenericBeanDefinition 对象,设置 Class 为 `type`
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
}

逻辑比较简单,就是将这个 @ConfigurationProperties 注解的 Class 对象生成一个 BeanDefinition 并注册

ConfigurationPropertiesBindingPostProcessor

org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor,将配置绑定到 @ConfigurationProperties 注解的配置类中

public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean { public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName(); /**
* The bean name of the configuration properties validator.
* @deprecated since 2.2.0 in favor of
* {@link EnableConfigurationProperties#VALIDATOR_BEAN_NAME}
*/
@Deprecated
public static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME; /** Spring 应用上下文 */
private ApplicationContext applicationContext; /** BeanDefinition 注册中心 */
private BeanDefinitionRegistry registry; /** 属性绑定器 */
private ConfigurationPropertiesBinder binder; /**
* Create a new {@link ConfigurationPropertiesBindingPostProcessor} instance.
* @deprecated since 2.2.0 in favor of
* {@link EnableConfigurationProperties @EnableConfigurationProperties} or
* {@link ConfigurationPropertiesBindingPostProcessor#register(BeanDefinitionRegistry)}
*/
@Deprecated
public ConfigurationPropertiesBindingPostProcessor() {
}
}

setApplicationContext 方法

ApplicationContextAware 的回调

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// Aware 接口回调,获取 Spring 应用上下文
this.applicationContext = applicationContext;
}

afterPropertiesSet 方法

InitializingBean 初始化方法

/**
* 初始化当前 Bean
*/
@Override
public void afterPropertiesSet() throws Exception {
// We can't use constructor injection of the application context because
// it causes eager factory bean initialization
// 从 Spring 应用上下文获取 BeanDefinition 注册中心
this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
// 获取 ConfigurationPropertiesBinder 这个 Bean,在这个类的 `register` 方法中注册了哦
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
}

getOrder 方法

PriorityOrdered 优先级

// 次于最高优先级
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}

1. postProcessBeforeInitialization 方法

BeanPostProcessor 的初始化前置操作

/**
* 在 Bean 的初始化前会调用这个方法
* 参考 {@link AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization(Object, String)}
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// <1> 先尝试根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含 `@ConfigurationProperties` 注解信息
// <2> 然后开始获取指定 `prefix` 前缀的属性值,设置到这个 Bean 中
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
// <3> 返回属性填充后的 Bean
return bean;
}

过程如下:

  1. 调用 ConfigurationPropertiesBean#get(..) 方法,尝试根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含 @ConfigurationProperties 注解信息
  2. 调用 bind(..) 方法,开始获取指定 prefix 前缀的属性值,设置到这个 Bean 中
  3. 返回属性填充后的 Bean

4. bind 方法

private void bind(ConfigurationPropertiesBean bean) {
// <1> 如果这个 `bean` 为空,或者已经处理过,则直接返回
if (bean == null || hasBoundValueObject(bean.getName())) {
return;
}
// <2> 对 `@ConstructorBinding` 的校验,如果使用该注解但是没有找到合适的构造器,那么在这里抛出异常
Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
+ bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
try {
// <3> 通过 Binder 将指定 `prefix` 前缀的属性值设置到这个 Bean 中,会借助 Conversion 类型转换器进行类型转换,过程复杂,没看懂...
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}

可以看到最后是通过 ConfigurationPropertiesBinder 属性绑定器来将属性绑定到 bean 中的

ConfigurationPropertiesBean

org.springframework.boot.context.properties.ConfigurationPropertiesBean,是 @ConfigurationProperties 注解对应的 Bean 的封装,用于将对应的属性值绑定到这个 Bean 中

public final class ConfigurationPropertiesBean {
/**
* Bean 的名称
*/
private final String name;
/**
* Bean 的实例对象
*/
private final Object instance;
/**
* Bean 的 `@ConfigurationProperties` 注解
*/
private final ConfigurationProperties annotation;
/**
* `@Bean` 对应的方法资源对象,包括实例对象和注解信息
*/
private final Bindable<?> bindTarget;
/**
* `@Bean` 对应的方法
*/
private final BindMethod bindMethod; private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation,
Bindable<?> bindTarget) {
this.name = name;
this.instance = instance;
this.annotation = annotation;
this.bindTarget = bindTarget;
this.bindMethod = BindMethod.forType(bindTarget.getType().resolve());
}
}

参考上面的注释查看每个属性的描述

2. get 方法

获取某个 @ConfigurationProperties 注解对应的 Bean 的 ConfigurationPropertiesBean

public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
// <1> 找到这个 `beanName` 对应的工厂方法,例如 `@Bean` 标注的方法就是一个工厂方法,不是 `@Bean` 的话这里为空
Method factoryMethod = findFactoryMethod(applicationContext, beanName);
// <2> 创建一个 ConfigurationPropertiesBean 对象,包含了这个 Bean 的 `@ConfigurationProperties` 注解信息
return create(beanName, bean, bean.getClass(), factoryMethod);
}

过程如下:

  1. 找到这个 beanName 对应的工厂方法,例如 @Bean 标注的方法就是一个工厂方法,不是 @Bean 的话这里为空
  2. 调用 create(..) 方法,创建一个 ConfigurationPropertiesBean 对象,包含了这个 Bean 的 @ConfigurationProperties 注解信息

3. create 方法

private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
// <1> 找到这个 Bean 上面的 `@ConfigurationProperties` 注解
// 如果是 `@Bean` 标注的方法 Bean,也会尝试从所在的 Class 类上面获取
ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
// <2> 如果没有配置 `@ConfigurationProperties` 注解,则直接返回 `null`
if (annotation == null) {
return null;
}
// <3> 找到这个 Bean 上面的 `@Validated` 注解
Validated validated = findAnnotation(instance, type, factory, Validated.class);
// <4> 将 `@ConfigurationProperties`、`Validated`注解信息,目标 Bean 以及它的 Class 对象,绑定到一个 Bindable 对象中
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
ResolvableType bindType = (factory != null) ? ResolvableType.forMethodReturnType(factory)
: ResolvableType.forClass(type); Bindable<Object> bindTarget = Bindable.of(bindType).withAnnotations(annotations);
if (instance != null) {
bindTarget = bindTarget.withExistingValue(instance);
}
// <5> 将 `beanName`、目标 Bean、`ConfigurationProperties` 注解、第 `4` 步的 Bindable 对象封装到一个 ConfigurationPropertiesBean 对象中
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
}

过程如下:

  1. 找到这个 Bean 上面的 @ConfigurationProperties 注解,如果是 @Bean 标注的方法 Bean,也会尝试从所在的 Class 类上面获取
  2. 如果没有配置 @ConfigurationProperties 注解,则直接返回 null
  3. 找到这个 Bean 上面的 @Validated 注解
  4. @ConfigurationPropertiesValidated注解信息,目标 Bean 以及它的 Class 对象,绑定到一个 Bindable 对象中
  5. beanName、目标 Bean、ConfigurationProperties 注解、第 4 步的 Bindable 对象封装到一个 ConfigurationPropertiesBean 对象中

ConfigurationPropertiesBinder

org.springframework.boot.context.properties.ConfigurationPropertiesBinder,对 ConfigurationPropertiesBean 进行属性绑定

5. bind 方法

对 ConfigurationPropertiesBean 进行属性绑定,如下:

BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
// <1> 获取这个 Bean 的 Bindable 对象(包含了 `@ConfigurationProperties`、`@Validated` 配置信息和这个 Bean)
Bindable<?> target = propertiesBean.asBindTarget();
// <2> 获取这个 Bean 的 `@ConfigurationProperties` 注解信息
ConfigurationProperties annotation = propertiesBean.getAnnotation();
// <3> 获取一个 BindHandler 绑定处理器
BindHandler bindHandler = getBindHandler(target, annotation);
// <4> 获取一个 Binder 对象,包含了 Spring 应用上下文的所有配置信息,占位符处理器,类型转换器
// <5> 通过这个 Binder 将指定 `prefix` 前缀的属性值设置到这个 Bean 中,会借助 Conversion 类型转换器进行类型转换,过程复杂,没看懂...
return getBinder().bind(annotation.prefix(), target, bindHandler);
}

过程如下:

  1. 获取这个 Bean 的 Bindable 对象(包含了 @ConfigurationProperties@Validated 配置信息和这个 Bean)

  2. 获取这个 Bean 的 @ConfigurationProperties 注解信息

  3. 获取一个 BindHandler 绑定处理器

    private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperties annotation) {
    // <1> 获取几个 Validator 校验器
    List<Validator> validators = getValidators(target);
    // <2> 创建一个最顶层的 BindHandler
    BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
    // <3> 如果忽略无效的字段(默认为 `false`)
    if (annotation.ignoreInvalidFields()) {
    handler = new IgnoreErrorsBindHandler(handler);
    }
    // <4> 如果不忽略不知道的字段(默认也不会进入这里)
    if (!annotation.ignoreUnknownFields()) {
    UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
    handler = new NoUnboundElementsBindHandler(handler, filter);
    }
    // <5> 如果检验器不为空,则将其封装成 ValidationBindHandler 对象,里面保存了这几个 Validator
    if (!validators.isEmpty()) {
    handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
    }
    // <6> 获取 ConfigurationPropertiesBindHandlerAdvisor 对 `handler` 应用,暂时忽略
    for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
    handler = advisor.apply(handler);
    }
    // <7> 返回这个 `handler` 配置绑定处理器
    return handler;
    } private List<Validator> getValidators(Bindable<?> target) {
    List<Validator> validators = new ArrayList<>(3);
    if (this.configurationPropertiesValidator != null) {
    validators.add(this.configurationPropertiesValidator);
    }
    if (this.jsr303Present && target.getAnnotation(Validated.class) != null) {
    validators.add(getJsr303Validator());
    }
    if (target.getValue() != null && target.getValue().get() instanceof Validator) {
    validators.add((Validator) target.getValue().get());
    }
    return validators;
    }
  4. 获取一个 Binder 对象,包含了 Spring 应用上下文的所有配置信息,占位符处理器,类型转换器

    private Binder getBinder() {
    if (this.binder == null) {
    this.binder = new Binder(getConfigurationPropertySources(), // Spring 应用的 PropertySource 属性资源
    getPropertySourcesPlaceholdersResolver(), // 占位符处理器
    getConversionService(), // 类型转换器
    getPropertyEditorInitializer(), // 属性编辑器
    null,
    ConfigurationPropertiesBindConstructorProvider.INSTANCE);
    }
    return this.binder;
    }
  5. 通过这个 Binder 将指定 prefix 前缀的属性值设置到这个 Bean 中,会借助 ConversionService 类型转换器进行类型转换

整个处理过程主要在第 5 步,有点复杂,借助于 Binder 绑定器实现的,这里就不讲述了,感兴趣的可以去研究研究

加餐

我们在编写 application.yml 文件时,当你输入一个字母时,IDE 是不是会提示很多选项供你选择,这个就要归功于 META-INF/spring-configuration-metadata.jsonMETA-INF/additional-spring-configuration-metadata.json 两个文件,在这两个文件里面可以定义你需要的配置的信息,例如 Spring Boot 提供的:

{
"groups": [
{
"name": "logging",
"type": "org.springframework.boot.context.logging.LoggingApplicationListener"
}
],
"properties": [
{
"name": "logging.config",
"type": "java.lang.String",
"description": "Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
},
{
"name": "spring.application.name",
"type": "java.lang.String",
"description": "Application name.",
"sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer"
},
{
"name": "spring.profiles",
"type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of profile expressions that at least one should match for the document to be included.",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener"
},
{
"name": "spring.profiles.active",
"type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of active profiles. Can be overridden by a command line switch.",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener"
}
],
"hints": [
{
"name": "logging.level.values",
"values": [
{
"value": "trace"
},
{
"value": "debug"
},
{
"value": "info"
},
{
"value": "warn"
},
{
"value": "error"
},
{
"value": "fatal"
},
{
"value": "off"
}
],
"providers": [
{
"name": "any"
}
]
}
]
}

上面仅列出了部分内容,可以看到定义了每个配置的名称、类型、描述和来源,同时可以定义每个配置能够输入的值,这样一来,我们就能够在 IDE 中快速的输入需要的配置项。

这个文件是通过 Spring Boot 提供的 spring-boot-configuration-processor 工具模块生成的,借助于 SPI 机制配置了一个 ConfigurationMetadataAnnotationProcessor 注解处理器,它继承 javax.annotation.processing.AbstractProcessor 抽象类。也就是说这个处理器在编译阶段,会解析每个 @ConfigurationProperties 注解标注的类,将这些类对应的一些配置项(key)的信息保存在 META-INF/spring-configuration-metadata.json 文件中,例如类型、默认值,来帮助你编写 application.yml 的时候会有相关提示。

而且,当我们使用 @ConfigurationProperties 注解后,IDE 会提示我们引入这个工具类:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

关于这部分内容可参考 Spring Boot 官方文档

总结

本文分析了 Spring Boot 中的 @ConfigurationProperties 注解的实现过程,原理就是通过注册的一个 BeanPostProcessor 会在加载 Spring Bean 初始化的时候进行前置处理,解析出 @ConfigurationProperties 注解相关信息,然后找到对应前缀的属性值绑定到这个 Bean 中。

使用这个注解有两种方式:

  • @ConfigurationProperties + @Component 注解(一个类)
  • @EnableConfigurationProperties(某个 Bean)+ @ConfigurationProperties 注解(另一个普通类)

关于 @EnableConfigurationProperties 注解的处理过程也比较简单,通过 @Import 注解的方法,注册一个 BeanPostProcessor 用于处理 @ConfigurationProperties 注解的 Bean,同时会将指定的带有 @ConfigurationProperties 注解的 Class 对象注册到 Spring IoC 容器中,这也就是为什么不用加 @Component 注解的原因

关于上面第一种方式是通过一个 ConfigurationPropertiesAutoConfiguration 自动配置类借助 @EnableConfigurationProperties 注解注册的这个 BeanPostProcessor 去处理 @ConfigurationProperties 注解的 Bean

学习完 Spring Boot 源码后,个人觉得是非常有帮助的,让自己能够清楚的了解 Sprig Boot 应用的运行原理,在处理问题以及调优等方面会更加轻松。另外,熟悉 Spring Boot 的自动配置功能后,编写一个 Spring Boot Starter 可以说是轻而易举。

至此,关于 Spirng 和 Spring Boot 两个流行的基础框架的源码已经全部分析完了,接下来笔者要开始学习其他的东西了,例如 MySQL、Dubbo 和 Spring Cloud,敬请期待吧,加油‍

这里提一句,Apache Dubbo 3.0 正式发布,全面拥抱云原生,先深入学习一下 Dubbo ~

路漫漫其修远兮,吾将上下而求索

精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现的更多相关文章

  1. 精尽Spring Boot源码分析 - 文章导读

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  2. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  4. 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - Condition 接口的扩展

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. 精尽Spring Boot源码分析 - 日志系统

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  9. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

随机推荐

  1. CentOS6.7系统文本安装-2020

    CentOS6.7系统文本安装 [日期:2016-01-30] 来源:Linux社区  作者:endmoon [字体:大 中 小]   一.选择虚拟机软件 1)VMware Workstation   ...

  2. WIN10 分区 C盘 至少250-300G E盘至少700G

    win10 就2个分区 C盘 至少250-300G  E盘至少700G

  3. 用urllib库几行代码实现最简单爬虫

    """ 使用urllib.request()请求一个网页内容,并且把内容打印出来. """ from urllib import reque ...

  4. synchronized 的超多干货!

    前言 synchronized 这个关键字的重要性不言而喻,几乎可以说是并发.多线程必须会问到的关键字了.synchronized 会涉及到锁.升级降级操作.锁的撤销.对象头等.所以理解 synchr ...

  5. 开源软硬一体OpenCV AI Kit(OAK)

    开源软硬一体OpenCV AI Kit(OAK) OpenCV 涵盖图像处理和计算机视觉方面的很多通用算法,是非常有力的研究工具之一,且稳居开发者最喜爱的 AI 工具/框架榜首. 1.会不会被USA禁 ...

  6. 旷视MegEngine基本概念

    旷视MegEngine基本概念 MegEngine 是基于计算图的深度神经网络学习框架. 本文简要介绍计算图及其相关基本概念,以及它们在 MegEngine 中的实现. 计算图(Computation ...

  7. 将HLSL射线追踪到Vulkan

    将HLSL射线追踪到Vulkan Bringing HLSL Ray Tracing to Vulkan Vulkan标志 DirectX光线跟踪(DXR)允许您使用光线跟踪而不是传统的光栅化方法渲染 ...

  8. NVIDIA TensorRT高性能深度学习推理

    NVIDIA TensorRT高性能深度学习推理 NVIDIA TensorRT 是用于高性能深度学习推理的 SDK.此 SDK 包含深度学习推理优化器和运行时环境,可为深度学习推理应用提供低延迟和高 ...

  9. vulhub-struct2-s2-005

    0x00 漏洞原理   s2-005漏洞的起源源于S2-003(受影响版本: 低于Struts 2.0.12),struts2会将http的每个参数名解析为OGNL语句执行(可理解为java代码).O ...

  10. Web打印插件实现思路(C#/Winform)

    最近,客户有个需求过来,Web端无预览打印,美其名曰:快捷打印. 当时第一反应就是找插件,拿来主义永远不过时.找了一圈发现,免费的有限制,没限制的需要收费(LODOP真的好用).说来就是一个简单的无预 ...