文章目录
夯实Spring系列|第十九章:Spring 数据绑定(Data Binding)
1.项目环境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模块:data-binding
2.Spring 数据绑定使用场景
Spring BeanDefinition 到 Bean 实例创建
Spring 数据绑定(DataBinder)
Spring Web 参数绑定(WebDataBinder)
3.Spring 数据绑定组件
标准组件
- org.springframework.validation.DataBinder
Web 组件
- org.springframework.web.bind.ServletRequestDataBinder
- org.springframework.web.bind.WebDataBinder
- org.springframework.web.bind.support.WebExchangeDataBinder - @since 5.0
- org.springframework.web.bind.support.WebRequestDataBinder
DataBinder 核心属性
属性 | 说明 |
---|---|
target | 关联目标 Bean |
objectName | 目标 Bean 名称 |
bindingResult | 属性绑定结果 |
typeConverter | 类型转换器 |
conversionService | 类型转换服务 |
messageCodesResolver | 效验错误文案 Code 处理器 |
validators | 关联的 Bean Validator 实例集合 |
DataBinder 绑定方法
- bind(PropertyValues):将 PropertyValue Key-Value 内容映射到关联的 Bean(target)中的属性上
- 假设 PropertyValues 中包含
name = 小马哥
的键值对,同时 User 对象中存在 name 属性,当 bind 方法执行时,User 对象中的 name 属性值将被绑定为小马哥
。
- 假设 PropertyValues 中包含
4.Spring 数据绑定元数据
DataBinder 元数据 - PropertyValues
特征 | 说明 |
---|---|
数据来源 | BeanDefinition,主要来源 XML 资源配置 BeanDefinition |
数据结构 | 由一个或者多个 PropertyValue 组成 |
成员结构 | PropertyValue 包含属性名称,以及属性值(包括原始值、类型转换后的值) |
常见实现 | MutablePropertyValues |
Web 扩展实现 | ServletConfigPropertyValues、ServletRequestParameterPropertyValues |
相关生命周期 | InstantiationAwareBeanPostProcessor#postProcessProperties |
4.1 PropertyValues 来源
通常来源于 XML 资源配置 BeanDefinition,因为 @Bean 或者其他编程方式,属性值可以直接使用,并不需要使用 PropertyValues 来进行转换。
通过 org.springframework.beans.factory.config.BeanDefinition#getPropertyValues 来获取。
5.Spring 数据绑定控制参数
5.1 DataBinder 绑定特殊场景分析
- 当 PropertyValues 中包含名称 x 的 PropertyValue,目标对象 B 不存在 x 属性,当 bind 方法执行时,会发生什么?
- 当 PropertyValues 中包含名称 x 的 PropertyValue,目标对象 B 存在 x 属性,当 bind 方法执行时,如何避免 B 属性 x 不被绑定?
- 当 PropertyValues 中包含名称 x.y 的 PropertyValue,目标对象 B 不存在 x 属性(嵌套 y 属性),当 bind 方法执行时,会发生什么?
示例
/**
* {@link DataBinder} 示例
*
* @see DataBinder
*/
public class DataBinderDemo {
public static void main(String[] args) {
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("id", "1")
.add("name", "xwf")
// 添加一个不存在的属性值
// DataBinder 忽略未知属性
.add("otherName", "xwf")
// 嵌套属性
.add("company.name", "阿里");
dataBinder.bind(mpvs);
System.out.println(user);
}
}
新增 Company 类
public class Company {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
'}';
}
}
将属性增加到 User 类中,并新增 setter/getter 方法,重写 toString 方法。
执行结果:
User{id=1, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=Company{name='阿里'}}
由结果可以得到两个结论
1.添加一个不存在的属性值,DataBinder 忽略未知属性
2.DataBinder 支持嵌套属性
5.2 DataBinder 绑定控制参数
参数名称 | 说明 |
---|---|
ignoreUnknownFields | 是否忽略未知字段,默认值:true |
ignoreInvalidFields | 是否忽略非法字段,默认值:false |
autoGrowNestedPaths | 是否自动增加嵌套路径,默认值:true |
allowedFields | 绑定字段白名单 |
disallowedFields | 绑定字段黑名单 |
requiredFields | 必须绑定字段 |
5.2.1 ignoreUnknownFields
public class DataBinderDemo {
public static void main(String[] args) {
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("id", "1")
.add("name", "xwf")
// 添加一个不存在的属性值
// DataBinder 忽略未知属性
.add("otherName", "xwf")
// 嵌套属性
.add("company.name", "阿里");
// 1.ignoreUnknownFields true(默认) -> false
dataBinder.setIgnoreUnknownFields(false);
dataBinder.bind(mpvs);
// 输出
System.out.println(user);
}
}
执行结果:
Exception in thread "main" org.springframework.beans.NotWritablePropertyException: Invalid property 'otherName' of bean class [com.huajie.thinking.in.spring.ioc.overview.domain.User]: Bean property 'otherName' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
结论:如果设置 ignoreUnknownFields 为false,绑定的字段不存在,抛出异常 NotWritablePropertyException
5.2.2 autoGrowNestedPaths
public class DataBinderDemo {
public static void main(String[] args) {
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("id", "1")
.add("name", "xwf")
// 添加一个不存在的属性值
// DataBinder 忽略未知属性
.add("otherName", "xwf")
// 嵌套属性
.add("company.name", "阿里");
// 1.ignoreUnknownFields true(默认) -> false
// dataBinder.setIgnoreUnknownFields(false);
// 2.autoGrowNestedPaths true(默认) -> false
dataBinder.setAutoGrowNestedPaths(false);
dataBinder.bind(mpvs);
// 输出
System.out.println(user);
}
}
执行结果:
Exception in thread "main" org.springframework.beans.NullValueInNestedPathException: Invalid property 'company' of bean class [com.huajie.thinking.in.spring.ioc.overview.domain.User]: Value of nested property 'company' is null
设置 autoGrowNestedPaths 为 false 不支持嵌套属性。
5.2.3 ignoreInvalidFields
ignoreInvalidFields 属性设置为 true,可以忽略 5.2.2 中的错误,但是相应出错的属性也不会设置
public class DataBinderDemo {
public static void main(String[] args) {
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("id", "1")
.add("name", "xwf")
// 添加一个不存在的属性值
// DataBinder 忽略未知属性
.add("otherName", "xwf")
// 嵌套属性
.add("company.name", "阿里");
// 1.ignoreUnknownFields true(默认) -> false
// dataBinder.setIgnoreUnknownFields(false);
// 2.autoGrowNestedPaths true(默认) -> false
dataBinder.setAutoGrowNestedPaths(false);
// 3.ignoreInvalidFields false(默认) -> true
dataBinder.setIgnoreInvalidFields(true);
dataBinder.bind(mpvs);
// 输出
System.out.println(user);
}
}
执行结果:
User{id=1, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}
5.2.4 requiredFields
- 错误信息需要通过
dataBinder.getBindingResult();
获取,不会报错
public class DataBinderDemo {
public static void main(String[] args) {
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("id", "1")
.add("name", "xwf")
// 添加一个不存在的属性值
// DataBinder 忽略未知属性
.add("otherName", "xwf")
// 嵌套属性
.add("company.name", "阿里");
// 1.ignoreUnknownFields true(默认) -> false
// dataBinder.setIgnoreUnknownFields(false);
// 2.autoGrowNestedPaths true(默认) -> false
dataBinder.setAutoGrowNestedPaths(false);
// 3.ignoreInvalidFields false(默认) -> true
dataBinder.setIgnoreInvalidFields(true);
// 4.requiredFields 设置不能为空的字段
dataBinder.setRequiredFields("id","name","age");
dataBinder.bind(mpvs);
// 输出
System.out.println(user);
// 绑定结果(结果包含了错误文案信息,但是不会报错)
BindingResult bindingResult = dataBinder.getBindingResult();
System.out.println(bindingResult);
}
}
执行结果:
User{id=1, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'user' on field 'age': rejected value []; codes [required.user.age,required.age,required.java.lang.Integer,required]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age]]; default message [Field 'age' is required]
5.2.5 allowedFields
相关设置
dataBinder.setAllowedFields("id");//表示只允许 id 字段进行绑定
执行结果:
User{id=1, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}
5.2.6 disallowedFields
相关设置
dataBinder.setDisallowedFields("id");//表示不允许 id 字段进行绑定
执行结果:
User{id=null, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}
6.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)
7.BeanWrapper 的使用场景
BeanWrapper
- Spring 底层 JavaBeans 基础设施的中心化接口
- 通常不会直接使用,间接用于 BeanFactory 和 DataBinder
- 提供标准 JavaBeans 分析和操作,能够单独或者批量存储 Java Bean 的属性(Properties)
- 支持嵌套属性路径(nested path)
- 实现类 org.springframework.beans.BeanWrapperImpl
BeanWrapper 接口源码中相关的方法
-
setAutoGrowCollectionLimit
-
org.springframework.beans.AbstractNestablePropertyAccessor 中
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
默认实现中这个值是无限制的,表示支持无线的嵌套路径
-
-
getWrappedInstance 获取 Bean 的实例,在 IoC 场景下的 BeanWrapper 是和 Bean 关联的
-
getWrappedClass 获取 Bean 的 Class
-
getPropertyDescriptors 获取所有属性的描述信息
-
getPropertyDescriptor 获取单个属性的描述信息
8.JavaBeans
标准 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 事件集合描述符 |
简单示例
/**
* JavaBeans 示例
*/
public class JavaBeansDemo {
public static void main(String[] args) throws IntrospectionException {
// stopClass 排除类
BeanInfo beanInfo = Introspector.getBeanInfo(User.class,Object.class);
Stream.of(beanInfo.getPropertyDescriptors()).forEach(propertyDescriptor -> {
System.out.println(propertyDescriptor);
});
Stream.of(beanInfo.getMethodDescriptors()).forEach(System.out::println);
}
}
9.DataBinder 与 BeanWrapper 关系
- bind 方法生成 BeanPropertyBindingResult
- BeanPropertyBindingResult 关联 BeanWrappper
源码调用链路
org.springframework.validation.DataBinder#bind
- org.springframework.validation.DataBinder#doBind
- org.springframework.validation.DataBinder#applyPropertyValues
- org.springframework.validation.DataBinder#getPropertyAccessor
- org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
- org.springframework.validation.BeanPropertyBindingResult#createBeanWrapper
- org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
- org.springframework.validation.DataBinder#getPropertyAccessor
- org.springframework.validation.DataBinder#applyPropertyValues
由上面调用链路可以知道当调用 DataBinder#bind 方法时,默认会创建 BeanWrapper 对象,此对象和 BeanPropertyBindingResult 进行关联。
10.面试
10.1 Spring 数据绑定 API 是什么?
-
org.springframework.validation.DataBinder
-
org.springframework.validation.BeanPropertyBindingResult
10.2 BeanWrapper 与 JavaBeans 之间关系是?
- Spring 底层 JavaBeans 基础设施的中心化接口
- BeanWrapper 有且仅有一个实现类 BeanWrapperImpl,BeanWrapperImpl 底层的一些实现是基于 JavaBeans 进行的二次封装
11.参考
- 极客时间-小马哥《小马哥讲Spring核心编程思想》