聊聊Spring中的数据绑定 --- WebDataBinder、ServletRequestDataBinder、WebBindingInitializer...【享学Spring】(上)

简介: 聊聊Spring中的数据绑定 --- WebDataBinder、ServletRequestDataBinder、WebBindingInitializer...【享学Spring】(上)

前言


上篇文章聊了DataBinder,这篇文章继续聊聊实际应用中的数据绑定主菜WebDataBinder

在上文的基础上,我们先来看看DataBinder它的继承树:


image.png


继承树中可以看到,web环境统一对数据绑定DataBinder进行了增强。


毕竟数据绑定的实际应用场景:不夸张的说99%情况都是web环境~


WebDataBinder


它的作用就是从web request里(**注意:这里指的web请求,并不一定就是ServletRequest请求哟**)把web请求的`parameters`绑定到`JavaBean`上


Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型。若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参


// @since 1.2
public class WebDataBinder extends DataBinder {
  // 此字段意思是:字段标记  比如name -> _name
  // 这对于HTML复选框和选择选项特别有用。
  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;
  // 默认也会绑定空的文件流~
  private boolean bindEmptyMultipartFiles = true;
  // 完全沿用父类的两个构造~~~
  public WebDataBinder(@Nullable Object target) {
    super(target);
  }
  public WebDataBinder(@Nullable Object target, String objectName) {
    super(target, objectName);
  }
  ... //  省略get/set
  // 在父类的基础上,增加了对_和!的处理~~~
  @Override
  protected void doBind(MutablePropertyValues mpvs) {
    checkFieldDefaults(mpvs);
    checkFieldMarkers(mpvs);
    super.doBind(mpvs);
  }
  protected void checkFieldDefaults(MutablePropertyValues mpvs) {
    String fieldDefaultPrefix = getFieldDefaultPrefix();
    if (fieldDefaultPrefix != null) {
      PropertyValue[] pvArray = mpvs.getPropertyValues();
      for (PropertyValue pv : pvArray) {
        // 若你给定的PropertyValue的属性名确实是以!打头的  那就做处理如下:
        // 如果JavaBean的该属性可写 && mpvs不存在去掉!后的同名属性,那就添加进来表示后续可以使用了(毕竟是默认值,没有精确匹配的高的)
        // 然后把带!的给移除掉(因为默认值以已经转正了~~~)
        // 其实这里就是说你可以使用!来给个默认值。比如!name表示若找不到name这个属性的时,就取它的值~~~
        // 也就是说你request里若有穿!name保底,也就不怕出现null值啦~
        if (pv.getName().startsWith(fieldDefaultPrefix)) {
          String field = pv.getName().substring(fieldDefaultPrefix.length());
          if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
            mpvs.add(field, pv.getValue());
          }
          mpvs.removePropertyValue(pv);
        }
      }
    }
  }
  // 处理_的步骤
  // 若传入的字段以_打头
  // JavaBean的这个属性可写 && mpvs木有去掉_后的属性名字
  // getEmptyValue(field, fieldType)就是根据Type类型给定默认值。
  // 比如Boolean类型默认给false,数组给空数组[],集合给空集合,Map给空map  可以参考此类:CollectionFactory
  // 当然,这一切都是建立在你传的属性值是以_打头的基础上的,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);
        }
      }
    }
  }
  // @since 5.0
  @Nullable
  public Object getEmptyValue(Class<?> fieldType) {
    try {
      if (boolean.class == fieldType || Boolean.class == fieldType) {
        // Special handling of boolean property.
        return Boolean.FALSE;
      } else if (fieldType.isArray()) {
        // Special handling of array property.
        return Array.newInstance(fieldType.getComponentType(), 0);
      } 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());
      }
    }
    // 若不在这几大类型内,就返回默认值null呗~~~
    // 但需要说明的是,若你是简单类型比如int,
    // Default value: null. 
    return null;
  }
  // 单独提供的方法,用于绑定org.springframework.web.multipart.MultipartFile类型的数据到JavaBean属性上~
  // 显然默认是允许MultipartFile作为Bean一个属性  参与绑定的
  // Map<String, List<MultipartFile>>它的key,一般来说就是文件们啦~
  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);
      }
    });
  }
}


单从WebDataBinder来说,它对父类进行了增强,提供的增强能力如下:


  1. 支持对属性名以_打头的默认值处理(自动挡,能够自动处理所有的Bool、Collection、Map等)
  2. 支持对属性名以!打头的默认值处理(手动档,需要手动给某个属性赋默认值,自己控制的灵活性很高)
  3. 提供方法,支持把MultipartFile绑定到JavaBean的属性上~


Demo示例


下面以一个示例来演示使用它增强的这些功能:

@Getter
@Setter
@ToString
public class Person {
    public String name;
    public Integer age;
    // 基本数据类型
    public Boolean flag;
    public int index;
    public List<String> list;
    public Map<String, String> map;
}


演示使用!手动精确控制字段的默认值:

    public static void main(String[] args) {
        Person person = new Person();
        WebDataBinder binder = new WebDataBinder(person, "person");
        // 设置属性(此处演示一下默认值)
        MutablePropertyValues pvs = new MutablePropertyValues();
        // 使用!来模拟各个字段手动指定默认值
        //pvs.add("name", "fsx");
        pvs.add("!name", "不知火舞");
        pvs.add("age", 18);
        pvs.add("!age", 10); // 上面有确切的值了,默认值不会再生效
        binder.bind(pvs);
        System.out.println(person);
    }


打印输出(符合预期):

Person(name=null, age=null, flag=false, index=0, list=[], map={})


请用此打印结果对比一下上面的结果,你是会有很多发现,比如能够发现基本类型的默认值就是它自己。

另一个很显然的道理:若你啥都不做特殊处理,包装类型默认值那铁定都是null了~


了解了WebDataBinder后,继续看看它的一个重要子类ServletRequestDataBinder


ServletRequestDataBinder


前面说了这么多,亲有没有发现还木有聊到过我们最为常见的Web场景API:javax.servlet.ServletRequest。本类从命名上就知道,它就是为此而生。


它的目标就是:data binding from servlet request parameters to JavaBeans, including support for multipart files.从Servlet Request里把参数绑定到JavaBean里,支持multipart。


备注:到此类为止就已经把web请求限定为了Servlet Request,和Servlet规范强绑定了。


public class ServletRequestDataBinder extends WebDataBinder {
  ... // 沿用父类构造
  // 注意这个可不是父类的方法,是本类增强的~~~~意思就是kv都从request里来~~当然内部还是适配成了一个MutablePropertyValues
  public void bind(ServletRequest request) {
    // 内部最核心方法是它:WebUtils.getParametersStartingWith()  把request参数转换成一个Map
    // request.getParameterNames()
    MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
    MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
    // 调用父类的bindMultipart方法,把MultipartFile都放进MutablePropertyValues里去~~~
    if (multipartRequest != null) {
      bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
    }
    // 这个方法是本类流出来的一个扩展点~~~子类可以复写此方法自己往里继续添加
    // 比如ExtendedServletRequestDataBinder它就复写了这个方法,进行了增强(下面会说)  支持到了uriTemplateVariables的绑定
    addBindValues(mpvs, request);
    doBind(mpvs);
  }
  // 这个方法和父类的close方法类似,很少直接调用
  public void closeNoCatch() throws ServletRequestBindingException {
    if (getBindingResult().hasErrors()) {
      throw new ServletRequestBindingException("Errors binding onto object '" + getBindingResult().getObjectName() + "'", new BindException(getBindingResult()));
    }
  }
}


下面就以MockHttpServletRequest为例作为Web 请求实体,演示一个使用的小Demo。说明:MockHttpServletRequest它是HttpServletRequest的实现类~



相关文章
|
1月前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
69 2
|
存储 前端开发 Java
Spring MVC 中的数据绑定和验证机制是什么,如何使用
Spring MVC 中的数据绑定和验证机制是什么,如何使用
|
7月前
|
存储 前端开发 Java
Spring Boot中Spring MVC的表单标签库与数据绑定讲解与实战(附源码 超详细必看)
Spring Boot中Spring MVC的表单标签库与数据绑定讲解与实战(附源码 超详细必看)
102 0
|
前端开发 Java 应用服务中间件
Spring MVC-05循序渐进之数据绑定和form标签库(下) 实战从0到1
Spring MVC-05循序渐进之数据绑定和form标签库(下) 实战从0到1
727 0
|
前端开发 Java 数据安全/隐私保护
Spring MVC-05循序渐进之数据绑定和form标签库(上)
Spring MVC-05循序渐进之数据绑定和form标签库(上)
10326 0
|
XML Java API
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(上)
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)
224 0
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(上)
|
Java API Spring
Spring官网阅读(十六)Spring中的数据绑定(2)
Spring官网阅读(十六)Spring中的数据绑定(2)
252 0
Spring官网阅读(十六)Spring中的数据绑定(2)
|
XML Java 数据格式
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(下)
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(下)
275 0
|
XML 前端开发 Java
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(中)
最新最全面的Spring详解(三)——Resources,验证、数据绑定和类型转换与Spring表达式语言(SpEL)(中)
224 0
|
XML 存储 Java
聊聊 Spring 核心特性中的数据绑定 (DataBinder)
Spring 的核心特性包括 IOC 容器、事件、资源管理、国际化、校验、数据绑定、类型转换、EL 表达式、AOP。其他特性可以轻易的在网络上找到很多资料,而数据绑定这个特性即便在 Spring 官网描述却也不太多。这是因为数据绑定主要应用于 Spring 内部,对于用户而言直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。
262 0