数据绑定有助于将用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象),主要用于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");
// 待绑定的数据driver
Driver driver=new Driver();
/* 准备绑定的数据(实际应用中看做前端输入的数据) */
Map<String, Object> input = new HashMap<>();
input.put("surname", "wang");
input.put("name", "wang");
// age是整形 注1
input.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);
// 获取给定类型低属性编辑器
@Nullable
PropertyEditor 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;
@Nullable
default <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.
// 赋值到对象属性 注1
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// 绑定过程发生错误,则生成错误放到BindingResult
for (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)
设置对象属性。
@Override
public 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);
}
}
// 过渡方法
@Override
public 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;
@Nullable
public String[] keys;
}
// 本类设置属性值入口
@Override
public 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 methods
cause = 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