建设银行网站邮箱磁力搜索引擎2023
数据绑定有助于将用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象),主要用于web层,但实际可用于任何层。Spring提供了DataBinder来做到这一点,并提供了Validator进行数据验证,两者组成了完整的数据输入数据处理机制。但数据绑定主要应用于 Spring 内部(如在SpringMVC中,http传入/返回参数如何绑定到具体参数或类上,就是通过数据绑定进行的),开发者直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。
体验数据绑定
先以如下例子体验数据绑定:
@Component
public class DemoDatabinder{public void demo() {// 数据Address Address addr=new Address();addr.setProvince("四川");addr.setCity("成都");addr.setCounty("高新区");addr.setDesc("学习大道168");// 待绑定的数据driverDriver driver=new Driver();/* 准备绑定的数据(实际应用中看做前端输入的数据) */Map<String, Object> input = new HashMap<>();input.put("surname", "wang");input.put("name", "wang");// age是整形 注1input.put("age", 20);// address是对象input.put("address", addr);/* 绑定数据 *///把准备绑定的数据转换成绑定PropertyValues PropertyValues propertyValues = new MutablePropertyValues(input);// 用DataBinder 进行绑定DataBinder dataBinder = new DataBinder(driver);dataBinder.bind(propertyValues); BindingResult br = dataBinder.getBindingResult(); if(br.hasErrors())System.out.println("数据绑定错误");}
}
注1:age是整形,输入值可以是整形20,也可以是字符串“20”或“20 ”(包含空格)
从上例中可看出,数据绑定就是把输入数据(input)绑定到对象(driver)的对应属性上,绑定有以下要求:
1、输入数据名同对象属性名相同。
2、输入数据值的数据类型同对象属性的数据类型可以不一致,绑定会自动转换。
Spring数据绑定
功能
Spring中,DataBinder有两大功能:
1、设置对象属性值。
2、对数据进行校验(数据校验见:https://blog.csdn.net/davidwkx/article/details/131401699)。
作用
Spring作为一个成熟框架,数据绑定是重要特性之一,数据绑定主要作用体现如下:
1、在WEB环境下,Spring可自动把Http中的请求参数或者请求体中的Json字符串绑定到对应实体对象上,这样可大大提高开发人员的效率,这是开发人员直观感知到的数据绑定。非WEB环境,开发人员也可按需使用数据绑定技术,只不过很少使用而已。
2、Spring框架本身也用到数据绑定:在xml中配置bean属性或注解配置属性,也是用的数据绑定来实现的。
类关系图
从图中可以看出,除了DataBinder使用的数据PropertyValues,关系图比较简单,只是实现了PropertyEditorRegistry, TypeConverter。
BindingResult继承于Errors,保存绑定结果,包括错误信息。
PropertyEditorRegistry
设置bean属性值的属性编辑器,代码如下:
public interface PropertyEditorRegistry {// 为给定类型的所有属性设置属性编辑器void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);// 获取给定类型低属性编辑器@NullablePropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}
TypeConverter
转换数据到指定的类型,接口定义简单(仅参数不同):
public interface TypeConverter {@Nullable<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException;@Nullable<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException;@Nullable<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field) throws TypeMismatchException;@Nullabledefault <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {throw new UnsupportedOperationException("TypeDescriptor resolution not supported");}
}
源码跟踪
DataBinder.bind(PropertyValues pvs)
// 过渡类public void bind(PropertyValues pvs) {MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues mutablePropertyValues ?mutablePropertyValues : new MutablePropertyValues(pvs));doBind(mpvs);}// 绑定处理protected void doBind(MutablePropertyValues mpvs) {// 根据允许绑定的字段检查给定的属性值,删除不允许的字段的值checkAllowedFields(mpvs);// 根据所需绑定字段检查给定的属性值,对于必须有值的字段如果没有值,生成缺少值的错误信息checkRequiredFields(mpvs);// 赋值到对象属性applyPropertyValues(mpvs);}// 过渡类protected void applyPropertyValues(MutablePropertyValues mpvs) {try {// Bind request parameters onto target object.// 赋值到对象属性 注1getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());}catch (PropertyBatchUpdateException ex) {// 绑定过程发生错误,则生成错误放到BindingResultfor (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());}}}
注1:此处实际调用BeanWrapperImpl.setPropertyValues,其类关系图如下:
BeanWrapperImpl是BeanWrapper的默认实现,BeanWrapper是Spring服务于JavaBeans的底层接口,主要提供分析和操作JavaBeans,包括:获取和设置属性值(单独或批量)、获取属性描述符以及查询属性可读性/可写性的能力等。BeanWrapperImpl关系图比较复杂,这里不具体分析,只关注数据绑定用到的实现在AbstractPropertyAccessor.setPropertyValues
AbstractPropertyAccessor.setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
设置对象属性。
@Overridepublic void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)throws BeansException {List<PropertyAccessException> propertyAccessExceptions = null;// 把数据转换成MutablePropertyValues List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues mpvs ?mpvs.getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));if (ignoreUnknown) {this.suppressNotWritablePropertyException = true;}try {// 按数据逐一设置for (PropertyValue pv : propertyValues) {try {setPropertyValue(pv);}catch (NotWritablePropertyException ex) {if (!ignoreUnknown) {throw ex;}// Otherwise, just ignore it and continue...}catch (NullValueInNestedPathException ex) {if (!ignoreInvalid) {throw ex;}// Otherwise, just ignore it and continue...}catch (PropertyAccessException ex) {if (propertyAccessExceptions == null) {propertyAccessExceptions = new ArrayList<>();}propertyAccessExceptions.add(ex);}}}finally {if (ignoreUnknown) {this.suppressNotWritablePropertyException = false;}}// 如果有属性可访问性异常,组合成一个异常抛出if (propertyAccessExceptions != null) {PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);throw new PropertyBatchUpdateException(paeArray);}}// 过渡方法@Overridepublic void setPropertyValue(PropertyValue pv) throws BeansException {// 实现在AbstractNestablePropertyAccessor setPropertyValue(pv.getName(), pv.getValue());}
AbstractNestablePropertyAccessor .setPropertyValue(String propertyName, @Nullable Object value)
// 列出这个类便于后续理解-AbstractNestablePropertyAccessor内部类:用于存储属性token的Holder类protected static class PropertyTokenHolder {public PropertyTokenHolder(String name) {this.actualName = name;this.canonicalName = name;}public String actualName;public String canonicalName;@Nullablepublic String[] keys;}// 本类设置属性值入口@Overridepublic void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {AbstractNestablePropertyAccessor nestedPa;try {// 获取属性访问器nestedPa = getPropertyAccessorForPropertyPath(propertyName);}catch (NotReadablePropertyException ex) {throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,"Nested property in path '" + propertyName + "' does not exist", ex);}// 将属性名称解析为相应的属性名称标记PropertyTokenHolder PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));// setPropertyValue在AbstractNestablePropertyAccessor 实现nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));}// 分两种情况处理protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {if (tokens.keys != null) {// 集合类属性赋值processKeyedProperty(tokens, pv);}else {// 单个(非集合类)属性赋值processLocalProperty(tokens, pv);}}// 集合类属性赋值@SuppressWarnings({"unchecked", "rawtypes"})private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {Object propValue = getPropertyHoldingValue(tokens);PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);if (ph == null) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.actualName, "No property handler found");}Assert.state(tokens.keys != null, "No token keys");String lastKey = tokens.keys[tokens.keys.length - 1];// 属性是数组类型if (propValue.getClass().isArray()) {Class<?> requiredType = propValue.getClass().getComponentType();int arrayIndex = Integer.parseInt(lastKey);Object oldValue = null;try {if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {oldValue = Array.get(propValue, arrayIndex);}// 转换数据为属性值类型Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),requiredType, ph.nested(tokens.keys.length));int length = Array.getLength(propValue);if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {Class<?> componentType = propValue.getClass().getComponentType();Object newArray = Array.newInstance(componentType, arrayIndex + 1);System.arraycopy(propValue, 0, newArray, 0, length);int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');String propName = tokens.canonicalName.substring(0, lastKeyIndex);setPropertyValue(propName, newArray);propValue = getPropertyValue(propName);}// Array赋值Array.set(propValue, arrayIndex, convertedValue);}catch (IndexOutOfBoundsException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,"Invalid array index in property path '" + tokens.canonicalName + "'", ex);}}// 属性是list类型else if (propValue instanceof List list) {Class<?> requiredType = ph.getCollectionType(tokens.keys.length);int index = Integer.parseInt(lastKey);Object oldValue = null;if (isExtractOldValueForEditor() && index < list.size()) {oldValue = list.get(index);}Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),requiredType, ph.nested(tokens.keys.length));int size = list.size();if (index >= size && index < this.autoGrowCollectionLimit) {for (int i = size; i < index; i++) {try {list.add(null);}catch (NullPointerException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,"Cannot set element with index " + index + " in List of size " +size + ", accessed using property path '" + tokens.canonicalName +"': List does not support filling up gaps with null elements");}}// list赋值-增加list.add(convertedValue);}else {try {// list赋值-增加或修改list.set(index, convertedValue);}catch (IndexOutOfBoundsException ex) {throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,"Invalid list index in property path '" + tokens.canonicalName + "'", ex);}}}// 属性是Map 类型else if (propValue instanceof Map map) {Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);// IMPORTANT: Do not pass full property name in here - property editors// must not kick in for map keys but rather only for map values.TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);Object oldValue = null;if (isExtractOldValueForEditor()) {oldValue = map.get(convertedMapKey);}// Pass full property name and old value in here, since we want full// conversion ability for map values.Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),mapValueType, ph.nested(tokens.keys.length));// map赋值map.put(convertedMapKey, convertedMapValue);}else {throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,"Property referenced in indexed property path '" + tokens.canonicalName +"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");}}// 单个(非集合类)属性赋值private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);if (ph == null || !ph.isWritable()) {if (pv.isOptional()) {if (logger.isDebugEnabled()) {logger.debug("Ignoring optional value for property '" + tokens.actualName +"' - property not found on bean class [" + getRootClass().getName() + "]");}return;}if (this.suppressNotWritablePropertyException) {// Optimization for common ignoreUnknown=true scenario since the// exception would be caught and swallowed higher up anyway...return;}throw createNotWritablePropertyException(tokens.canonicalName);}Object oldValue = null;try {Object originalValue = pv.getValue();Object valueToApply = originalValue;if (!Boolean.FALSE.equals(pv.conversionNecessary)) {/* 数据类型转换 */if (pv.isConverted()) {// 已转换直接取转换后的值valueToApply = pv.getConvertedValue();}else {if (isExtractOldValueForEditor() && ph.isReadable()) {try {oldValue = ph.getValue();}catch (Exception ex) {if (ex instanceof PrivilegedActionException pae) {ex = pae.getException();}if (logger.isDebugEnabled()) {logger.debug("Could not read previous value of property '" +this.nestedPath + tokens.canonicalName + "'", ex);}}}// 数据类型转换valueToApply = convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());}pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);}// 赋值ph.setValue(valueToApply);}catch (TypeMismatchException ex) {throw ex;}catch (InvocationTargetException ex) {PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());if (ex.getTargetException() instanceof ClassCastException) {throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());}else {Throwable cause = ex.getTargetException();if (cause instanceof UndeclaredThrowableException) {// May happen e.g. with Groovy-generated methodscause = cause.getCause();}throw new MethodInvocationException(propertyChangeEvent, cause);}}catch (Exception ex) {PropertyChangeEvent pce = new PropertyChangeEvent(getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());throw new MethodInvocationException(pce, ex);}}
注:关于数据类型转换详见:https://blog.csdn.net/davidwkx/article/details/131913840