【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)
【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor
对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码) 前言上篇文章聊了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示例