DataBinder 是数据绑定器,它的作用是把一组属性值绑定到目标对象上,参数绑定的方式一般是通过 Java 反射。WebDataBinder 顾名思义,它是专门在 Web 环境使用的数据绑定器。
“数据绑定”对于框架而言是一项基础能力,很多地方都有数据绑定的需求。以 Spring MVC 为例,框架需要把 HttpServletRequest 参数绑定到 Java 对象上。
先来看一下最基础的数据绑定器如何使用,再来分析会比较有思路。
DataBinder 实现了 PropertyEditorRegistry 和 TypeConverter 接口。前者是属性编辑器的注册表,用于注册和查找属性编辑器 PropertyEditor,PropertyEditor 允许开发者自由设置属性值,例如把一个字符串的值设置为 LocalDate;后者是一个类型转换接口,它可以实现不同类型对象间的转换。在底层实现上,TypeConverter 其实是依赖 PropertyEditorRegistry 查找对应的 PropertyEditor 来实现类型转换的。
DataBinder 有两大职责:数据绑定、数据校验,对应的方法是bind()
和validate()
,数据校验先跳过,本文重点分析数据绑定。
下面是一个简单示例,利用 DataBinder 把三个属性绑定到了目标对象 user。DataBinder 还自动把"2005-01-01"字符串转换成了 LocalDate 类型,默认情况下这个转换不会发生,因为 DataBinder 并不知道如何把一段文本转换成日期,所以我们手动注册了一个 PropertyEditor。
public static void main(String[] args) throws Exception {
User user = new User();
DataBinder dataBinder = new DataBinder(user);
dataBinder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
setValue(LocalDate.parse(text, formatter));
}
});
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Lisa");
pvs.add("age", "18");
pvs.add("birthday", "2005-01-01");
dataBinder.bind(pvs);
}
@Data
public static class User {
String name;
Integer age;
LocalDate birthday;
}
我们已经知道,它实现了 TypeConverter 接口,所以自带数据类型转换的能力,下面是把字符串转换成日期对象:
LocalDate localDate = dataBinder.convertIfNecessary("2005-01-01", LocalDate.class);
这些就是 DataBinder 的基本能力。
如下示例,是一个最简单的接口方法。参数 user 是我们自定义的 POJO 类,且没有加任何注解。
@GetMapping("get/{id}")
public Object get(User user) {
return "success";
}
@Data
public class User {
Long id;
String name;
}
启动服务,访问以下链接:
http://127.0.0.1:8080/get/10?name=lisa
你会发现 user 对象的 id 和 name 都有对应的值,也就是说 Spring MVC 帮我们完成了 HttpServletRequest 参数到 Java 对象的属性绑定,而且数据源不止是 Request 参数,还包含 URL 的路径变量,这一切是如何做到的呢?
我们已经知道,DispatcherServlet 调用目标方法前,会通过 HandlerMethodArgumentResolver 组件来解析方法参数,对于上面的例子,解析器实现是 ServletModelAttributeMethodProcessor。它在解析参数时,首先会查找到参数类的构造器对象,再利用反射实例化参数对象,此时对象还是空的,紧接着就会调用ServletRequestDataBinder#bind
把 HttpServletRequest 参数绑定到目标方法的参数对象上。
Spring MVC 使用被增强的子类 ExtendedServletRequestDataBinder 来把 HttpServletRequest 参数绑定到 Controller 方法参数上。类图如下:
再来看bind()
流程:
doBind()
开始绑定public void bind(ServletRequest request) {
// 提取Request Parameter参数构建MutablePropertyValues
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
// 上传的文件也添加到mpvs
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
else if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE)) {
HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class);
if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles());
}
}
// 子类扩展 往mpvs添加自定义属性
// ExtendedServletRequestDataBinder会把Path变量填充进去
addBindValues(mpvs, request);
// 开始绑定
doBind(mpvs);
}
WebDataBinder#doBind
主要是对属性源的默认值和空值做了一些处理,最终把属性源 mpvs 交给父类,由父类统一处理数据绑定:
protected void doBind(MutablePropertyValues mpvs) {
/**
* 处理字段的默认值,字段名以"!"开头
* 例如:有!name=lisa,如果Request参数不含name,则自动填入name=lisa,有则忽略
*/
checkFieldDefaults(mpvs);
/**
* 处理字段的控制,字段名以"_"开头
* 例如:有_old,如果Request参数不含old,则自动填入old=false,有则忽略
* 处理控制的类包括:Boolean/Array/Collection/Map
*/
checkFieldMarkers(mpvs);
// 处理以“[]”结尾的属性名
adaptEmptyArrayIndices(mpvs);
// 调用父类方法绑定数据
super.doBind(mpvs);
}
DataBinder#doBind
移除了不被允许绑定的属性和必要字段的校验处理,最终调用applyPropertyValues()
实现目标对象属性的绑定:
protected void doBind(MutablePropertyValues mpvs) {
// 移除不被允许绑定的属性
checkAllowedFields(mpvs);
// 检查必须要的属性值
checkRequiredFields(mpvs);
// 属性值绑定到目标对象
applyPropertyValues(mpvs);
}
属性绑定的流程是:遍历属性源 mpvs,根据要绑定的目标属性类型通过PropertyEditorRegistrySupport#findCustomEditor
找到对应类型的属性编辑器,实现属性类型的转换,再通过反射调用目标 setter 方法给属性赋值,以完成数据绑定。
WebDataBinder 是用于 Web 环境的数据绑定器,它的职责是把 HttpServletRequest 参数绑定到 Controller 方法里没有被注解的自定义 POJO 类型的参数对象上。绑定的数据源有两个,一个是 HttpServletRequest 参数、一个是请求 Uri 的路径变量。除了绑定参数,它还会自动进行数据类型的转换,例如把字符串转换成日期类型,内置了一些基础的转换器,对于复杂类型的转换,你需要手动注册自定义的 PropertyEditor。