Spring MVC 是如何进行数据绑定的?
Spring MVC 的数据绑定(Data Binding)是将 HTTP 请求中的参数(通常是字符串形式,如 URL 参数、Form 表单数据)自动转换并赋值给 Java 方法参数(如 POJO 对象、基本数据类型)的过程。
这个过程的核心组件是 WebDataBinder。
以下是 Spring MVC 数据绑定的详细工作流程和核心机制:
1. 核心流程概览
当一个 HTTP 请求到达 DispatcherServlet 后,流程如下:
- HandlerAdapter 调用:
DispatcherServlet将请求委托给RequestMappingHandlerAdapter来执行具体的 Controller 方法。 - 参数解析 (Argument Resolution): Adapter 需要确定如何填充 Controller 方法的参数。它会遍历
HandlerMethodArgumentResolver列表,找到支持该参数类型的解析器(例如ServletModelAttributeMethodProcessor用于处理 POJO 对象)。 - 创建 Binder: 如果参数需要绑定(例如是一个 JavaBean),解析器会创建一个
WebDataBinder实例。 - 数据注入:
WebDataBinder利用PropertyAccessor(通常是BeanWrapper)将请求中的参数值注入到目标对象中。 - 类型转换: 由于 HTTP 参数都是 String,而 Java 字段可能是 Integer、Date 等,Binder 会调用
ConversionService(或旧式的PropertyEditor) 进行类型转换。 - 校验 (Validation): 如果参数上标注了
@Valid或@Validated,Binder 会执行校验逻辑。 - 返回结果: 绑定完成的对象被传给 Controller 方法。
2. 关键组件详解
A. WebDataBinder (核心)
它是 DataBinder 的子类,专门用于 Web 环境。它的作用就像一个“指挥官”:
- 它持有目标对象(Target Object,即你要绑定的 POJO)。
- 它持有数据源(Request Parameters)。
- 它负责调用转换器和校验器。
B. HandlerMethodArgumentResolver (参数解析器)
Spring MVC 使用策略模式来解析不同类型的参数。
- 简单类型: 如果参数是
int,String等,通常由RequestParamMethodArgumentResolver处理。 - 对象类型: 如果参数是自定义 POJO(如
User user),通常由ServletModelAttributeMethodProcessor处理。正是这个处理器触发了WebDataBinder的创建和绑定过程。
C. ConversionService & PropertyEditor (类型转换)
这是将 "String" 变为 "Object" 的底层机制。
- PropertyEditor (旧机制): 基于 JDK 的
java.beans.PropertyEditor。它是非线程安全的,通常为每个请求创建一个。Spring 内置了许多 Editor(如CustomDateEditor)。 - Converter / Formatter (新机制): Spring 3.0 引入。
Converter<S, T>: 将类型 S 转换为类型 T(通用)。Formatter<T>: 专门处理 String 和 Object 之间的转换,支持国际化(如日期、货币格式)。- ConversionService: 统一管理这些 Converter 和 Formatter。
WebDataBinder会委托它进行类型转换。
D. BeanWrapper
WebDataBinder 并不直接操作对象属性,而是通过 BeanWrapper。BeanWrapper 提供了设置和获取 Bean 属性(getter/setter)的低级 API,它利用 Java 反射机制来完成实际的赋值操作。
3. 绑定步骤细节 (以表单提交到 POJO 为例)
假设有一个 User 类和一个请求 POST /save?name=zhangsan&age=20。
- 匹配字段:
WebDataBinder获取请求参数名(name,age)。 - 查找属性: 它在
User对象中查找对应的属性(通常通过 setter 方法,如setName,setAge)。 - 类型转换:
name(String) ->user.name(String): 直接赋值。age(String "20") ->user.age(Integer):WebDataBinder发现类型不匹配,调用ConversionService将字符串 "20" 转换为整数 20。
- 赋值: 通过反射调用
user.setAge(20)。 - 错误处理: 如果转换失败(例如
age=abc),错误信息会被放入BindingResult对象中,而不是直接抛出异常(除非没有定义BindingResult参数)。
4. 特殊情况:@RequestBody (JSON/XML)
需要区分的是,@RequestBody 的处理机制与上述普通的表单绑定不同。
- 普通绑定 (Form/URL): 使用
WebDataBinder,基于字段名匹配,处理application/x-www-form-urlencoded数据。 @RequestBody(JSON/XML): 使用HttpMessageConverter(如 Jackson, Gson)。- 它不使用
WebDataBinder进行逐个字段的 setter 调用。 - 它直接读取 HTTP Request Body 的输入流,利用序列化库(如 Jackson 的
ObjectMapper)将整个 JSON 字符串反序列化为 Java 对象。
- 它不使用
5. 总结
Spring MVC 的数据绑定可以概括为:
Request 参数 -> HandlerMethodArgumentResolver (识别参数) -> WebDataBinder (构建绑定上下文) -> ConversionService (类型转换 String to Object) -> BeanWrapper (反射赋值) -> Target Object (最终对象)。