聊聊 Spring 核心特性中的数据绑定 (DataBinder)

简介: Spring 的核心特性包括 IOC 容器、事件、资源管理、国际化、校验、数据绑定、类型转换、EL 表达式、AOP。其他特性可以轻易的在网络上找到很多资料,而数据绑定这个特性即便在 Spring 官网描述却也不太多。这是因为数据绑定主要应用于 Spring 内部,对于用户而言直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。

前面的话


Spring 的核心特性包括 IOC 容器、事件、资源管理、国际化、校验、数据绑定、类型转换、EL 表达式、AOP。其他特性可以轻易的在网络上找到很多资料,而数据绑定这个特性即便在 Spring 官网描述却也不太多。这是因为数据绑定主要应用于 Spring 内部,对于用户而言直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。


理解数据绑定


数据绑定允许将用户输入的内容动态绑定到应用程序中的领域模型中。对于 Spring 而言,用于输入具体的场景主要包括 xml 中 bean 的定义、web 环境下的请求参数。


数据绑定在 Spring 中的核心类是 org.springframework.validation.DataBinder,可以看到这个类位于 validation 包中,数据绑定时也往往伴随着参数校验。先看看如何手动使用这个类。


@Data
public class LoginDTO {
    private String username;
    private String password;
}
public class App {
    public static void main(String[] args) {
        LoginDTO dto = new LoginDTO();
        Map<String, Object> input = new HashMap<>();
        input.put("username", "hkp");
        input.put("password", "123");
        PropertyValues propertyValues = new MutablePropertyValues(input);
        DataBinder dataBinder = new DataBinder(dto, "loginDTO");
        dataBinder.bind(propertyValues);
    // LoginDTO(username=hkp, password=123)
        System.out.println(dto);
    }
}


DataBinder#bind 是 DataBinder 类的核心方法,它接收 PropertyValues 类型的参数,并将参数中的数据设置到对象的属性中。


PropertyValues


PropertyValues 可以简单的理解为 Map,它包含更多的属性信息。接口核心方法定义如下。


public interface PropertyValues extends Iterable<PropertyValue>
  PropertyValue[] getPropertyValues();
  PropertyValue getPropertyValue(String propertyName);
  boolean contains(String propertyName);
  boolean isEmpty();
}


PropertyValues 继承接口 Iterable,可以理解为 PropertyValue 的容器,PropertyValue 表示对象中的某一个属性值,包含的主要字段如下。


public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
  private final String name;
  private final Object value;
  private boolean optional = false;
  // 是否进行过类型转换
  private boolean converted = false;
  // 类型转换后的值
  private Object convertedValue;
}


PropertyValue 包括原始值和转换后的值,也就意味着 Spring 可能会对用于输入的值进行类型转换。


PropertyValues 接口常用实现如下。


MutablePropertyValues:标准的 PropertyValues 实现,除了属性获取,还支持对属性的设置。

ServletConfigPropertyValues:支持 web 环境获取 servlet 上下文配置的 PropertyValues。

ServletRequestParameterPropertyValues:支持 web 环境获取请求参数的 PropertyValues。


DataBinder


DataBinder 是数据绑定的核心类,它内部的属性可以分为两类,一类是数据绑定的配置,另一类用于数据绑定,理解这些属性后我们就能大概知道它内部的机制。


数据绑定配置有关的字段如下。


public class DataBinder implements PropertyEditorRegistry, TypeConverter {
  // 是否忽略未知字段
  private boolean ignoreUnknownFields = true;
  // 是否忽略无效字段,如这些字段的值在目标对象中不可访问(如嵌套路径中的字段对应值为空)
  private boolean ignoreInvalidFields = false;
  // 是否自动增长包含空值的嵌套路径
  private boolean autoGrowNestedPaths = true;
  // 自动增长的数组或集合的大小的限制
  private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
  // 绑定字段白名单
  private String[] allowedFields;
  // 绑定字段黑名单
  private String[] disallowedFields;
  // 必须绑定的字段
  private String[] requiredFields;
}


数据绑定功能实现的字段如下。


public class DataBinder implements PropertyEditorRegistry, TypeConverter {
  // 需要进行数据绑定的目标对象
  private final Object target;
  // 需要进行数据绑定的目标对象名称
  private final String objectName;
  // 数据绑定结果
  private AbstractPropertyBindingResult bindingResult;
  // 类型转换
  private SimpleTypeConverter typeConverter;
  // 类型转换服务
  private ConversionService conversionService;
  // 将错误码转换为消息编码的解析器
  private MessageCodesResolver messageCodesResolver;
  // 错误处理器,用于处理字段缺失和进行异常转换
  private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
  // 参数校验器
  private final List<Validator> validators = new ArrayList<>();
}


数据绑定功能实现的字段又分为几大类:数据绑定的目标对象、负责类型转换的转换器、负责参数校验的校验器、保存参数校验结果的 BindingResult。


DataBinder 进行数据绑定时,需要将属性值转换为目标对象属性的类型,还会把校验结果存至 BindingResult。由于 DataBinder 实现了接口 PropertyEditorRegistry、TypeConverter,还会将实现委托到内部的 typeConverter。


至于校验器则用于额外的校验方法使用,在上篇《Spring 参数校验最佳实践及原理解析》有进行分析 web 请求参数的校验会调用 DataBinder 的校验方法,感兴趣可以自行阅读。


也就是说 DataBinder 兼具对象属性设置、类型转换、校验的能力。#bind 方法实现代码如下。


  public void bind(PropertyValues pvs) {
    MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
        (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
    doBind(mpvs);
  }


这里对属性进行了简单处理后就进入了真正数据绑定的方法。


  protected void doBind(MutablePropertyValues mpvs) {
    checkAllowedFields(mpvs);
    checkRequiredFields(mpvs);
    applyPropertyValues(mpvs);
  }


数据绑定时会对属性必要的校验和处理。让我们把重点放到属性的设置。


  protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
      // Bind request parameters onto target object.
      getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    } catch (PropertyBatchUpdateException ex) {
      // Use bind error processor to create FieldErrors.
      for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
        getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
      }
    }
  }


属性设置直接调用 PropertyAccessor 设置属性的方法,遇到异常后则使用错误处理器处理。那么 PropertyAccessor 是从哪里来的呢?继续跟踪代码。


  protected ConfigurablePropertyAccessor getPropertyAccessor() {
    return getInternalBindingResult().getPropertyAccessor();
  }


这里可以看出,PropertyAccessor 是从内部的 BindingResult 获取到的,继续跟踪代码则会发现这里 BindingResult 的实现是 BeanPropertyBindingResult,至于 PropertyAccessor 的实现使用的则是 BeanWrapper。也是说真正进行数据绑定的实现由 BeanWrapper 完成,这里不再具体分析。


Spring XML 配置中属性设置分析


我们已经对 Spring 数据绑定的能力有了一定的了解,那 Spring 是怎么运用这项能力的呢?这里进行一些简单分析,先看 Spring 如何将 XML 中的属性配置设置到 bean 的属性上的。


数据绑定中,属性使用 PropertyValues 表示,Spring 会将 xml 中的属性配置转换为 PropertyValues 并存储至表示 bean 元数据的 BeanDefinition。核心代码如下。


public class BeanDefinitionParserDelegate {
  public void parsePropertyElement(Element ele, BeanDefinition bd) {
    String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
    ... 省略部分代码
    try {
      Object val = parsePropertyValue(ele, bd, propertyName);
      PropertyValue pv = new PropertyValue(propertyName, val);
      bd.getPropertyValues().addPropertyValue(pv);
    } finally {
      this.parseState.pop();
    }
  }
}


有了 BeanDefinition,Spring 就可以根据其内部的元数据实例化 bean,并设置 bean 的属性。核心代码如下。


public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
    implements AutowireCapableBeanFactory {
  protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
    ... 省略部分代码
    try {
      bw.setPropertyValues(new MutablePropertyValues(deepCopy));
    } catch (BeansException ex) {
      throw new BeanCreationException(
          mbd.getResourceDescription(), beanName, "Error setting property values", ex);
    }
  }
}


可以看到,bean 属性的设置和 DataBinder 数据绑定的底层实现一样,也是委托给 BeanWrapper 设置属性值,至于这里的 BeanWrapper,则是 Spring 实例化 bean 之后创建的。


WEB 请求参数数据绑定分析


Web 环境下,Spring 需要把请求中的参数或其他参数绑定到 Controller 方法参数值中,这是由 HandlerMethodArgumentResolver 接口来处理的,它可以根据 Controller 方法参数定义解析出参数值。


对于请求到 Model 参数的绑定,实现类为 ModelAttributeMethodProcessor 有关参数绑定的核心代码如下。


public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
  @Override
  @Nullable
  public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
      ... 省略部分代码
      WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
      if (binder.getTarget() != null) {
        if (!mavContainer.isBindingDisabled(name)) {
          // 数据绑定
          bindRequestParameters(binder, webRequest);
        }
        // 参数校验
        validateIfApplicable(binder, parameter);
        if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
          throw new BindException(binder.getBindingResult());
        }
      }
      if (!parameter.getParameterType().isInstance(attribute)) {
        // 类型转换
        attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
      }
      bindingResult = binder.getBindingResult();
      ... 省略部分代码    
  }
  protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    ((WebRequestDataBinder) binder).bind(request);
  }
}


可以看到,这里直接调用了WebRequestDataBinder#bind方法将 request 转换为方法参数中的属性,此外数据绑定后还进行了参数校验与类型转换。


总结

Spring 数据绑定的核心类是 DataBinder,其底层使用 BeanWrapper 实现对象属性值的设置,对于数据绑定,往往又伴随着数据校验、类型转换。Spring 中的类型转换同样具有多种实现,下篇将进行介绍。


目录
相关文章
|
4月前
|
Java 数据库 Spring
Spring事务的传播机制(行为、特性)
Spring事务的传播机制(行为、特性)
105 0
|
4月前
|
存储 监控 Java
|
4月前
|
JSON Dubbo Java
微服务框架(二十)Dubbo Spring Boot 生产就绪特性
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo Spring Boot 生产就绪特性
|
1月前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
33 1
|
4月前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
67 1
|
4月前
|
设计模式 前端开发 Java
玩转Spring—Spring5新特性之Reactive响应式编程实战
玩转Spring—Spring5新特性之Reactive响应式编程实战
187 0
|
18天前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
72 6
|
21天前
|
存储 Java 开发者
使用Spring Boot 3.3全新特性CDS,启动速度狂飙100%!
【8月更文挑战第30天】在快速迭代的软件开发周期中,应用的启动速度是开发者不可忽视的一个重要指标。它不仅影响着开发效率,还直接关系到用户体验。随着Spring Boot 3.3的发布,其中引入的Class Data Sharing(CDS)技术为应用的启动速度带来了革命性的提升。本文将围绕这一全新特性,深入探讨其原理、使用方法以及带来的实际效益,为开发者们带来一场技术盛宴。
40 2
|
1月前
|
XML Java 应用服务中间件
深入探索Spring Boot框架的核心特性
Spring Boot 是一款基于Spring框架的开源框架,旨在简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式(默认配置)来简化整个构建过程。
44 11
|
4月前
|
安全 Java 程序员
Spring框架的核心特性是什么?
【4月更文挑战第30天】Spring 的特性
208 0