该系列文章是笔者在学习 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
注解的值有三种情况:
-
该 Class 对象实现了
ImportSelector
接口,调用它的selectImports(..)
方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理- 该 Class 对象实现了
DeferredImportSelector
接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持@Order
排序
- 该 Class 对象实现了
该 Class 对象实现了
ImportBeanDefinitionRegistrar
接口,会调用它的registerBeanDefinitions(..)
方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)该 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 的前身)的过程如下:
-
先注册两个内部 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 开始就被废弃了,忽略掉
-
创建一个
ConfigurationPropertiesBeanRegistrar
对象获取
@EnableConfigurationProperties
注解指定的 Class 类对象们调用
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);
}
}
过程如下:
先获取这个 Class 类对象的
@ConfigurationProperties
注解-
调用
register(..)
方法,为这个 Class 对象注册一个 BeanDefinitionvoid 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());
}- 生成一个 Bean 的名称,为
@ConfigurationProperties
注解的${prefix}-类全面
,或者类全名
- 如果没有该名称的 Bean,则注册一个
type
类型的 BeanDefinition
- 生成一个 Bean 的名称,为
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;
}
过程如下:
- 调用
ConfigurationPropertiesBean#get(..)
方法,尝试根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含@ConfigurationProperties
注解信息 - 调用
bind(..)
方法,开始获取指定prefix
前缀的属性值,设置到这个 Bean 中 - 返回属性填充后的 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);
}
过程如下:
- 找到这个
beanName
对应的工厂方法,例如@Bean
标注的方法就是一个工厂方法,不是@Bean
的话这里为空 - 调用
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);
}
过程如下:
- 找到这个 Bean 上面的
@ConfigurationProperties
注解,如果是@Bean
标注的方法 Bean,也会尝试从所在的 Class 类上面获取 - 如果没有配置
@ConfigurationProperties
注解,则直接返回null
- 找到这个 Bean 上面的
@Validated
注解 - 将
@ConfigurationProperties
、Validated
注解信息,目标 Bean 以及它的 Class 对象,绑定到一个 Bindable 对象中 - 将
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);
}
过程如下:
获取这个 Bean 的 Bindable 对象(包含了
@ConfigurationProperties
、@Validated
配置信息和这个 Bean)获取这个 Bean 的
@ConfigurationProperties
注解信息-
获取一个 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;
} -
获取一个 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;
} 通过这个 Binder 将指定
prefix
前缀的属性值设置到这个 Bean 中,会借助 ConversionService 类型转换器进行类型转换
整个处理过程主要在第 5
步,有点复杂,借助于 Binder 绑定器实现的,这里就不讲述了,感兴趣的可以去研究研究
加餐
我们在编写 application.yml
文件时,当你输入一个字母时,IDE 是不是会提示很多选项供你选择,这个就要归功于 META-INF/spring-configuration-metadata.json
、META-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 注解的实现的更多相关文章
-
精尽Spring Boot源码分析 - 文章导读
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - Condition 接口的扩展
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - 日志系统
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
-
精尽Spring Boot源码分析 - 序言
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
随机推荐
-
JS中的宽高(基础知识很重要)
IE中:document.body.clientWidth ==> BODY对象宽度document.body.clientHeight ==> BODY对象高度document.docu ...
-
启动Eclipse弹出:Failed to load JavaHL Library 错误框的解决办法
一.问题背景描述: eclipse安装完svn插件以后,在启动时出现:Failed to load JavaHL Library. These are the errors that were en ...
-
MapReduce实现的Join
MapReduce Join 对两份数据data1和data2进行关键词连接是一个很通用的问题,如果数据量比较小,可以在内存中完成连接. 如果数据量比较大,在内存进行连接操会发生OOM.mapredu ...
-
Drawing Lines - SGU 135(简单递推)
求N条直线最多能把一个平面分成几部分. 代码如下: ========================================================================== ...
-
enumerate小技巧和列表推导式
1.enumerate enumerate函数用于遍历序列中的元素以及它们的下标,这样你就可以通过index 直接定位你的数据了. 之前对list操作的时候,即想取到下表,又想取到对应值,我是这么来实 ...
-
为WebBrowser的WEB页的Document注册事件的问题
原文:为WebBrowser的WEB页的Document注册事件的问题 当使用WebBrowser,并对其装载的Web页建立Document的事件后,WebBrowser里的页面元素都变得难于操作了, ...
-
关于使用国内dock仓库,网易、DaoCloud
使用国内docker镜像仓库,大大提高镜像的下载速度,从docker hub下载慢的不要不要的,甚至根本下载不了镜像,在docker for windows 18.06中增加一个配置即可,非常简单,具 ...
-
Flask简述
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后 ...
-
JPA和SpringData知识梳理
一. JPA,全称Java Persistence API,用于对象持久化的API,定义一套接口,来规范众多的ORM框架,所以它是在ORM框架之上的应用. 下面主要讲JPA在Hibernate基础上的 ...
-
autofac生命周期入门(如何避免内存泄漏)
如果你是一个IOC新手,那么生命周期可能会比较难以理解.以至于谈到这个问题时,一些老手也时常表示疑虑和害怕.一个令人不安的问题就是-对象没有在合适的时机被销毁.这样一来内存的使用率就会一直攀升,直到程 ...