前言
上篇文章聊了DataBinder
,这篇文章继续聊聊实际应用中的数据绑定主菜:WebDataBinder
。
在上文的基础上,我们先来看看DataBinder
它的继承树:
继承树中可以看到,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来说,它对父类进行了增强,提供的增强能力如下:
- 支持对属性名以_打头的默认值处理(自动挡,能够自动处理所有的Bool、Collection、Map等)
- 支持对属性名以!打头的默认值处理(手动档,需要手动给某个属性赋默认值,自己控制的灵活性很高)
- 提供方法,支持把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的实现类~