Spring官网阅读(十六)Spring中的数据绑定(2)

简介: Spring官网阅读(十六)Spring中的数据绑定(2)

DataBinder的子类


子类概览


微信图片_20221113124009.png

可以看到DataBinder的直接子类只有一个WebDataBinder,从名字上我们就能知道,这个类主要作用于Web环境,从而也说明了数据绑定主要使用在Web环境中。


WebDataBinder


这个接口是为了Web环境而设计的,但是并不依赖任何的Servlet API。它主要的作用是作为一个基类让其它的类继承,例如ServletRequestDataBinder。


代码分析

public class WebDataBinder extends DataBinder {
    // 这两个字段的详细作用见下面的两个方法checkFieldDefaults/checkFieldMarkers
  public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
  public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
  @Nullable
  private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
  @Nullable
  private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
    // ......省略构造方法及一些getter/setter方法
  @Override
  protected void doBind(MutablePropertyValues mpvs) {
    checkFieldDefaults(mpvs);
    checkFieldMarkers(mpvs);
        // 没有对数据绑定做什么扩展,只是单纯的调用了父类的方法,也就是DataBinder的方法
    super.doBind(mpvs);
  }
    // 若你给定的PropertyValue的属性名是以!开头的,例如,传入的属性名称为:!name,属性值为:dmz
    // 那就做处理如下:
    // 如果Bean中的name属性是可写的并且mpvs不存在name属性,那么向mpvs中添加一个属性对,其中属性名称为name,值为dmz
    // 然后将!name这个属性值对从mpvs中移除
    // 其实这里就是说你可以使用!来给个默认值。比如!name表示若找不到name这个属性的时,就取它的值,
    // 也就是说你request里若有穿!name保底,也就不怕出现null值啦
  protected void checkFieldDefaults(MutablePropertyValues mpvs) {
    String fieldDefaultPrefix = getFieldDefaultPrefix();
    if (fieldDefaultPrefix != null) {
      PropertyValue[] pvArray = mpvs.getPropertyValues();
      for (PropertyValue pv : pvArray) {
        if (pv.getName().startsWith(fieldDefaultPrefix)) {
          String field = pv.getName().substring(fieldDefaultPrefix.length());
                    // 属性可写,并且当前要绑定的属性值中不包含这个去除了“!”的属性名
          if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
                        // 添加到要绑定到Bean中的属性值集合里
            mpvs.add(field, pv.getValue());
          }
          mpvs.removePropertyValue(pv);
        }
      }
    }
  }
    // 处理_的步骤
    // 若传入的字段以“_”开头,以属性名称:“_name”,属性值dmz为例
    // 如果Bean中的name字段可写,并且mpvs没有这个值
    // 那么对Bean中的name字段赋默认的空值,比如Boolean类型默认给false,数组给空数组[],集合给空集合,Map给空map 
    // 然后移除mpvs中的“_name”
    // 相当于说,当我们进行数据绑定时,传入“_name”时,如果没有传入具体的属性值,Spring会为我们赋默认的空值
    // 前提是必须以“_”开头
  protected void checkFieldMarkers(MutablePropertyValues mpvs) {
    String fieldMarkerPrefix = getFieldMarkerPrefix();
    if (fieldMarkerPrefix != null) {
      PropertyValue[] pvArray = mpvs.getPropertyValues();
      for (PropertyValue pv : pvArray) {
        if (pv.getName().startsWith(fieldMarkerPrefix)) {
          String field = pv.getName().substring(fieldMarkerPrefix.length());
          if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
            Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
            mpvs.add(field, getEmptyValue(field, fieldType));
          }
          mpvs.removePropertyValue(pv);
        }
      }
    }
  }
  @Nullable
  protected Object getEmptyValue(String field, @Nullable Class<?> fieldType) {
    return (fieldType != null ? getEmptyValue(fieldType) : null);
  }
    // 根据不同的类型给出空值
  @Nullable
  public Object getEmptyValue(Class<?> fieldType) {
    try {
           // 布尔值,默认false
      if (boolean.class == fieldType || Boolean.class == fieldType) {
        return Boolean.FALSE;
      }
            // 数组,默认给一个长度为0的符合要求的类型的数组 
      else if (fieldType.isArray()) {
        return Array.newInstance(fieldType.getComponentType(), 0);
      }
            // 集合,也是给各种空集合,Set/List等等
      else if (Collection.class.isAssignableFrom(fieldType)) {
        return CollectionFactory.createCollection(fieldType, 0);
      }
      else if (Map.class.isAssignableFrom(fieldType)) {
        return CollectionFactory.createMap(fieldType, 0);
      }
    }
    catch (IllegalArgumentException ex) {
      if (logger.isDebugEnabled()) {
        logger.debug("Failed to create default value - falling back to null: " + ex.getMessage());
      }
    }
    // Default value: null.
    return null;
  }
    // 这个方法表示,支持将文件作为属性绑定到对象的上
  protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
    multipartFiles.forEach((key, values) -> {
      if (values.size() == 1) {
        MultipartFile value = values.get(0);
        if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
          mpvs.add(key, value);
        }
      }
      else {
        mpvs.add(key, values);
      }
    });
  }
}

可以看到相对于父类DataBinder,它主要做了以下三点增强

  1. 可以手动为Bean中的属性提供默认值(提供“!”开头的属性名称)
  2. 可以让容器对属性字段赋上某些空值(提供“_”开头的属性名称)
  3. 可以将文件绑定到Bean上


使用示例

public class WebDataBinderMain {
  public static void main(String[] args) {
    A  a = new A();
    WebDataBinder webDataBinder = new WebDataBinder(a);
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    // propertyValues.add("name","I AM dmz");
    propertyValues.add("!name","dmz");
    propertyValues.add("_list","10");
    webDataBinder.bind(propertyValues);
    System.out.println(a);
        // 程序打印:
       // A{name='dmz', age=0, multipartFile=null, list=[], no_list=null}
       // 如果注释打开,程序打印:A{name='I AM dmz', age=0, multipartFile=null, list=[], no_list=null}
  }
}
// 省略getter/setter方法
class A{
  String name;
  int age;
  MultipartFile multipartFile;
  List<String> list;
  List<String> no_list;
  }
}

ServletRequestDataBinder


相比于父类,明确的依赖了Servlet API,会从ServletRequest中解析出参数,然后绑定到对应的Bean上,同时还能将文件对象绑定到Bean上。


代码分析

public class ServletRequestDataBinder extends WebDataBinder {
  public void bind(ServletRequest request) {
        // 从request中解析除MutablePropertyValues,用于后面的数据绑定
    MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
        // 如果是一个MultipartRequest,返回一个MultipartRequest
        // 上传文件时,都是使用MultipartRequest来封装请求
        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
        // 说明这个请求对象是一个MultipartRequest
    if (multipartRequest != null) {
            // 调用父类方法绑定对应的文件
      bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
    }
        // 留给子类扩展使用
    addBindValues(mpvs, request);
        // 调用WebDataBinder的doBind方法进行数据绑定
    doBind(mpvs);
  }
  protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
  }
  //....... 省略部分代码
}

ExtendedServletRequestDataBinder


代码分析

public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
  // ....省略构造方法
    // 这个类在ServletRequestDataBinder复写了addBindValues方法,在上面我们说过了,本身这个方法也是ServletRequestDataBinder专门提供了用于子类复写的方法
    @Override
    @SuppressWarnings("unchecked")
    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
        String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
        // 它会从request获取名为HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的属性
        // 我们在使用@PathVariable的时候,解析出来的参数就放在request中的这个属性上,然后由ExtendedServletRequestDataBinder完成数据绑定
        Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
        if (uriVars != null) {
            uriVars.forEach((name, value) -> {
                if (mpvs.contains(name)) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Skipping URI variable '" + name +
                                    "' because request contains bind value with same name.");
                    }
                }
                else {
                    mpvs.addPropertyValue(name, value);
                }
            });
        }
    }
}

WebExchangeDataBinder


这个绑定器用于web-flux响应式编程中,用于完成Mono类型的数据的绑定,最终绑定的动作还是调用的父类的doBind方法


MapDataBinder


它位于org.springframework.data.web是和Spring-Data相关,专门用于处理target是Map类型的目标对象的绑定,它并非一个public类,Spring定义的用于内部使用的类


WebRequestDataBinder


它是用于处理Spring自己定义的org.springframework.web.context.request.WebRequest的,旨在处理和容器无关的web请求数据绑定


总结


上面关于Web相关的数据绑定我没有做详细的介绍,毕竟当前的学习阶段的重点是针对Spring-Framework,对于Web相关的东西目前主要以了解为主,后续在完成SpringMVC相关文章时会对这部分做详细的介绍。


本文主要介绍了DataBinder的整个体系,重点学习了它的数据绑定相关的知识,但是不要忘记了,它本身也可以实现类型转换的功能。实际上,我们也可以这样理解,之所以要让DataBinder具备类型转换的能力,正是为了更好的完成数据绑定。


相关文章
|
6月前
|
Java 应用服务中间件 Spring
Spring5源码(50)-SpringMVC源码阅读环境搭建
Spring5源码(50)-SpringMVC源码阅读环境搭建
76 0
|
Java Nacos Spring
Nacos spring-cloud 版本没找到共享配置文件的说明,Nacos服务中共享,并且可以被多个应用获取和使用。这个在官网哪里有说明啊
Nacos spring-cloud 版本没找到共享配置文件的说明,Nacos服务中共享,并且可以被多个应用获取和使用。这个在官网哪里有说明啊
74 1
|
20天前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
56 2
|
5月前
|
存储 Java 程序员
Spring 注册BeanPostProcessor 源码阅读
Spring 注册BeanPostProcessor 源码阅读
|
存储 前端开发 Java
Spring MVC 中的数据绑定和验证机制是什么,如何使用
Spring MVC 中的数据绑定和验证机制是什么,如何使用
|
Java 中间件 Maven
Spring 6 源码编译和高效阅读源码技巧分享
Spring 6 源码编译和高效阅读源码技巧分享
|
6月前
|
存储 前端开发 Java
Spring Boot中Spring MVC的表单标签库与数据绑定讲解与实战(附源码 超详细必看)
Spring Boot中Spring MVC的表单标签库与数据绑定讲解与实战(附源码 超详细必看)
91 0
|
Java API Spring
Spring 6 源码编译和高效阅读源码技巧分享
Spring 6 源码编译和高效阅读源码技巧分享
|
Java Spring
Spring 官网无法查看的版本依赖如何查看?
Spring 官网无法查看的版本依赖如何查看?
73 0
|
Java 容器 Spring
【Spring源码阅读】IOC容器的依赖注入
SpringIOC容器的依赖注入发生在用户第一次向IOC容器获取Bean时。除在BeanDefinition中设置lazy-init属性让容器完成bean的预实例化。我们在前面《Spring-IOC容器接口设计与功能》中曾讲过,容器BeanFactory通过getBean方法获取Bean。所以这篇文章,我们将从getBean()方法入手分析SpringIOC容器依赖注入的过程。