Spring数据绑定详解,Spring-Data Binding源码分析

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


文章目录

  • ​​一、概述​​
  • ​​二、Spring 数据绑定组件​​
  • ​​1、DataBinder 核心属性​​
  • ​​2、DataBinder 绑定方法​​
  • ​​三、Spring 数据绑定元数据​​
  • ​​1、DataBinder 元数据 - PropertyValues​​
  • ​​四、Spring 数据绑定控制参数​​
  • ​​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​


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring