一、概述
Spring 数据绑定 默认情况下只知道如何把 简单类型 (如 int 、 string 、 boolean )自动绑定到与之对应的 Java 类型的属性上。对于复杂的数据绑定场景,开发者可以通过扩展 Spring 数据绑定实现。这篇文章通过源码阅读系统了解 Spring 如何进行数据绑定。可以带着问题读:
- 既然有转换(绑定),那得需要一个类表示转换前的
数据状态。 - 既然有转换(绑定),得有一个专门的类来完成这一
动作。二、DataBinder
2.1 概述
DataBinder允许将属性值设置到目标对象上,支持验证和绑定结果分析。可以指定允许字段(allowedFields)、必填字段(requiredFields)、不允许字段(disallowedFields)、自定义属性编辑器等精确控制绑定绑定过程。2.2 DataBinder 体系结构

DataBinder实现PropertyEditorRegistry和TypeConverter接口,PropertyEditorRegistry接口提供属性编辑器(PropertyEditor)注册与查询,TypeConverter接口提供类型转换,包括通过 JDK 扩展的PropertyEditor以及spring-beans封装关于类型转换的接口TypeConverter。有关类型转换详见WebDataBinder基于DataBinder做进一步扩展,用于Web环境。DataBinder可以通过BindingResult接口(getBindingResult())获取属性绑定结果,该接口实现Error接口。默认情况下,错误生成是通过BindingErrorProcessor策略生成,可以通过setBindingErrorProcessor方法设置覆盖默认策略,生成不同类型的错误代码。- 可以自定义
MessageSource自定义校验错误,通常需要将此类错误代码解析为适当的用户可见的错误消息。2.3 DataBinder 核心属性
| 属性 | 说明 | | —- | —- | | target | 关联目标Bean | | objectName | 目标Bean名称 | | bindingResult | 属性绑定结果 | | typeConverter | 类型转换器 | | conversionService | 类型转换服务 | | messageCodesReolver | 校验错误文案Code处理器 | | validators | 关联的Bean Validator实例集合 |
public class DataBinder implements PropertyEditorRegistry, TypeConverter {/** Default object name used for binding: "target". */public static final String DEFAULT_OBJECT_NAME = "target";/** Default limit for array and collection growing: 256. */public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;@Nullableprivate final Object target;private final String objectName;@Nullableprivate AbstractPropertyBindingResult bindingResult;private boolean directFieldAccess = false;@Nullableprivate SimpleTypeConverter typeConverter;private boolean ignoreUnknownFields = true;private boolean ignoreInvalidFields = false;private boolean autoGrowNestedPaths = true;private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;@Nullableprivate String[] allowedFields;@Nullableprivate String[] disallowedFields;@Nullableprivate String[] requiredFields;// 类型转换器@Nullableprivate ConversionService conversionService;// 消息编码解析器@Nullableprivate MessageCodesResolver messageCodesResolver;// 绑定错误执行器(在绑定过程中生成一系列错误消息)private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();private final List<Validator> validators = new ArrayList<>();...}
2.4 DataBinder 示例
private static void doSimpleSiturationDataBinder() {
User user = new User();
// #1.创建DataBinder
DataBinder binder = new DataBinder(user, "user");
// #2.创建PropertyValues对象
Map<String, Object> source = new HashMap<>();
source.put("name", "Clarencezero");
source.put("age", 24);
PropertyValues propertyValue = new MutablePropertyValues(source);
// #3.绑定(需要对象提供属性的setter方法)
binder.bind(propertyValue);
}
2.5 DataBinder绑定特殊场景分析
- 当
PropertyValues中包含名称x的PropertyValue,目标对象B不存在x属性,当bind方法执行时- Nothing
- 当
PropertyValues中包含名称x.y的PropertyValue,目标对象B存在x属性(嵌套y属性),当bind方法执行时,会发生什么? - 当
PropertyValues中包含名称x的PropertyValue,目标对象B存在x属性,当bind方法执行时,如何避免B属性x不被绑定?DataBinder绑定控制参数
可以使用如下绑定控制参数进行精确绑定操作
| 参数名称 | 说明 |
|---|---|
| ignoreUnknownFields | 是否忽略未知字段,默认值: true |
| ignoreInvalidFields | 是否忽略非法字段,默认值: true |
| autoGrowNestedPaths | 是否自动增加嵌套路径,默认值: true |
| allowedFields | 绑定字段白名单 |
| disallowedFields | 绑定字段黑名单 |
| requiredFields | 必须绑定字段 |
2.6 通过源码分析 DataBinder 绑定过程
DataBinder 最重要的方法是 bind(PropertyValues)
// org.springframework.validation.DataBinder#bind
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
doBind(mpvs);
}
// org.springframework.validation.DataBinder#doBind
protected void doBind(MutablePropertyValues mpvs) {
// #1 根据允许的字段检查给定的属性值,删除不允许的字段的值。
checkAllowedFields(mpvs);
// #2 检查必须字段
checkRequiredFields(mpvs);
// #3 进行属性绑定
applyPropertyValues(mpvs);
}
// org.springframework.validation.DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// #1 getPropertyAccessor(): 得到属性获取器,实际上是得到BeanWrapper实现类,下面有代码详细分析
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
} catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
// org.springframework.validation.DataBinder#getPropertyAccessor
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
// org.springframework.validation.DataBinder#getInternalBindingResult
private boolean directFieldAccess = false;
protected AbstractPropertyBindingResult getInternalBindingResult() {
// 根据directFieldAccess创建 AbstractPropertyBindingResult 对象,该抽象类有两个实现类,分别是
// org.springframework.validation.DirectFieldBindingResult(使用反向完成属性操作)
// org.springframework.validation.BeanPropertyBindingResult(使用标准 JavaBeans API 完成属性操作)
// Spring 默认选用后者完成对属性操作
if (this.bindingResult == null) {
this.bindingResult = (this.directFieldAccess ?
createDirectFieldBindingResult() : createBeanPropertyBindingResult());
}
return this.bindingResult;
}
// org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
// 底层使用 Beanwrapper 对对象进行包装,也就是说,
// 其实 org.springframework.validation.DataBinder#getPropertyAccessor也就返回一个BeanWrapperImpl 对象
// 并利用它来完成类型转换、校验等一系列操作。绑定过程中异常处理是由DateBinder本身完成的
public final ConfigurablePropertyAccessor getPropertyAccessor() {
if (this.beanWrapper == null) {
this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true);
this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
}
return this.beanWrapper;
}
2.7 DateBinder小结
DateBinder可单独使用进行对象属性绑定操作。DateBinder通过构造一个BeanWrapper实例对象,由beanwrapperimpl实现类来完成最终的类型转换操作。DateBinder通过BindingErrorProcessor绑定异常处理器记录绑定过程中出现的异常和错误。如类型不匹配、必须字段缺失等等。三、元数据
对于数据绑定过程而言,Spring把属性值封装为PropertyValue,而PropertyValues(它只是一个接口,MultablePropertyValues是实现类)持有一个List<PropertyValue>集合。3.1 PropertyValue
持有单个bean属性的信息和值的对象。
在这里使用对象,而不仅仅把所有属性值存储在一个Map的键值对中,是因为它可以提供更大的灵活性,并能够以优化的方式处理索引属性等。此外PropertyValue实现多个接口方便属性、值的解析与获取。
3.2 PropertyValues (接口)
3.2.1 概述
| 特征 | 说明 |
|---|---|
| 数据来源 | BeanDefinition,主要来源 XML 资源配置BeanDefinition |
| 数据结构 | 由一个或多个 PropertyValue 组成 |
| 成员结构 | PropertyValue包含属性名称,以及属性值(包含原始值、类型转换后的值) |
| 常见实现 | MutablePropertyValues |
| Web扩展实现 | ServletConfigPropertyValuesServletRequestParameterPropertyValues |
| 相关生命周期 | InstantiationAwareBeanPostProcessor#postProcessorProperties |
3.2.2 继承体系

PropertyValue 数据主要来源 XML 文件,如果通过Java代码是不需要进行类型转换。因为XML定义的是字符串类型,但我们需要把这里 属性值 转换为对象的属性类型,比如 枚举、Integer 等。
3.2.3 PropertyValues接口方法
3.2.4 实现类 — MutablePropertyValues
MutablePropertyValues是PropertyValue 默认实现,允许简单地操作属性,并提供构造函数以支持从 Map 中进行深度复制和构造。接口方法(主要):
<br />
3.2.5 使用 MutablePropertyValues 绑定示例:
public class User {
List<String> names;
public List<String> getNames() {
return names;
}
public void setNames(List<String> names) {
this.names = names;
}
}
private static void doDataBindList(){
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("names[0]", "FirstName");
propertyValues.add("names[1]", "LastName");
dataBinder.bind(propertyValues);
System.out.println(user);
System.out.println(user.getNames().size());
}
五、PropertyEditor(转换器,JDK提供)
5.1 概述
PropertyEditor 类原本是在 GUI 界面使用,允许用户编辑给定类型的属性值。支持多种不同种类的显示和更新属性值的方式。它规定了将外部值转换为内部 JavaBean 属性值的 转换 接口方法。接口如下: 
PropertyEditor 接口方法是 内部属性值 和 外部设置值 的沟通桥梁。PropertyEditor 默认实现是 PropertyEditorSupport ,用户可以通过此类设计自己的实现类。
5.2 自定义PropertyEditor
- 继承
PropertyEditorSupport接口。 - 分别实现
setAsText(String)、getAsText()方法。 通过创建自定义
PropertyEditorRegistrar注册自定义属性编辑器5.2.1 继承 PropertyEditor 并实现 setAsText(String) 、 getAsText() 方法。
```java /**
- 该自定义编辑器可以将指定时间字符串 yyyy==MM==dd HH时mm分ss秒 转换为LocalDateTime对象。
也可以通过 getAsText 方法返回特定格式的字符串 */ class MyDatePropsEditor extends PropertyEditorSupport { private String pattern;
public MyDatePropsEditor() { this.pattern = “yyyy==MM==dd HH时mm分ss秒”; }
public MyDatePropsEditor(String pattern) {
this.pattern = pattern;
}
// 解析字符为自定义类型,通过setValue()方法保存解析后的值
// 父类PropertyEditorSupport持有一个Object引用,用来保存解析后的对象
@Override
public void setAsText(String text) throws IllegalArgumentException {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
LocalDateTime localDateTime = LocalDateTime.parse(text, dateTimeFormatter);
setValue(localDateTime);
}
@Override
public String getAsText() {
if (getValue() instanceof LocalDateTime) {
LocalDateTime date = (LocalDateTime) getValue();
return date.toString();
}
return super.getAsText();
}
}
<a name="eyGE2"></a>
### 5.2.2 实现自定义属性注册器
- 实现 `PropertyEditorRegistrar` 完成属性编辑器的注册。
```java
public class CustomTimeStrToLocalTimeResistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 1. 只设置类型: 表明这个属性编辑器只针对于这一特定类型
// registry.registerCustomEditor(LocalDateTime.class, new CustomTimestrToDatePropertyEditor());
// 2. 类型+属性名称: 表示这个属性编辑器针对这个类的特定属性
registry.registerCustomEditor(LocalDateTime.class, "localDateTime", new CustomTimestrToDatePropertyEditor());
}
}
5.2.3 通过 XML 或 @Bean 注解配置注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.clarencezero.c2_core.custom_property_editor.UserByProperty">
<property name="localDateTime" value="2020==08==31 20时12分12秒" />
</bean>
<!--将自定义属性注册器交给Spring管理-->
<bean id="customTimeStrToLocalTimeResistrar" class="com.clarencezero.c2_core.custom_property_editor.CustomTimeStrToLocalTimeResistrar" />
<!--配置CustomEditorConfigurer-->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customTimeStrToLocalTimeResistrar"/>
</list>
</property>
</bean>
</beans>
@Configurable
public class CustomPropertyEditoryConfiguration {
@Bean
public PropertyEditorRegistrar customTimeStrToLocalTimeResistrar() {
return new CustomTimeStrToLocalTimeResistrar();
}
@Bean
public CustomEditorConfigurer customEditorConfigurer() {
CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
PropertyEditorRegistrar[] propertyEditorRegistrars = new PropertyEditorRegistrar[1];
propertyEditorRegistrars[0] = customTimeStrToLocalTimeResistrar();
customEditorConfigurer.setPropertyEditorRegistrars(propertyEditorRegistrars);
return customEditorConfigurer;
}
}
五、JDK的JavaBean
5.1 什么是JavaBean
- 所有属性修饰符为
private(使用getters/setters) - 有一个
public无参构造器 -
5.2 JavaBeans核心实现—
java.beans.BeanInfo 属性
Property—java.beans.PropertyEditor- 方法
Method - 事件
Event - 表达式
Expression5.3 标准的
| API | 说明 | | —- | —- | | java.beans.Introspector | Java Beans内省API | | java.beans.BeanInfo | Java Bean元信息API | | java.beans.BeanDescriptor | Java Bean信息描述符 | | java.beans.PropertyDescriptor | Java Bean属性描述符 | | java.beans.MethodDescriptor | Java Bean方法描述符 | | java.beans.EventSetDescriptor | Java Bean事件集合描述符 |JavaBeans是如何操作属性的?
5.4 简单实例
private static void doJavaBeansIntrospector() throws Exception{
BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
System.out.println("------遍历属性描述符-----");
System.out.println("属性名称:" + propertyDescriptor.getName());
System.out.println("get方法:" + propertyDescriptor.getReadMethod());
System.out.println("属性类型:" + propertyDescriptor.getPropertyType());
System.out.println("set方法:" + propertyDescriptor.getWriteMethod());
System.out.println("表示名称:" + propertyDescriptor.getDisplayName());
}
System.out.println("--------------------------------------");
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
System.out.println(beanDescriptor.getName());
System.out.println("--------------------------------------");
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
for (MethodDescriptor methodDescriptor : methodDescriptors) {
System.out.println("------遍历方法描述符------");
System.out.println("方法名称:" + methodDescriptor.getName());
System.out.println("方法对象:" + methodDescriptor.getMethod());
// System.out.println("方法参数描述:" + methodDescriptor.getParameterDescriptors()[0]);
}
}
5.5 小结
JDK 提供一套内省机制(Introspector)访问JavaBean ,就像 X光 照射,窥探里面的 属性 、 方法 等。 JDK 也提供了 JavaBean 事件机制。与反射区别:
反射可以对任意类进行操作,而内省只能操作JavaBean类型。内省需要类提供setter方法,才能完成属性赋值,而反射直接操作属性Field。反射出来的数据是客观事件,有就有,没有就没有。而内省更像主观判断,如果存在get/set方法,就认为会存在这个属性。比如getClass方法。六、△BeanWrapper
6.1 BeanWrapper 概述
spring-beans包下非常重要的类是BeanWrapper以及其相应的实现类BeanWrapperImpl。提供以下能力:设置/获取
属性值(单个/批量)- 获取属性描述符(
PropertyDescriptor) - 查询属性以确定
可读/可写功能 - 允许将子属性的属性设置为无限深度
- 支持标准 JDK
JavaBeans相关的PropertyChangeListeners和VetoableChangeListeners,而不需要在目标类中写相关的代码 - 提供对设置索引属性的支持
Spring 底层 JavaBean 基础设施中央接口。通常不会直接使用,而是通过 BeanFactory 或 org.springframework.validation.DataBinder 隐式调用。
6.2 BeanWrapper继承体系
6.2.1 PropertyEditorRegistry
6.2.2 PropertyAccessor
| 表达式 | 说明 |
|---|---|
| name | 最简单属性名称 |
| a.b | 嵌套属性 |
| a[1] | 索引属性第二个元素。属性可能为 数组 、 有序集合 |
| r[‘a’] | 指向一个 Map 属性,键为 a |
- 可以访问命名属性的类的通用接口(例如对象的Bean属性或对象中的字段),包含嵌套属性访问。
根据属性名称判断
可读/可写、获取属性描述符、给属性填充对应的值。
PropertyAccesor的实现类有两个,分别是:BeanWrapperImpl: 使用JavaBean内省API完成setter方法获取,然后通过反射注入属性。Spring默认使用此实现完成属性写入。DirectFieldAccessor: 直接通过反射来获取字段。不需要Bean提供gettter/setter方法。6.2.3 ConfigurablePropertyAccessor
可配置属性获取接口,三合一接口
- 扩展关于
ConversionService(Spring3.0+ 新一套类型转换接口)set/get方法
上述可知, BeanWrapper 拥有属性编辑器 注册 ,属性填充指定值,管理 ConversionService 类型转换等功能。
7.3 BeanWrapper 定义相关接口
public interface BeanWrapper extends ConfigurablePropertyAccessor {
// 指定数组和集合自动增长的限制。默认无限制
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
// 返回数组和集合自动增长的限制。
int getAutoGrowCollectionLimit();
// 返回此对象包装的Bean实例,此时对象并未进行赋值的初始化对象
Object getWrappedInstance();
// 返回被包装的类类型
Class<?> getWrappedClass();
// 获取被包装类的属性描述符(由标准JavaBean内省确定)
PropertyDescriptor[] getPropertyDescriptors();
// 获取指定包装类属性的属性描述符
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}
7.3 BeanWapper 实现类 BeanWrapperImpl
7.3.1 概述
默认的 BeanWrapper 实现应足以应付所有典型的用例,缓存自省结果以提高效率。
7.3.2 BeanWapperImpl 继承体系
① TypeConverter
类型转换器接口定义,将指定的值转换为指定的类型。相关类型转换请看这里
7.6 简述 Spring 如何通过 BeanWrapper 完成数据绑定与类型转换
- 在
ApplicationContext#finishBeanFactoryInitialization阶段通过依赖查找类型为ConversionService、名称为conversionService的bean并存入BeanFactory中。这是Spring 3.0+新一套类型转换接口。 - 经过
BeanFactoryPostProcessor完成类路径扫描之后,生成对应的BeanDefinition。 - 循环遍历
BeanDefinitionName数据,挨个实现化Bean对象。 - 通过
BeanWrapperImpl包装已使用反射完成实例化的对象,此时只是一个空壳对象,还未完成属性注入与Spring生命周期流程。 - 调用
AbstractAutowireCapableBeanFactory#populateBean方法完成属性注入
Spring底层JavaBeans基础设施的中心化接口- 通常不会直接使用,间接用于
BeanFactory和DataBinder - 提供标准
JavaBeans分析和操作,能够单独或批量存储Java Bean的属性(properties) - 支持嵌套属性路径
- 实现类
org.springframework.beans.BeanWrapperImpl
八、总结
- 对象属性值使用
PropertyValues进行包装而不是简单的Map对象。这样可以拥有更宽泛的操作空间。 - 通过反射实例化的对象被
BeanWrapperImpl对象包装,这个对象拥有很强大的能力。主要能力包括类型转换以及数据绑定 类型转换与数据绑定是通过代理类TypeConverterDelegate完成,而它最终也是通过Spring 3.0+相关的ConversionService以及JDK的PropertyEditor完成相应的动作。BeanWrapperImpl是一个纽带。

