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)
属性 | 说明 |
---|---|
target | 关联目标 Bean |
objectName | 目标 Bean名称 |
bindingResult | 属性绑定结果 |
typeConverter | 类型转换器 |
conversionService | 类型转换服务 |
messageCodesResolver | 校验错误文案 Code 处理器 |
validators | 关联的 Bean Validator 实例集合 |
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);
}
特征 | 说明 |
---|---|
数据来源 | 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包含许多属性转换和类型绑定的信息。
参数名称 | 说明 |
---|---|
ignoreUnknownFields | 是否忽略未知字段,默认值:true |
ignoreInvalidFields | 是否忽略非法字段,默认值:false |
autoGrowNestedPaths | 是否自动增加嵌套路径,默认值:true |
allowedFields | 绑定字段白名单 |
disallowedFields | 绑定字段黑名单 |
requiredFields | 必须绑定字段 |
// 创建空白对象
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);
JavaBeans 核心实现 - java.beans.BeanInfo:
Spring 替代实现 - org.springframework.beans.BeanWrapper:
Spring 底层 JavaBeans 基础设施的中心化接口
通常不会直接使用,间接用于 BeanFactory 和 DataBinder
提供标准 JavaBeans 分析和操作,能够单独或批量存储 Java Bean 的属性(properties)
支持嵌套属性路径(nested path)
实现类 org.springframework.beans.BeanWrapperImpl
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);
}
}
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数据绑定请移步(五、DataBinder数据绑定):
Spring MVC之注解的Controller使用大全
极客时间-《小马哥讲 Spring 核心编程思想》
https://docs.spring.io/spring-framework/docs/5.2.22.RELEASE/spring-framework-reference/web.html#mvc-ann-initbinder