概述

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

ConfigurationProperties使用

注解介绍

  1. @Target({ ElementType.TYPE, ElementType.METHOD })
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Indexed
  5. public @interface ConfigurationProperties {
  6. // 配置的前缀, 和prefix一样
  7. @AliasFor("prefix")
  8. String value() default "";
  9. // 配置的前缀,和value一样
  10. @AliasFor("value")
  11. String prefix() default "";
  12. // 绑定时是否忽视无效字段,比如字段的类型错误等,默认false
  13. boolean ignoreInvalidFields() default false;
  14. // 绑定时是否忽视没有的字段,默认true
  15. boolean ignoreUnknownFields() default true;
  16. }

例子

  1. 定义Bean, 使用@ConfigurationProperties

    1. @ConfigurationProperties(prefix = "bsfit.user")
    2. @Component
    3. public class User {
    4. private String userName;
    5. private Integer age;
    6. public String getUserName() {
    7. return userName;
    8. }
    9. public void setUserName(String userName) {
    10. this.userName = userName;
    11. }
    12. public Integer getAge() {
    13. return age;
    14. }
    15. public void setAge(Integer age) {
    16. this.age = age;
    17. }
    18. }
  2. 配置文件添加如下配置

image.png

  1. 执行查看Bean的结果

image.png
看到User这个bean被赋值了。

注意点

  1. 被注释对象必须被注册为一个Bean, 可以通过以下几种方式
  • 通过@Component@Service注解,参考上面的例子
  • 通过@Bean的方式,在方法上添加

    1. @Bean
    2. @ConfigurationProperties(prefix = "bsfit.user")
    3. public User newUser() {
    4. return new User();
    5. }
  • 通过@EnableConfigurationProperties(value = User.class)方式

  • 通过@ConfigurationPropertiesScan()的方式
  1. 属性的用中横线分隔和驼峰的方式都可以和Bean的字段映射

    1. bsfit.user:
    2. ## user-name等价于userName使用
    3. user-name: ${person.last-name}
    4. age: 55

    源码解析

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

    执行主流程

  2. 首先我们看下ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization方法,它是在Bean初始化前调用。

    1. // Bean初始化前调用postProcessBeforeInitialization方法
    2. @Override
    3. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    4. //1. ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName)方法根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含Bean和相关的注解信息
    5. //2. 调用bind方法为对象添加属性
    6. bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
    7. //3.返回填充了配置属性的对象
    8. return bean;
    9. }
  3. ConfigurationPropertiesBean#get(this.applicationContext, bean, beanName)方法如下:

    1. public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
    2. // 1.找到这个beanName对应的工厂方法,例如@Bean标注的方法就是一个工厂方法,不是@Bean的话这里为空
    3. Method factoryMethod = findFactoryMethod(applicationContext, beanName);
    4. // 2.创建一个 ConfigurationPropertiesBean 对象,包含了这个 Bean 的@ConfigurationProperties注解信息
    5. return create(beanName, bean, bean.getClass(), factoryMethod);
    6. }

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

    1. public final class ConfigurationPropertiesBean {
    2. /**
    3. * Bean 的名称
    4. */
    5. private final String name;
    6. /**
    7. * Bean 的实例对象
    8. */
    9. private final Object instance;
    10. /**
    11. * Bean 的 `@ConfigurationProperties` 注解
    12. */
    13. private final ConfigurationProperties annotation;
    14. /**
    15. * `@Bean` 对应的方法资源对象,包括实例对象和注解信息
    16. */
    17. private final Bindable<?> bindTarget;
    18. /**
    19. * `@Bean` 对应的方法
    20. */
    21. private final BindMethod bindMethod;
    22. }
  4. 调用关键方法bind对属性进行绑定

    1. private void bind(ConfigurationPropertiesBean bean) {
    2. //1. 如果bean为空或者bean采用构造器绑定,直接返回,构造器方式绑定注解暂时不讨论,用的相对较小
    3. if (bean == null || hasBoundValueObject(bean.getName())) {
    4. return;
    5. }
    6. Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
    7. + bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
    8. try {
    9. // 通过属性绑定器ConfigurationPropertiesBinder进行属性的绑定
    10. this.binder.bind(bean);
    11. }
    12. catch (Exception ex) {
    13. throw new ConfigurationPropertiesBindException(bean, ex);
    14. }
    15. }
  5. 通过属性绑定器ConfigurationPropertiesBinder#bind方法对属性进行绑定

    1. BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
    2. //1. 获取这个 Bean 的 Bindable 对象(包含了 @ConfigurationProperties、@Validated 配置信息和这个 Bean)
    3. Bindable<?> target = propertiesBean.asBindTarget();
    4. //2.获取这个 Bean 的 @ConfigurationProperties注解信息
    5. ConfigurationProperties annotation = propertiesBean.getAnnotation();
    6. //3. 获取属性绑定处理器,此处用了装饰器模式,提供了一些回调接口处理额外的逻辑,比如执行Validated校验等
    7. BindHandler bindHandler = getBindHandler(target, annotation);
    8. //4. 通过getBinder()方法new一个绑定器Binder对象,里面包括了配置属性对象列表、配置属性解析器、类型转换器等
    9. //5. 调用Binder的bind方法进行属性设置
    10. return getBinder().bind(annotation.prefix(), target, bindHandler);
    11. }
  6. 最关键的是Binder#bind方法 ```java public BindResult bind(String name, Bindable target, BindHandler handler) {

    1. //1. ConfigurationPropertyName.of(name)根据传入的name构造出ConfigurationPropertyName对象
    2. //2. 调用重载的方法bind
    3. return bind(ConfigurationPropertyName.of(name), target, handler);

    }

public BindResult bind(ConfigurationPropertyName name, Bindable target, BindHandler handler) { // 调用重载方法bind,返回绑定了属性的bean对象 T bound = bind(name, target, handler, false); return BindResult.of(bound); }

private T bind(ConfigurationPropertyName name, Bindable 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 bind(ConfigurationPropertyName name, Bindable target, BindHandler handler, Context context, boolean allowRecursiveBinding, boolean create) { try { // 1. 调用handler的onStart回调方法,在绑定属性前做一些额外的处理 Bindable 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); } }

  1. - `ConfigurationPropertyName`: 由点分隔的元素组成的配置属性名称。用户创建的名称可以包含“a-z”“0-9”)和“-”,必须为小写,且首字符必须为字母和数字。“-”纯粹用于格式化,即。“foo-bar”和“foobar”被认为是等价的。
  2. 5. 我们看下核心方法`Binder#bindObject`
  3. ```java
  4. private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
  5. Context context, boolean allowRecursiveBinding) {
  6. // 1. 根据属性名name从配置文件等source中遍历查找对应的结果
  7. ConfigurationProperty property = findProperty(name, target, context);
  8. // 2. 如果属性为空且不是第一层节点且不包含子节点,直接返回,否则继续遍历
  9. if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
  10. return null;
  11. }
  12. //3. 处理聚合的绑定,比如List, Map等
  13. AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
  14. if (aggregateBinder != null) {
  15. return bindAggregate(name, target, handler, context, aggregateBinder);
  16. }
  17. // 如果属性不为空, 直接绑定数据到属性上
  18. if (property != null) {
  19. try {
  20. //
  21. return bindProperty(target, context, property);
  22. }
  23. catch (ConverterNotFoundException ex) {
  24. // We might still be able to bind it using the recursive binders
  25. Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
  26. if (instance != null) {
  27. return instance;
  28. }
  29. throw ex;
  30. }
  31. }
  32. // 绑定对象数据
  33. return bindDataObject(name, target, handler, context, allowRecursiveBinding);
  34. }
  1. 关注下核心方法Binder#bindDataObject

    1. private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
    2. Context context, boolean allowRecursiveBinding) {
    3. if (isUnbindableBean(name, target, context)) {
    4. return null;
    5. }
    6. Class<?> type = target.getType().resolve(Object.class);
    7. if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
    8. return null;
    9. }
    10. // 1. 构造属性绑定器,比如用于绑定我们user对象的userName
    11. // DataObjectPropertyBinder是一个接口,最终执行的是Binder中的bind方法
    12. DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
    13. propertyTarget, handler, context, false, false);
    14. // 2. 相对比较复杂,调用Binder上下文对象Context的withDataObject
    15. return context.withDataObject(type, () -> {
    16. for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
    17. Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
    18. if (instance != null) {
    19. return instance;
    20. }
    21. }
    22. return null;
    23. });
    24. }
  2. 看下Context.withDataObject方法 ```java private T withDataObject(Class<?> type, Supplier supplier) {

    1. // 向栈中推入当前正在绑定的对象
    2. this.dataObjectBindings.push(type);
    3. try {
    4. // 深入属性绑定
    5. return withIncreasedDepth(supplier);
    6. }
    7. finally {
    8. this.dataObjectBindings.pop();
    9. }
    10. }

private T withIncreasedDepth(Supplier supplier) { // 层级+1 increaseDepth(); try { // 执行supplier,也就是一开始传入的方法 return supplier.get(); } finally { decreaseDepth(); } }

  1. 8. 我们看下传入的Supperlier方法如下
  2. ```java
  3. () -> { // 遍历dataObjectBinders, 有两个,一个是构造函数绑定器,一个是JavaBeanBinder,也就是Set方法绑定,我们重点关注该绑定器
  4. for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
  5. // 调用数据对象绑定器DataObjectBinder的bind方法
  6. Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
  7. if (instance != null) {
  8. return instance;
  9. }
  10. }
  11. return null;
  12. }
  1. 关注在JavaBeanBinder#bind方法 ```java private boolean bind(DataObjectPropertyBinder propertyBinder, Bean bean, BeanSupplier beanSupplier,
    1. Context context) {
    2. boolean bound = false;
    3. // 遍历Bean的属性列表
    4. for (BeanProperty beanProperty : bean.getProperties().values()) {
    5. // 调用bind方法绑定属性
    6. bound |= bind(beanSupplier, propertyBinder, beanProperty);
    7. context.clearConfigurationProperty();
    8. }
    9. return bound;
    }

private boolean bind(BeanSupplier beanSupplier, DataObjectPropertyBinder propertyBinder, BeanProperty property) { String propertyName = property.getName(); ResolvableType type = property.getType(); Supplier 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才能更好的理解和体会。

参考

https://www.cnblogs.com/lifullmoon/p/14957836.html