DataBinder 是Spring用于数据绑定、类型转换及验证的类。使用场景有:1)xml配置文件定义bean,Spring 内部使用DataBinder 来完成属性的绑定;2)Web请求参数绑定,在Spring MVC 中,Controller的方法参数通常会自动绑定到请求参数中,主要用DataBinder来完成。3)自定义数据绑定,可手动创建DataBinder 对象,为其设置校验器和转换器,来满足特定需求。
1 DataBinder 类
图 DataBinder UML图
PropertyEditorRegistry:
PropertyEditor 注册商,用来管理及保存PropertyEditor(JDK自带接口,支持各种不同的方式来显示和更新属性值,比如在Spring的xml中,配置Bean 时,把字符串类型转化成对应的Integer、File等类型)。定义了注册PropertyEditor 的接口。
TypeConverter:
定义了类型转化方法。
public class DataBinderTest {
public static void main(String[] args) throws BindException {
User user = new User();
DataBinder binder = new DataBinder(user);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("username","黄先生");
propertyValues.add("address.province","广东");
propertyValues.add("address.city","深圳");
propertyValues.add("infos['job']","程序员");
propertyValues.add("age",28);
binder.bind(propertyValues);
System.out.println(user); // User{username='黄先生', age=28, address=Address{province='广东', city='深圳'}, infos={job=程序员}}
}
public static class User {
private String username;
private Integer age;
private Address address;
private Map<String,Object> infos;
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setAddress(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
public Map<String, Object> getInfos() {
return infos;
}
public void setInfos(Map<String, Object> infos) {
this.infos = infos;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
", address=" + address +
", infos=" + infos +
'}';
}
}
public static class Address {
private String province;
private String city;
public void setProvince(String province) {
this.province = province;
}
public String getProvince() {
return province;
}
public void setCity(String city) {
this.city = city;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
}
}
2 数值绑定过程
如上代码。使用了DataBinder的bind(PropertyValues pvs) 来将属性值绑定到目标对象中。
图 DataBinder的bind 方法
图 数据绑定过程中的方法调用
2.1 PropertyValues
PropertyValues 用于保存一个或多个Property的接口。MutablePropertyValues是其实现类。新增了对Property 新增、删除等操作。
2.1.1 Property
图 Property UML
AttributeAccessor: 定义了对象属性的访问及设置值等操作的接口。
AttributeAccessorSupport: 是AttributeAccessor 的实现类,这里的属性操作对象是一个Map类型。
BeanMetadataElement:定义了一个方法getSource(),用于获取Bean元数据的源对象。这个源对象通常是源文件信息。
BeanMetadataAttributeAccessor: 会覆盖AttributeAccessorSupport中设置和获取属性的方法,会将属性转换为BeanMetadataAttribute,目的是为了跟踪Bean定义的源对象。
BeanMetadataAttribute: 在Spring容器中,Bean的定义来源与多种不同的配置方式,例如XML、注解、配置类等。当Spring容器解析Bean的定义时,它会将源信息(如XML文件的路径、注解的位置等)与Bean的定义关联起来,这样,在后续处理中,如果需要对Bean的定义进行查找、修改或报告错误,Spring可以使用这个源信息。
Property:与BeanMetadataAttributeAccessor 不同的是,Property将属性和值封装在一个对象中,而不是使用Map对象来保存所有属性。这种设计提供了更大的灵活性和便利性,并允许处理索引属性以进行优化。
2.2 applyPropertyValues方法
在该方法中,执行下面语句来完成对目标对象的赋值:
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
getPropertyAccessor()方法:
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
getInternalBindingResult()方法:
protected AbstractPropertyBindingResult getInternalBindingResult() {
if (this.bindingResult == null) {
initBeanPropertyAccess();
}
return this.bindingResult;
}
调用关系为:
AbstractPropertyBindingResult.getPropertyAccessor() -> ConfigurablePropertyAccessor.setPropertyValues(参数) 完成对象属性赋值。
2.2.1 AbstractPropertyBindingResult
数据绑定的结果。BindingResult用于处理数据绑定的错误。主要用来收集、存储和管理在数据绑定过程中发生的错误。
图 AbstractPropertyBindingResult UML
AbstractPorpertyBindingResult 是一个抽象类,定义了一个抽象方法:getPropertyAccessor。返回ConfigurablePropertyAccessor类型。有两个实现类。
DirectFieldBindingResult | 用于能直接访问的对象,而不是通过getter和setter方法访问的。 |
BeanPropertyBindingResult | 默认实现,标准bean,通过getter和setter方法访问的。 |
表 AbstractPropertyBindingResult两个实现类
图 BeanPropertyBindingResult的getPropertyAccessor方法
图 BeanPropertyBindingResult 类的代码
PropertyAccessorFactory 是一个简单的工厂类,根据对象属性是否直接访问来创建对应的ConfigurablePropertyAccessor。
2.2.2 ConfigurablePropertyAccessor
提供了灵活的方式来访问和操作Bean对象的属性,并支持类型转化和属性编辑等功能。该接口新增了设置及获取ConversionService的方法。
图 ConfigurablePropertyAccessor接口 UML
PropertyAccessor接口:提供了可以访问命名属性(bean属性或对象中的字段)的接口。提供了对JavaBean属性的读取和写入操作。
图 PropertyAccessor 接口的方法
2.3 BeanWrapperImpl
为标准bean属性的访问实现的方法。还支持类型转化及自定义属性编辑等功能。
图 BeanWrapperImpl UML
BeanWrapper 继承了ConfigurablePropertyAccessor 接口,并增加了getWrappedInstance()获取将目标对象包装后的对象等方法。
AbstractNestablePropertyAccessor 主要用于支持嵌套属性的访问和类型转换。
2.3.1 AbstractNestablePropertyAccessor
图 AbstractPropertyAccessor 中的setPropertyValues方法部分截图
通过遍历属性值的List集合,来为每个属性值进行绑定。
图 AbstractNestablePropertyAccessor 中的setPropertyValue 方法
AbstractNestrablePropertyAccessor 中实现了父类抽象类的抽象方法setPropertyValue,来完成对单个属性值的绑定。
PropertyTokenHolder 是AbstractNestrablePropertyAccessor 的一个内部类,主要作用是解析和处理嵌套属性的名称,并将它们转换成可访问和操作的形式。
actualName | 属性的实际名称。可能包含了方括号和引号等元素格式。 |
canonicalName | 属性的规范名称。会去除属性名称中的方括号和引号,并将属性名称转换为规范的形式。例如,person[‘address’][‘city’]转换为address.city。 |
keys:String[] | 保存了属性的键列表,例如上面的属性保存为[“address”,“city”] |
表 PropertyTokenHolder 的字段
图 PropertyTokenHolder 代码
2.4 processLocalProperty方法完成属性类型转换及绑定
图 processLocalProperty方法中的关键代码
2.4.1 PropertyHandler
是一个抽象类,用于处理属性的读取和写入操作。定义了获取/设置属性值及检查属性是否可读可写的方法。其默认实现是BeanPropertyHandler, 针对JavaBean属性,使用JavaBean规范来操作属性。如果不是通过getter和setter方法访问的,则使用LocalPropertyHandler。