一、概述
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;
@Nullable
private final Object target;
private final String objectName;
@Nullable
private AbstractPropertyBindingResult bindingResult;
private boolean directFieldAccess = false;
@Nullable
private SimpleTypeConverter typeConverter;
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields = false;
private boolean autoGrowNestedPaths = true;
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
@Nullable
private String[] allowedFields;
@Nullable
private String[] disallowedFields;
@Nullable
private String[] requiredFields;
// 类型转换器
@Nullable
private ConversionService conversionService;
// 消息编码解析器
@Nullable
private 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扩展实现 | ServletConfigPropertyValues ServletRequestParameterPropertyValues |
相关生命周期 | 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
- 表达式
Expression
5.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
是一个纽带。