springboot @ConfigurationProperties注解的使用
前言
@ConfigurationProperties注解的使用
@ConfigurationProperties注解的原理
如何动态刷新@ConfigurationProperties
前言
相信大家肯定了解@Value注解,它可以通过一个配置文件中的属性名与对象的属性进行绑定。
@ConfigurationProperties注解的作用其实就类似于使用多个@Value注解同时绑定一个对象的多个属性,@ConfigurationProperties注解用于自动配置绑定,可以将application.properties配置中的值(准确来说是Environment中的属性值)注入到bean对象上,该注解的使用必须先将对象注入到IOC容器中才有配置自动绑定的功能。
@ConfigurationProperties注解的使用
先来看下@ConfigurationProperties的源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
// 匹配的前缀
@AliasFor("prefix")
String value() default "";
// 同上
@AliasFor("value")
String prefix() default "";
// 忽略属性类型不匹配的字段
boolean ignoreInvalidFields() default false;
// 忽略类中未知的属性,ignoreUnknownFields=false后当出现未知字段时会出现异常
boolean ignoreUnknownFields() default true;
}
注解使用在类上:
@Data
@Component
@ConfigurationProperties(prefix = "database")
public class DatabaseProperties {
private String username;
private String password;
private String driverClass;
private String connectionUrl;
}
配置文件中的属性名称需要与实体类的属性保持一致,不然值会绑定不上,多个单词可以使用横杠进行分割,SpringBoot会将横杠命名转驼峰命名。
注解使用方法上:
@Configuration
public class FlowRuleConfig {
@Bean
@ConfigurationProperties("flow.config")
public RuleProperties ruleProperties() {
return new RuleProperties();
}
@Data
public static class RuleProperties {
private Map<String, Integer> rules;
}
}
@ConfigurationProperties注解的原理
在SpringBoot的spring.factories文件注入了org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration。
ConfigurationPropertiesAutoConfiguration
ConfigurationPropertiesAutoConfiguration上面加了@EnableConfigurationProperties注解。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {
}
@EnableConfigurationProperties
@EnableConfigurationProperties注解导入了EnableConfigurationPropertiesRegistrar类。
@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";
/**
* Convenient way to quickly register
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
* Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@code @ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};
}
EnableConfigurationPropertiesRegistrar
EnableConfigurationPropertiesRegistrar实现了ImportBeanDefinitionRegistrar接口,主要用于向Spring容器中注入Bean。
主要注入了以下Bean:
ConfigurationPropertiesBindingPostProcessor
BoundConfigurationProperties
MethodValidationExcludeFilter
ConfigurationPropertiesBinder
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注入ConfigurationPropertiesBindingPostProcessor
// 注入BoundConfigurationProperties
registerInfrastructureBeans(registry);
// 注入MethodValidationExcludeFilter
registerMethodValidationExcludeFilter(registry);
// 注入@EnableConfigurationProperties注解指定的ConfigurationProperties
// 例如org.springframework.boot.autoconfigure.web.ServerProperties
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
getTypes(metadata).forEach(beanRegistrar::register);
}
ConfigurationPropertiesBindingPostProcessor
ConfigurationPropertiesBindingPostProcessor实现了BeanPostProcessor接口,其postProcessBeforeInitialization()方法会在Bean实例化后执行,在这里完成了配置文件的属性与对象的属性的绑定。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
return bean;
}
private void bind(ConfigurationPropertiesBean bean) {
if (bean == null || hasBoundValueObject(bean.getName())) {
return;
}
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 {
// 主要是通过ConfigurationPropertiesBinder来完成配置文件的属性与对象的属性的绑定
// 最终会调用Binder这个类来完成绑定
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
ConfigurationPropertiesBinder
ConfigurationPropertiesBinder主要负责构建Binder并进行缓存,SpringBoot启动过程中已经将配置文件的属性值存到Environment中的PropertySources中了,所以Binder只要从Environment中获取即可。
ConfigurationPropertiesBinder(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext);
this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
}
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
Bindable<?> target = propertiesBean.asBindTarget();
ConfigurationProperties annotation = propertiesBean.getAnnotation();
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bind(annotation.prefix(), target, bindHandler);
}
private Binder getBinder() {
if (this.binder == null) {
this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
getConversionServices(), getPropertyEditorInitializer(), null,
ConfigurationPropertiesBindConstructorProvider.INSTANCE);
}
return this.binder;
}
如何动态刷新@ConfigurationProperties
如果配置中心配置更新了,遇到了@ConfigurationProperties标注的配置bean,那么bean的属性就不会自动更新了,那么实现动态更新@ConfigurationProperties标注的bean的属性呢?
如果使用的Nacos注册中心,可以监听NacosConfigReceivedEvent事件后使用SpringBoot提供的Binder进行bean的属性的更新:
DatabaseProperties databaseProperties = applicationContext.getBean(DatabaseProperties.class);
System.out.println(databaseProperties);
// test refresh @ConfigurationProperties
// 这里使用app.properties模拟
ClassPathResource classPathResource = new ClassPathResource("app.properties");
ResourcePropertySource resourcePropertySource = new ResourcePropertySource(classPathResource);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(resourcePropertySource);
Binder binder = new Binder(configurationPropertySource);
Bindable<DatabaseProperties> bindable = Bindable.ofInstance(databaseProperties);
binder.bind("database", bindable);
System.out.println(databaseProperties);
当前最简单的办法就是在bean上面加上@RefreshScope注解就能实现自动刷新属性值了。