文章目录
- 一、概述
- 二、Spring 数据绑定组件
- 1、DataBinder 核心属性
- 2、DataBinder 绑定方法
- 1、DataBinder 元数据 - PropertyValues
- 1、DataBinder 绑定特殊场景分析
- 2、DataBinder 绑定控制参数
- 3、代码实例
- 五、Spring 底层 Java Beans 替换实现
- 1、BeanWrapper 的使用场景
- 2、标准 JavaBeans 是如何操作属性的
- 3、DataBinder 数据校验
- 六、SpringMVC数据绑定
- 参考资料
一、概述
Spring数据绑定使用场景主要有三个:
- Spring BeanDefinition 到 Bean 实例创建,Bean在实例化的过程中涉及数据绑定(注解方式不需要)
- Spring 数据绑定(DataBinder)
- Spring Web 参数绑定(WebDataBinder)(包含SpringMVC、SpringWebFlux)
二、Spring 数据绑定组件
标准组件:
org.springframework.validation.DataBinder
Web 组件:
org.springframework.web.bind.WebDataBinder
org.springframework.web.bind.ServletRequestDataBinder
org.springframework.web.bind.support.WebRequestDataBinder
org.springframework.web.bind.support.WebExchangeDataBinder(since 5.0)
1、DataBinder 核心属性
属性
| 说明
|
target
| 关联目标 Bean
|
objectName
| 目标 Bean名称
|
bindingResult
| 属性绑定结果
|
typeConverter
| 类型转换器
|
conversionService
| 类型转换服务
|
messageCodesResolver
| 校验错误文案 Code 处理器
|
validators
| 关联的 Bean Validator 实例集合
|
2、DataBinder 绑定方法
bind(PropertyValues)方法:将 PropertyValues Key-Value 内容映射到关联 Bean(target)中的属性上。
假设 PropertyValues 中包含“name = 张三”的键值对,同时 Bean 对象 User 中存在 name属性,当 bind 方法执行时,User 对象中的 name 属性值将被绑定为 “张三”。
// org.springframework.validation.DataBinder#bind
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
doBind(mpvs);
}
三、Spring 数据绑定元数据
1、DataBinder 元数据 - PropertyValues
特征
| 说明
|
数据来源
| BeanDefinition,主要来源 XML 资源配置 BeanDefinition
|
数据结构
| 由一个或多个 PropertyValue 组成
|
成员结构
| PropertyValue 包含属性名称,以及属性值(包括原始值、类型转换后的值)
|
常见实现
| MutablePropertyValues
|
Web 扩展实现
| ServletConfigPropertyValues、ServletRequestParameterPropertyValues
|
相关生命周期
| InstantiationAwareBeanPostProcessor#postProcessProperties
|
DataBinder核心方法bind需要一个参数PropertyValues,而PropertyValues我们在BeanDefinition中有看到过:
// org.springframework.beans.factory.config.BeanDefinition#getPropertyValues
MutablePropertyValues getPropertyValues();
// org.springframework.beans.factory.config.BeanDefinition#hasPropertyValues
default boolean hasPropertyValues() {
return !getPropertyValues().isEmpty();
}
MutablePropertyValues就是实现了PropertyValues接口。此时我们基本也就跟前面的知识点串起来了。
PropertyValues接口是继承了Iterable<PropertyValue>,代表是可以被迭代的,也就是说PropertyValues是PropertyValue的集合。
PropertyValue包含许多属性转换和类型绑定的信息。
四、Spring 数据绑定控制参数
1、DataBinder 绑定特殊场景分析
- 当 PropertyValues 中包含名称 x 的 PropertyValue,目标对象 B 不存在 x 属性,当 bind 方法执行时,会发生什么?
- 当 PropertyValues 中包含名称 x 的 PropertyValue,目标对象 B 中存在 x 属性,当 bind 方法执行时,如何避免 B 属性 x 不被绑定?
- 当 PropertyValues 中包含名称 x.y 的 PropertyValue,目标对象 B 中存在 x 属性(嵌套 y 属性),当 bind 方法执行时,会发生什么?
2、DataBinder 绑定控制参数
参数名称
| 说明
|
ignoreUnknownFields
| 是否忽略未知字段,默认值:true
|
ignoreInvalidFields
| 是否忽略非法字段,默认值:false
|
autoGrowNestedPaths
| 是否自动增加嵌套路径,默认值:true
|
allowedFields
| 绑定字段白名单
|
disallowedFields
| 绑定字段黑名单
|
requiredFields
| 必须绑定字段
|
3、代码实例
// 创建空白对象
User user = new User();
// 1. 创建 DataBinder
DataBinder binder = new DataBinder(user, "user");
// 2. 创建 PropertyValues
Map<String, Object> source = new HashMap<>();
source.put("id", 1);
source.put("name", "张三");
// a. PropertyValues 存在 User 中不存在属性值
// DataBinder 特性一 : 会忽略未知的属性
source.put("age", 18);
// b. PropertyValues 存在一个嵌套属性,比如 company.name
// DataBinder 特性二:支持嵌套属性
// Company company = new Company();
// company.setName("huawei");
// user.setCompany(compay)
// source.put("company", new Company()); // 需要与setIgnoreInvalidFields配合
source.put("company.name", "huawei");
PropertyValues propertyValues = new MutablePropertyValues(source);
// 1. 调整 IgnoreUnknownFields true(默认) -> false(抛出异常,age 字段不存在于 User 类)
// binder.setIgnoreUnknownFields(false);
// 2. 调整自动增加嵌套路径 true(默认) —> false
binder.setAutoGrowNestedPaths(false);
// 3. 调整 ignoreInvalidFields false(默认) -> true(默认情况调整不变化,需要调增 AutoGrowNestedPaths 为 false)
binder.setIgnoreInvalidFields(true);
// 白名单,必填字段,不会抛异常,但是会在绑定结果显示
binder.setRequiredFields("id", "name", "city");
binder.bind(propertyValues);
// 3. 输出 User 内容
System.out.println(user);
// 4. 获取绑定结果(结果包含错误文案 code,不会抛出异常)
BindingResult result = binder.getBindingResult();
System.out.println(result);
五、Spring 底层 Java Beans 替换实现
JavaBeans 核心实现 - java.beans.BeanInfo:
- 属性(Property):java.beans.PropertyEditor
- 方法(Method)
- 事件(Event)
- 表达式(Expression)
Spring 替代实现 - org.springframework.beans.BeanWrapper:
- 属性(Property):java.beans.PropertyEditor
- 嵌套属性路径(nested path)
1、BeanWrapper 的使用场景
Spring 底层 JavaBeans 基础设施的中心化接口
通常不会直接使用,间接用于 BeanFactory 和 DataBinder
提供标准 JavaBeans 分析和操作,能够单独或批量存储 Java Bean 的属性(properties)
支持嵌套属性路径(nested path)
实现类 org.springframework.beans.BeanWrapperImpl
2、标准 JavaBeans 是如何操作属性的
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 事件集合描述符
|
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.util.stream.Stream;
/**
* JavaBeans 示例
*/
public class JavaBeansDemo {
public static void main(String[] args) throws IntrospectionException {
// stopClass(Object.class) 排除(截止)类
BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
// 属性描述符 PropertyDescriptor
// 所有 Java 类均继承 java.lang.Object
// class 属性来自于 Object#getClass() 方法,所以属性描述只看get或set方法,不看属性是否存在
Stream.of(beanInfo.getPropertyDescriptors())
.forEach(propertyDescriptor -> {
// propertyDescriptor.getReadMethod(); // Getter 方法
// propertyDescriptor.getWriteMethod(); // Setter 方法
System.out.println(propertyDescriptor);
});
// 输出 User 定义的方法 MethodDescriptor
Stream.of(beanInfo.getMethodDescriptors()).forEach(System.out::println);
}
}
3、DataBinder 数据校验
DataBinder 与 BeanWrapper的关系:
bind 方法生成 BeanPropertyBindingResult,BeanPropertyBindingResult 关联 BeanWrapper。
继续来到我们DataBinder的bind方法:
// 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) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
// 核心方法
applyPropertyValues(mpvs);
}
// org.springframework.validation.DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
// 获取AbstractPropertyBindingResult的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());
}
}
}
getPropertyAccessor()方法我们继续看:
// org.springframework.validation.DataBinder#getPropertyAccessor
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
getInternalBindingResult()会获取一个AbstractPropertyBindingResult,getPropertyAccessor()会返回一个ConfigurablePropertyAccessor,实际上是new了一个BeanWrapperImpl。
我们继续回到setPropertyValues:
// org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)
@Override
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
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);
}
}
// If we encountered individual exceptions, throw the composite exception.
if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
throw new PropertyBatchUpdateException(paeArray);
}
}
下面就开始处理Bean的属性了。
// org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue(java.lang.String, java.lang.Object)
@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 tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}
六、SpringMVC数据绑定
更多关于SpringMVC数据绑定请移步(五、DataBinder数据绑定):
Spring MVC之注解的Controller使用大全
参考资料
极客时间-《小马哥讲 Spring 核心编程思想》
https://docs.spring.io/spring-framework/docs/5.2.22.RELEASE/spring-framework-reference/web.html#mvc-ann-initbinder