DataBinder的子类
子类概览
可以看到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,它主要做了以下三点增强
- 可以手动为Bean中的属性提供默认值(提供“!”开头的属性名称)
- 可以让容器对属性字段赋上某些空值(提供“_”开头的属性名称)
- 可以将文件绑定到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具备类型转换的能力,正是为了更好的完成数据绑定。