前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >@ConfigurationProperties工作原理

@ConfigurationProperties工作原理

作者头像
叔牙
发布2023-09-07 09:39:52
2750
发布2023-09-07 09:39:52
举报

一、使用方式

@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;
  }

到这里就完成了应用配置文件中属性绑定到配置类的操作,整个流程大致如下:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.定义属性配置
  • 2.定义对应属性配置类
  • 3.定义自动配置类
  • 4.使用属性配置
  • 二、原理介绍
    • 1.@EnableConfigurationProperties注解
      • 2.EnableConfigurationPropertiesRegistrar
        • 3.ConfigurationPropertiesBindingPostProcessor
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档