@ConfigurationProperties工作原理

2023-09-07 09:39:52 浏览数 (2)

一、使用方式

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

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

0 人点赞