dcddc

西米大人的博客

0%

系统学习SpringBoot

@EnableConfigurationProperties

当你开发一个中间件,或者一个通用组件的时候,通常需要给使用方提供一组可自定义的配置项,这些配置项往往在properties文件里定义,且带有特定前缀用于区分是这个组件的配置。那么如何能在应用启动后读到这些使用方自定义的配置项,影响组件的运行态呢?这时可以借助注解EnableConfigurationProperties

注解EnableConfigurationProperties通常加在你开发的中间件或通用组件的配置类上,注解的value表示需要注册到使用方bf的properties应用配置bean,该bean能把使用方的配置项注入进来

1
2
3
4
5
6
7
8
9
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EnableConfigurationPropertiesRegistrar.class})
public @interface EnableConfigurationProperties {
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";

Class<?>[] value() default {};
}

properties应用配置bean需要使用@ConfigurationProperties 注解指定在应用属性文件里的属性前缀。例如 RocketMQ 使用 RocketMQProperties 这个 bean,注入 applicaiton.properties 里对 RocketMQ 的全局配置

1
2
3
4
@ConfigurationProperties(prefix = "rocketmq")
public class RocketMQProperties {
...
}

接下来从@EnableConfigurationProperties 注解开始,分析它是如何完成注册应用配置 bean 的

EnableConfigurationPropertiesRegistrar

@EnableConfigurationProperties 注解主要通过 Import 方式引入了一个 bd 注册器 EnableConfigurationPropertiesRegistrar,看下它的注册 bd 方法

1
2
3
4
5
6
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
registerMethodValidationExcludeFilter(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
this.getTypes(metadata).forEach(beanRegistrar: :register);
}

1、registerInfrastructureBeans 方法会注册一些基础设施 bean,包括 bpp:ConfigurationPropertiesBindingPostProcessor、应用配置绑定器工厂:ConfigurationPropertiesBinder.Factory、应用配置绑定器:ConfigurationPropertiesBinder、应用配置容器:BoundConfigurationProperties

1
2
3
4
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
BoundConfigurationProperties.register(registry);
}
1
2
3
4
5
6
7
8
9
10
public static void register(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "Registry must not be null");
if (!registry.containsBeanDefinition(BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class, ConfigurationPropertiesBindingPostProcessor: :new).getBeanDefinition();
definition.setRole(2);
registry.registerBeanDefinition(BEAN_NAME, definition);
}

ConfigurationPropertiesBinder.register(registry);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void register(BeanDefinitionRegistry registry) {
AbstractBeanDefinition definition;
if (!registry.containsBeanDefinition("org.springframework.boot.context.internalConfigurationPropertiesBinderFactory")) {
definition = BeanDefinitionBuilder.genericBeanDefinition(ConfigurationPropertiesBinder.Factory.class, ConfigurationPropertiesBinder.Factory: :new).getBeanDefinition();
definition.setRole(2);
registry.registerBeanDefinition("org.springframework.boot.context.internalConfigurationPropertiesBinderFactory", definition);
}

if (!registry.containsBeanDefinition("org.springframework.boot.context.internalConfigurationPropertiesBinder")) {
definition = BeanDefinitionBuilder.genericBeanDefinition(ConfigurationPropertiesBinder.class, () - >{
return ((ConfigurationPropertiesBinder.Factory)((BeanFactory) registry).getBean("org.springframework.boot.context.internalConfigurationPropertiesBinderFactory", ConfigurationPropertiesBinder.Factory.class)).create();
}).getBeanDefinition();
definition.setRole(2);
registry.registerBeanDefinition("org.springframework.boot.context.internalConfigurationPropertiesBinder", definition);
}

}
1
2
3
4
5
6
7
8
9
static void register(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "Registry must not be null");
if (!registry.containsBeanDefinition(BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(BoundConfigurationProperties.class, BoundConfigurationProperties: :new).getBeanDefinition();
definition.setRole(2);
registry.registerBeanDefinition(BEAN_NAME, definition);
}

}

2、registerMethodValidationExcludeFilter 方法注册了一个过滤器,过滤条件是必须包含 ConfigurationProperties 注解

1
2
3
4
5
6
7
8
9
static void registerMethodValidationExcludeFilter(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(MethodValidationExcludeFilter.class, () - >{
return MethodValidationExcludeFilter.byAnnotation(ConfigurationProperties.class);
}).setRole(2).getBeanDefinition();
registry.registerBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME, definition);
}

}

3、从父注解@EnableConfigurationProperties 的 value 属性中取出所有应用配置类 class,注册到 bf

  • 如果应用配置类 class 通过 ConfigurationProperties 注解指定了前缀,beanName 的格式为:{前缀}-{class.getName}
1
this.getTypes(metadata).forEach(beanRegistrar::register);
1
2
3
4
5
6
7
private Set < Class < ?>>getTypes(AnnotationMetadata metadata) {
return (Set) metadata.getAnnotations().stream(EnableConfigurationProperties.class).flatMap((annotation) - >{
return Arrays.stream(annotation.getClassArray("value"));
}).filter((type) - >{
return Void.TYPE != type;
}).collect(Collectors.toSet());
}
1
2
3
4
5
6
7
8
9
10
11
12
void register(Class < ?>type, MergedAnnotation < ConfigurationProperties > annotation) {
String name = this.getName(type, annotation);
if (!this.containsBeanDefinition(name)) {
this.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();
}

ConfigurationPropertiesBindingPostProcessor

应用配置绑定 bpp。该 bpp 在初始化阶段,通过 ac 加载并持有绑定器:ConfigurationPropertiesBinder

1
2
3
4
public void afterPropertiesSet() throws Exception {
this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
}

绑定器在构造函数里,通过 ac 拿到应用的所有配置项。包括系统属性、环境变量、应用配置

1
2
3
4
5
6
ConfigurationPropertiesBinder(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.propertySources = (new PropertySourcesDeducer(applicationContext)).getPropertySources();
this.configurationPropertiesValidator = this.getConfigurationPropertiesValidator(applicationContext);
this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
}

ConfigurationPropertiesBindingPostProcessor 在 bean 前置初始化阶段,对使用@ConfigurationProperties 修饰的 bean,完成应用配置的绑定

1
2
3
4
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
this.bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
return bean;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
Method factoryMethod = findFactoryMethod(applicationContext, beanName);
return create(beanName, bean, bean.getClass(), factoryMethod);
}

private static ConfigurationPropertiesBean create(String name, Object instance, Class < ?>type, Method factory) {
ConfigurationProperties annotation = (ConfigurationProperties) findAnnotation(instance, type, factory, ConfigurationProperties.class);
if (annotation == null) {
return null;
} else {
...

return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
}
}

内部使用绑定器ConfigurationPropertiesBinder完成绑定

1
2
3
4
5
private void bind(ConfigurationPropertiesBean bean) {
...

this.binder.bind(bean);
}

已完成绑定的应用配置,会缓存在 BoundConfigurationProperties bean

总结

通过注解@EnableConfigurationProperties 开启 sb 的应用配置绑定 bean 能力,在该注解的 value 属性上指定接收应用配置绑定的 bean
该注解引入一个 bd 注册器:EnableConfigurationPropertiesRegistrar,该注册器会将 @EnableConfigurationProperties 指定的应用配置绑定 bean 注册到 bf,还会注册一些用于完成应用配置绑定的组件 bean,最重要的是两个 bean:

  • bpp:ConfigurationPropertiesBindingPostProcessor。它在 bean 前置初始化阶段完成应用配置绑定注入
  • 绑定器:ConfigurationPropertiesBinder。ConfigurationPropertiesBindingPostProcessor 依赖该绑定器 bean 实现绑定注入。绑定器通过 ac 在构造函数里拿到应用的所有配置项