当前位置: 首页 > article >正文

玩转springboot之springboot属性绑定原理

属性绑定原理

注意:使用版本为spring-boot-2.2.2.RELEASE

在进行自定义配置的时候,我们通常使用@ConfigurationProperties注解来进行配置文件和配置类的映射,为什么可以映射呢?

主要靠的是@EnableConfigurationProperties注解来进行自动的将外部配置绑定到@ConfigurationProperties标注的类的属性中

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties

看到该注解上引入了一个EnableConfigurationPropertiesRegistrar类,这个应该就是关键了

class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      registerInfrastructureBeans(registry);
      ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
     // 将配置的XxxProperties注册到spring容器中
      getTypes(metadata).forEach(beanRegistrar::register);
   }

  // @EnableConfigurationProperties注解中配置的value值,比如@EnableConfigurationProperties(ServerProperties.class),那么得到的值是ServerProperties.class
   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());
   }

   @SuppressWarnings("deprecation")
   static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
     // 将ConfigurationPropertiesBindingPostProcessor注册到spring容器中,ConfigurationPropertiesBindingPostProcessor用于属性绑定
      ConfigurationPropertiesBindingPostProcessor.register(registry);
      ConfigurationPropertiesBeanDefinitionValidator.register(registry);
      ConfigurationBeanFactoryMetadata.register(registry);
   }

}

在代码中我们看到了其向spring容器中注册了ConfigurationPropertiesBindingPostProcessor后置处理器,看该类的名字像是进行属性绑定的,来看一下该类的代码逻辑是如何进行属性绑定的

// 只展示了关键方法,其他方法没有展示
public class ConfigurationPropertiesBindingPostProcessor
      implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {

   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
     // 进行绑定
      bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
      return bean;
   }

   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类
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
  // 返回一个绑定了XxxProperties类的Bindable对象target,这个target对象即被外部属性值注入的目标对象
        Bindable<?> target = propertiesBean.asBindTarget();
  // 得到@ConfigurationProperties注解
        ConfigurationProperties annotation = propertiesBean.getAnnotation();
  // 得到BindHandler对象(默认是IgnoreTopLevelConverterNotFoundBindHandler对象),
   // 用于对ConfigurationProperties注解的ignoreUnknownFields等属性的处理
        BindHandler bindHandler = getBindHandler(target, annotation);
    // 得到一个Binder对象,并利用其bind方法执行外部属性绑定逻辑
        return getBinder().bind(annotation.prefix(), target, bindHandler);
    }


// Binder类
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
            boolean allowRecursiveBinding, boolean create) {
  // 清空Binder的configurationProperty属性值
        context.clearConfigurationProperty();
        try {
      // 调用BindHandler的onStart方法,执行一系列的责任链对象的该方法
            Bindable<T> replacementTarget = handler.onStart(name, target, context);
            if (replacementTarget == null) {
                return handleBindResult(name, target, handler, context, null, create);
            }
            target = replacementTarget;
      // 调用bindObject方法对Bindable对象target的属性进行绑定外部配置的值,并返回赋值给bound对象
            Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
      // 封装handleBindResult对象并返回,注意在handleBindResult的构造函数中会调用BindHandler的onSucess,onFinish方法
            return handleBindResult(name, target, handler, context, bound, create);
        }
        catch (Exception ex) {
            return handleBindError(name, target, handler, context, ex);
        }
    }

绑定对象的真正操作在bindObject方法中

private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
            Context context, boolean allowRecursiveBinding) {
    // 从propertySource中的配置属性,获取ConfigurationProperty对象property即application.properties配置文件中若有相关的配置的话,那么property将不会为null
        ConfigurationProperty property = findProperty(name, context);
  // 若property为null,则不会执行后续的属性绑定相关逻辑
        if (property == null && containsNoDescendantOf(context.getSources(), name) && context.depth != 0) {
            return null;
        }
  // 根据target类型获取不同的Binder,可以是null(普通的类型一般是Null),MapBinder,CollectionBinder或ArrayBinder
        AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
  // 若aggregateBinder不为null,则调用bindAggregate并返回绑定后的对象
        if (aggregateBinder != null) {
            return bindAggregate(name, target, handler, context, aggregateBinder);
        }
  // 若property不为null
        if (property != null) {
            try {
        // 绑定属性到对象中,比如配置文件中设置了server.port=8888,那么将会最终调用bindProperty方法进行属性设置
                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;
            }
        }
  // 只有@ConfigurationProperties注解的类进行外部属性绑定才会走这里
        return bindDataObject(name, target, handler, context, allowRecursiveBinding);
    }



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的实现类对象,注意这个对象实现了bindProperty方法
        DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
                propertyTarget, handler, context, false, false);
        return context.withDataObject(type, () -> {
            for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
        // 真正实现将外部配置属性绑定到@ConfigurationProperties注解的XxxProperties类的属性中的逻辑
                Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
                if (instance != null) {
                    return instance;
                }
            }
            return null;
        });
    }

https://zhhll.icu/2021/框架/springboot/源码/3.属性绑定原理/



喜欢的朋友记得点赞、收藏、关注哦!!!


http://www.kler.cn/news/368376.html

相关文章:

  • 【Android】基础回顾--四大组件
  • (7) cuda异常处理
  • 软考:常用协议和端口号
  • 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-24
  • uni-app跨平台开发小程序表单校验
  • Python图像处理——基于ResNet152的人脸识别签到系统(Pytorch框架)
  • ESP32-S3-DevKitC-1开发记录帖(2)——与MPU6050一起部署动作识别神经网络
  • 单链表总结
  • Zig 语言通用代码生成器:逻辑,冒烟测试版发布二
  • 学习qmake
  • 配置smaba (Linux与windows通信)
  • GPT打数模——电商品类货量预测及品类分仓规划
  • LeetCode9:回文数
  • golang将指针传给cgo后还能被回收吗?
  • 15-4连续子串和的整除问题
  • Android Studio历史版本下载
  • 开源模型应用落地-Qwen2-VL-7B-Instruct-vLLM-OpenAI API Client调用
  • jsp网上招标系统+lw视频讲解
  • 【论文阅读】Reliable, Adaptable, and Attributable Language Models with Retrieval
  • 基于stm32的HAL库的adc采集实验
  • 第18次CCF CSP认证真题解
  • 算法题总结(十九)——图论
  • Redis设计与实现 学习笔记 第十三章 客户端
  • Sora高端制造业WordPress外贸主题
  • 编程学习与心理健康:孩子会因学习编程而焦虑吗?
  • 远程:HTTP基本身份验证失败。提供的密码或令牌不正确,或者您的账户启用了两步验证,您必须使用个人访问令牌而不是密码。