精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现

概述

我们的 Spring Boot 应用经常会在 application.yml 配置文件里vb.net教程面配置一些自定义的配置,对于不同环境设置不同的值,然后可以通过 @ConfigurationProperties 注解将这些配置作为 Spring Bean 的属性值进行注入,那么本文来c#教程简单分析一下这个注解是如何将配置自动设置到 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 对象python基础教程实现了 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. 调用 ConfigurationPropertiesBeanRegistrar 的 register(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,则java基础教程注册一个 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 前缀sql教程的属性值,设置到这个 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 ~

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

__EOF__

本文作者:月圆
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值