一、使用方式
@ConfigurationProperties是springboot框架中一个比较重要的注解,和@EnableConfigurationProperties一起使用,用于将配置属性绑定到Java类的字段上。这样可以方便地在应用程序中读取和使用配置属性。
1.定义属性配置
在应用配置文件中定义需要绑定的相关属性。
代码语言:javascript复制alarm:
openAlarm: true
alarmType: 1
webhookUrl: https://open.feishu.cn/open-apis/bot/v2/hook/xxxx
2.定义对应属性配置类
定义接收属性解析后需要绑定属性的目标类。
代码语言:javascript复制@Data
@ConfigurationProperties(prefix = "alarm")
public class AlarmConfig {
private Boolean openAlarm;
private Integer alarmType;
private String webhookUrl;
}
3.定义自动配置类
在自定义配置类或者自动装配类上添加@EnableConfigurationProperties注解并填入属性配置类。
代码语言:javascript复制@Configuration
@EnableConfigurationProperties({AlarmConfig.class})
@Slf4j
public class AlarmAutoConfiguration {
//声明配置和bean信息
}
4.使用属性配置
在业务类中通过@Resource或者@Autowired方式注入属性配置类,就可以像其他bean一样使用属性配置了。
代码语言:javascript复制@Service
@Slf4j
public class CustomService {
@Autowired
AlarmConfig alarmConfig;
public void doSomething() {
log.info("read props;config={}",this.alarmConfig);
}
}
二、原理介绍
@ConfigurationProperties属性绑定能力的开启是@EnableConfigurationProperties注解,我们就从@EnableConfigurationProperties注解开始分析属性绑定的工作原理。
1.@EnableConfigurationProperties注解
先看一下注解的定义:
代码语言:javascript复制@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
Class<?>[] value() default {};
}
注解只有一个属性,是需要进行属性绑定的类数组,并且该注解导入了EnableConfigurationPropertiesRegistrar类,该类是属性绑定能力实现的关键。
2.EnableConfigurationPropertiesRegistrar
代码语言:javascript复制class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
private static final String METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME = Conventions
.getQualifiedAttributeName(EnableConfigurationPropertiesRegistrar.class, "methodValidationExcludeFilter");
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
registerMethodValidationExcludeFilter(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
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());
}
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
BoundConfigurationProperties.register(registry);
}
static void registerMethodValidationExcludeFilter(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(MethodValidationExcludeFilter.class,
() -> MethodValidationExcludeFilter.byAnnotation(ConfigurationProperties.class))
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition();
registry.registerBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME, definition);
}
}
}
该类是一个ImportBeanDefinitionRegistrar,在springboot应用启动时,会通过ConfigurationClassPostProcessor类实例化并调用registerBeanDefinitions方法,具体可参考《ImportBeanDefinitionRegistrar原理》,我们分析一下此处的registerBeanDefinitions实现的逻辑。
代码语言:javascript复制@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
registerMethodValidationExcludeFilter(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
getTypes(metadata).forEach(beanRegistrar::register);
}
里边做了三件事,分别是注册基础组件beans,注册方法校验过滤器和注册属性配置BeanDefination;先看一下注册基础组件beans:
代码语言:javascript复制static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
BoundConfigurationProperties.register(registry);
}
调用ConfigurationPropertiesBindingPostProcessor的register方法注册相关信息和BoundConfigurationProperties的register方法注册相关信息,分别看一下:
代码语言:javascript复制public static void register(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "Registry must not be null");
if (!registry.containsBeanDefinition(BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.rootBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class).getBeanDefinition();
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN_NAME, definition);
}
ConfigurationPropertiesBinder.register(registry);
}
ConfigurationPropertiesBindingPostProcessor的register方法先检查是否已经注册了ConfigurationPropertiesBindingPostProcessor,如果没有则注册BeanDefination,然后调用ConfigurationPropertiesBinder的register方法:
代码语言:javascript复制static void register(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(FACTORY_BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.rootBeanDefinition(ConfigurationPropertiesBinder.Factory.class).getBeanDefinition();
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationPropertiesBinder.FACTORY_BEAN_NAME, definition);
}
if (!registry.containsBeanDefinition(BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.rootBeanDefinition(ConfigurationPropertiesBinder.class,
() -> ((BeanFactory) registry)
.getBean(FACTORY_BEAN_NAME, ConfigurationPropertiesBinder.Factory.class).create())
.getBeanDefinition();
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME, definition);
}
}
这里检查有没有注册internalConfigurationPropertiesBinderFactory的BeanDefination,如果没有则注册,同样检查有没有注册internalConfigurationPropertiesBinder的BeanDefination,没有则注册备用。
回到前一步,ConfigurationPropertiesBinder调用自己的静态方法register注册自己的BeanDefination。然后回到EnableConfigurationPropertiesRegistrar的registerBeanDefinitions中调用registerMethodValidationExcludeFilter方法:
代码语言:javascript复制static void registerMethodValidationExcludeFilter(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME)) {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(MethodValidationExcludeFilter.class,
() -> MethodValidationExcludeFilter.byAnnotation(ConfigurationProperties.class))
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition();
registry.registerBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME, definition);
}
}
此处是检查注册MethodValidationExcludeFilter相关BeanDefination。
registerBeanDefinitions方法最后是创建ConfigurationPropertiesBeanRegistrar实例,然后从EnableConfigurationProperties注解中解析value数组,然后调用ConfigurationPropertiesBeanRegistrar实例的register方法注册ConfigurationProperties注解的相关BeanDefination,看一下ConfigurationPropertiesBeanRegistrar的register方法实现:
代码语言:javascript复制void register(Class<?> type) {
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
register(type, annotation);
}
解析ConfigurationProperties注解相关属性,然后进行相关BeanDefination注册,调用内部方法registerBeanDefinition:
代码语言:javascript复制private void registerBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation) {
Assert.state(annotation.isPresent(), () -> "No " ConfigurationProperties.class.getSimpleName()
" annotation found on '" type.getName() "'.");
this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
}
组装BeanDefination并调用BeanDefinitionRegistry的registerBeanDefinition方法将@ConfigurationProperties注解的类注册到容器中,createBeanDefinition不再展开分析。
3.ConfigurationPropertiesBindingPostProcessor
执行属性绑定的核心是在前边分析的ConfigurationPropertiesBindingPostProcessor类中实现,继续分析一下。
ConfigurationPropertiesBindingPostProcessor本身是一个BeanPostProcessor,在应用启动阶段会调用其postProcessBeforeInitialization方法:
代码语言:javascript复制@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
return bean;
}
先调用ConfigurationPropertiesBean的get方法将待绑定属性目标类封装成ConfigurationPropertiesBean,然后调用内部方法bind进行绑定,先看一下ConfigurationPropertiesBean的get方法:
代码语言:javascript复制public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
Method factoryMethod = findFactoryMethod(applicationContext, beanName);
return create(beanName, bean, bean.getClass(), factoryMethod);
}
先获取工厂方法,然后调用create方法创建ConfigurationPropertiesBean:
代码语言:javascript复制private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
if (annotation == null) {
return null;
}
Validated validated = findAnnotation(instance, type, factory, Validated.class);
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);
}
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
}
这里获取bean上是否有ConfigurationProperties注解,如果没有则不需要绑定,直接返回null,否则将目标bean封装成ConfigurationPropertiesBean实例返回备用。
这里细心的小伙伴可以发现,有一个可以优化的地方,前边先寻找工厂方法,然后检查是否需要绑定,如果前边执行过了,但是没有解析到ConfigurationProperties注解,这里就返回了,前边的调用就是无用功,所以这里检查的位置或者调用顺序需要调整优化,可以自行思考。
言归正传,回到前边调用私有方法bind进行属性绑定的调用:
代码语言:javascript复制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 {
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
会继续调用ConfigurationPropertiesBinder的bind方法进行绑定,前边有分析过ConfigurationPropertiesBinder在EnableConfigurationPropertiesRegistrar的registerBeanDefinitions方法注册BeanDefination,在应用启动时会实例化,看一下ConfigurationPropertiesBinder的bind方法:
代码语言:javascript复制BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
Bindable<?> target = propertiesBean.asBindTarget();
ConfigurationProperties annotation = propertiesBean.getAnnotation();
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bind(annotation.prefix(), target, bindHandler);
}
从ConfigurationPropertiesBean获取绑定目标和注解,然后获取BindHandler,最后获取Binder进行属性绑定,属性绑定会调用Binder类的bind方法:
代码语言:javascript复制public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) {
return bind(ConfigurationPropertyName.of(name), target, handler);
}
继续调用重载方法bind进行绑定:
代码语言:javascript复制public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
T bound = bind(name, target, handler, false);
return BindResult.of(bound);
}
然后会调用私有方法bind进行绑定:
代码语言:javascript复制private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
boolean allowRecursiveBinding, boolean create) {
try {
Bindable<T> replacementTarget = handler.onStart(name, target, context);
if (replacementTarget == null) {
return handleBindResult(name, target, handler, context, null, create);
}
target = replacementTarget;
Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
return handleBindResult(name, target, handler, context, bound, create);
}
catch (Exception ex) {
return handleBindError(name, target, handler, context, ex);
}
}
核心逻辑是bindObject方法调用,传入属性名、目标绑定对象等进行绑定操作。按照我们前边实例定义的属性格式会走到如下方法:
代码语言:javascript复制private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
Context context, boolean allowRecursiveBinding) {
if (isUnbindableBean(name, target, context)) {
return null;
}
Class<?> type = target.getType().resolve(Object.class);
if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
return null;
}
DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
propertyTarget, handler, context, false, false);
return context.withDataObject(type, () -> {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
if (instance != null) {
return instance;
}
}
return null;
});
}
根据前边绑定方式是BindMethod.JAVA_BEAN,所以会走到JavaBeanBinder类的bind方法:
代码语言:javascript复制@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
DataObjectPropertyBinder propertyBinder) {
boolean hasKnownBindableProperties = target.getValue() != null && hasKnownBindableProperties(name, context);
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) {
return null;
}
BeanSupplier<T> beanSupplier = bean.getSupplier(target);
boolean bound = bind(propertyBinder, bean, beanSupplier, context);
return (bound ? beanSupplier.get() : null);
}
属性绑定最终会调用到JavaBeanBinder的私有方法bind:
代码语言:javascript复制 private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder,
BeanProperty property) {
String propertyName = property.getName();
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
Annotation[] annotations = property.getAnnotations();
Object bound = propertyBinder.bindProperty(propertyName,
Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
if (bound == null) {
return false;
}
if (property.isSettable()) {
property.setValue(beanSupplier, bound);
}
else if (value == null || !bound.equals(value.get())) {
throw new IllegalStateException("No setter found for property: " property.getName());
}
return true;
}
到这里就完成了应用配置文件中属性绑定到配置类的操作,整个流程大致如下: