一文深入了解ConfigurationProperties注解

简介: 一文深入了解ConfigurationProperties注解

概述


@ConfigurationProperties注解大家应该在项目中都使用过,主要用来绑定属性中的值到我们的对象中。那大家对于怎么进行属性绑定的原理知道吗?


ConfigurationProperties使用


注解介绍


@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
  // 配置的前缀, 和prefix一样
  @AliasFor("prefix")
  String value() default "";
  // 配置的前缀,和value一样
  @AliasFor("value")
  String prefix() default "";
  // 绑定时是否忽视无效字段,比如字段的类型错误等,默认false
  boolean ignoreInvalidFields() default false;
  // 绑定时是否忽视没有的字段,默认true
  boolean ignoreUnknownFields() default true;
}


例子


  1. 定义Bean, 使用@ConfigurationProperties
@ConfigurationProperties(prefix = "bsfit.user")
@Component
public class User {
    private String userName;
    private Integer age;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
  1. 配置文件添加如下配置

1671113332945.jpg

  1. 执行查看Bean的结果

1671113342655.jpg

看到User这个bean被赋值了。


注意点


  1. 被注释对象必须被注册为一个Bean, 可以通过以下几种方式
  • 通过@Component@Service注解,参考上面的例子
  • 通过@Bean的方式,在方法上添加
@Bean
    @ConfigurationProperties(prefix = "bsfit.user")
    public User newUser() {
        return new User();
    }
  • 通过@EnableConfigurationProperties(value = User.class)方式
  • 通过@ConfigurationPropertiesScan()的方式
  1. 属性的用中横线分隔和驼峰的方式都可以和Bean的字段映射
bsfit.user:
  ## user-name等价于userName使用
  user-name: ${person.last-name}   
  age: 55


源码解析


@ConfigurationProperties读取配置主要是通过ConfigurationPropertiesBindingPostProcessor配置绑定bean初始化后置处理器来实现的,它是在Bean初始化前后执行的。TODO


执行主流程


  1. 首先我们看下ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization方法,它是在Bean初始化前调用。
// Bean初始化前调用postProcessBeforeInitialization方法
    @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //1. ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName)方法根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含Bean和相关的注解信息
        //2. 调用bind方法为对象添加属性
        bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
    //3.返回填充了配置属性的对象
        return bean;
  }
  1. ConfigurationPropertiesBean#get(this.applicationContext, bean, beanName)方法如下:
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
    // 1.找到这个beanName对应的工厂方法,例如@Bean标注的方法就是一个工厂方法,不是@Bean的话这里为空
        Method factoryMethod = findFactoryMethod(applicationContext, beanName);
        // 2.创建一个 ConfigurationPropertiesBean 对象,包含了这个 Bean 的@ConfigurationProperties注解信息
    return create(beanName, bean, bean.getClass(), factoryMethod);
  }

我们看下创建的ConfigurationPropertiesBean对象都有哪些属性。

public final class ConfigurationPropertiesBean {
  /**
   * Bean 的名称
   */
  private final String name;
  /**
   * Bean 的实例对象
   */
  private final Object instance;
  /**
   * Bean 的 `@ConfigurationProperties` 注解
   */
  private final ConfigurationProperties annotation;
  /**
   * `@Bean` 对应的方法资源对象,包括实例对象和注解信息
   */
  private final Bindable<?> bindTarget;
  /**
   * `@Bean` 对应的方法
   */
  private final BindMethod bindMethod;
}
  1. 调用关键方法bind对属性进行绑定
private void bind(ConfigurationPropertiesBean bean) {
        //1. 如果bean为空或者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进行属性的绑定
      this.binder.bind(bean);
    }
    catch (Exception ex) {
      throw new ConfigurationPropertiesBindException(bean, ex);
    }
  }
  1. 通过属性绑定器ConfigurationPropertiesBinder#bind方法对属性进行绑定
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
        //1. 获取这个 Bean 的 Bindable 对象(包含了 @ConfigurationProperties、@Validated 配置信息和这个 Bean)
    Bindable<?> target = propertiesBean.asBindTarget();
    //2.获取这个 Bean 的 @ConfigurationProperties注解信息
        ConfigurationProperties annotation = propertiesBean.getAnnotation();
        //3. 获取属性绑定处理器,此处用了装饰器模式,提供了一些回调接口处理额外的逻辑,比如执行Validated校验等
    BindHandler bindHandler = getBindHandler(target, annotation);
        //4. 通过getBinder()方法new一个绑定器Binder对象,里面包括了配置属性对象列表、配置属性解析器、类型转换器等
    //5. 调用Binder的bind方法进行属性设置
        return getBinder().bind(annotation.prefix(), target, bindHandler);
  }
  1. 最关键的是Binder#bind方法
public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) {
        //1. ConfigurationPropertyName.of(name)根据传入的name构造出ConfigurationPropertyName对象
        //2. 调用重载的方法bind
    return bind(ConfigurationPropertyName.of(name), target, handler);
  }
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
  // 调用重载方法bind,返回绑定了属性的bean对象
    T bound = bind(name, target, handler, false);
    return BindResult.of(bound);
  }
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) {
    Assert.notNull(name, "Name must not be null");
    Assert.notNull(target, "Target must not be null");
    handler = (handler != null) ? handler : this.defaultBindHandler;
        // 创建绑定的上下文对象,主要用来处理属性的递归调用,比如我们的bean中可能包含了另外一个bean
    Context context = new Context();
        // 调用重载方法bind
    return bind(name, target, handler, context, false, create);
  }
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
      boolean allowRecursiveBinding, boolean create) {
    try {
            // 1. 调用handler的onStart回调方法,在绑定属性前做一些额外的处理
      Bindable<T> replacementTarget = handler.onStart(name, target, context);
      if (replacementTarget == null) {
        return handleBindResult(name, target, handler, context, null, create);
      }
      target = replacementTarget;
            // 2. 调用bindObject方法机进行绑定
      Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
      // 3. 处理绑定后的结果
            return handleBindResult(name, target, handler, context, bound, create);
    }
    catch (Exception ex) {
      return handleBindError(name, target, handler, context, ex);
    }
  }
  • ConfigurationPropertyName: 由点分隔的元素组成的配置属性名称。用户创建的名称可以包含“a-z”“0-9”)和“-”,必须为小写,且首字符必须为字母和数字。“-”纯粹用于格式化,即。“foo-bar”和“foobar”被认为是等价的。
  1. 我们看下核心方法Binder#bindObject
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
      Context context, boolean allowRecursiveBinding) {
  // 1. 根据属性名name从配置文件等source中遍历查找对应的结果 
    ConfigurationProperty property = findProperty(name, target, context);
    // 2. 如果属性为空且不是第一层节点且不包含子节点,直接返回,否则继续遍历
    if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
      return null;
    }
    //3. 处理聚合的绑定,比如List, Map等
    AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
    if (aggregateBinder != null) {
      return bindAggregate(name, target, handler, context, aggregateBinder);
    }
        // 如果属性不为空, 直接绑定数据到属性上
    if (property != null) {
      try {
                // 
        return bindProperty(target, context, property);
      }
      catch (ConverterNotFoundException ex) {
        // We might still be able to bind it using the recursive binders
        Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
        if (instance != null) {
          return instance;
        }
        throw ex;
      }
    }
    // 绑定对象数据
    return bindDataObject(name, target, handler, context, allowRecursiveBinding);
  }
  1. 关注下核心方法Binder#bindDataObject
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;
    }
        // 1. 构造属性绑定器,比如用于绑定我们user对象的userName
        // DataObjectPropertyBinder是一个接口,最终执行的是Binder中的bind方法
    DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
        propertyTarget, handler, context, false, false);
    // 2. 相对比较复杂,调用Binder上下文对象Context的withDataObject
       return context.withDataObject(type, () -> {
      for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
        Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
        if (instance != null) {
          return instance;
        }
      }
      return null;
    });
  }
  1. 看下Context.withDataObject方法
private <T> T withDataObject(Class<?> type, Supplier<T> supplier) {
            // 向栈中推入当前正在绑定的对象
      this.dataObjectBindings.push(type);
      try {
                // 深入属性绑定
        return withIncreasedDepth(supplier);
      }
      finally {
        this.dataObjectBindings.pop();
      }
    }
private <T> T withIncreasedDepth(Supplier<T> supplier) {
    // 层级+1
      increaseDepth();
      try {
                // 执行supplier,也就是一开始传入的方法
        return supplier.get();
      }
      finally {
        decreaseDepth();
      }
    }
  1. 我们看下传入的Supperlier方法如下
() -> {     // 遍历dataObjectBinders, 有两个,一个是构造函数绑定器,一个是JavaBeanBinder,也就是Set方法绑定,我们重点关注该绑定器
      for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
                // 调用数据对象绑定器DataObjectBinder的bind方法
        Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
        if (instance != null) {
          return instance;
        }
      }
      return null;
    }
  1. 关注在JavaBeanBinder#bind方法
private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier,
      Context context) {
    boolean bound = false;
        // 遍历Bean的属性列表
    for (BeanProperty beanProperty : bean.getProperties().values()) {
      // 调用bind方法绑定属性
            bound |= bind(beanSupplier, propertyBinder, beanProperty);
      context.clearConfigurationProperty();
    }
    return bound;
  }
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();
    // 调用属性绑定器获取属性的值,bindProperty最终还是调用Binder对象中的bind方法
    Object bound = propertyBinder.bindProperty(propertyName,
        Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
    if (bound == null) {
      return false;
    }
    if (property.isSettable()) {
            // 通过反射的set方法设置值
      property.setValue(beanSupplier, bound);
    }
    else if (value == null || !bound.equals(value.get())) {
      throw new IllegalStateException("No setter found for property: " + property.getName());
    }
    return true;
  }


总结


整个ConfigurationProperties属性绑定的过程还是很复杂的,代码的很多细节都没有展示,需要大家一步一步debug才能更好的理解和体会。

目录
相关文章
|
XML 数据格式 容器
基于注解的自动装配~
基于注解的自动装配~
|
Java 开发者 Spring
@ConfigurationProperties
@ConfigurationProperties
Zp
|
小程序 Java Spring
记@ComponentScan注解的坑
记@ComponentScan注解的坑
Zp
478 0
|
XML Java 数据格式
@Bean 注解
@Bean 注解
3025 5
|
Java Spring
@ConfigurationProperties注解的理解和使用
@ConfigurationProperties注解的理解和使用
|
Java 编译器 Spring
什么是注解
什么是注解
|
存储 JSON Java
一文学会注解的正确使用姿势
一文学会注解的正确使用姿势
一文学会注解的正确使用姿势
扒一扒@Retryable注解,很优雅,有点意思! (3)
扒一扒@Retryable注解,很优雅,有点意思! (3)
524 0
扒一扒@Retryable注解,很优雅,有点意思! (3)
|
Java Maven
扒一扒@Retryable注解,很优雅,有点意思! (5)
扒一扒@Retryable注解,很优雅,有点意思! (5)
283 0
扒一扒@Retryable注解,很优雅,有点意思! (5)
|
安全 Java
扒一扒这个注解,我发现还有点意思。 (中)
扒一扒这个注解,我发现还有点意思。 (中)
144 0
扒一扒这个注解,我发现还有点意思。 (中)