参数绑定体系全景
一、是什么 ------ 参数绑定体系的全貌
前面我们分别讲了参数解析、Model/Map、自定义 POJO 绑定,现在把它们串成一张完整的体系图。
Spring MVC 的参数绑定体系 = 参数解析器(ArgumentResolver) + 数据绑定器(WebDataBinder) + 类型转换器(Converter) + 数据校验器(Validator) + 消息转换器(HttpMessageConverter)。
这五个组件协同工作,根据参数的类型和注解,自动选择最合适的处理路径。
二、四种参数绑定方式全景对比
1. 方式一览
java
// 源码位置:springboot2-master/boot-05-web-01/.../controller/ParameterTestController.java
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(
// 方式一:路径变量绑定
@PathVariable("id") Integer id, // URL: /car/2/owner/zhangsan → id=2
@PathVariable("username") String name, // → name="zhangsan"
// 方式二:查询参数绑定
@RequestParam("age") Integer age, // ?age=28 → age=28
@RequestParam("inters") List<String> inters, // ?inters=1&inters=2 → [1,2]
// 方式三:请求头 / Cookie 绑定
@RequestHeader("User-Agent") String ua, // 从请求头提取
@CookieValue("_ga") Cookie cookie, // 从 Cookie 提取
// 方式四:POJO 对象绑定(无注解)
Person person // POST 表单 → Person 对象
) { ... }
2. 四种方式对比表
| 绑定方式 | 注解 | 数据来源 | 解析器 | 类型转换/数据绑定 |
|---|---|---|---|---|
| 路径变量 | @PathVariable |
URI 模板 {id} |
PathVariableMethodArgumentResolver |
WebDataBinder + ConversionService |
| 查询参数 | @RequestParam |
?key=value |
RequestParamMethodArgumentResolver |
WebDataBinder + ConversionService |
| 请求头/Cookie | @RequestHeader / @CookieValue |
Header / Cookie | 对应解析器 | WebDataBinder + ConversionService |
| POJO 对象 | 无 / @ModelAttribute |
表单参数 | ServletModelAttributeMethodProcessor |
WebDataBinder + ConversionService |
| JSON 请求体 | @RequestBody |
HTTP Body | RequestResponseBodyMethodProcessor |
HttpMessageConverter(Jackson) |
3. 两条截然不同的处理路径
参数有 @RequestBody?
├── 是 → RequestResponseBodyMethodProcessor
│ └── HttpMessageConverter.read()(Jackson 反序列化整个 JSON → Java 对象)
│ └── 然后可选地:WebDataBinder.validate()(@Valid 校验)
│
└── 否 → 各种命名参数解析器 / POJO 解析器
└── WebDataBinder + ConversionService(逐字段 String → 目标类型转换)
三、@RequestBody 场景下 WebDataBinder 的特殊角色
1. 一个常见误区
很多人以为 @RequestBody 完全不需要 WebDataBinder。实际上:
WebDataBinder在@RequestBody场景下的数据绑定功能确实不参与(因为 Jackson 已经完成了 JSON → Java 对象的转换),但它的**数据校验(@Valid)**功能仍然会被触发。
2. 执行顺序
@RequestBody Person person
↓
① HttpMessageConverter(Jackson)read() → 将 JSON 字节流反序列化为 Person 对象
↓
② Spring 为这个已填充的 Person 对象创建 WebDataBinder
↓
③ 如果方法参数上有 @Valid 注解 → WebDataBinder.validate(person) 触发 Hibernate Validator
↓
④ 校验结果放入 BindingResult
↓
⑤ 如果校验失败 → 抛出 MethodArgumentNotValidException
3. 非 @RequestBody 的场景
saveuser(Person person) 或 @RequestParam Date date
↓
① 解析器从 Request 中提取原始 String 值
↓
② WebDataBinder 创建(目标对象可能是空的,也可能是预填充的)
↓
③ WebDataBinder.bind() → 将 String K-V 绑定到对象属性
↓
④ 绑定过程中逐属性调用 ConversionService.convert() 进行类型转换
↓
⑤ 如果参数上有 @Valid → 触发校验
↓
⑥ 返回填充并校验完成的 Person 对象
四、WebDataBinder 的完整能力矩阵
从闲聊中我们深入讨论过,WebDataBinder 是一个"包工头",它自己不干脏活累活,而是集成了多个专业工具:
| WebDataBinder 的能力 | 依赖的组件 | 生效场景 |
|---|---|---|
| 数据绑定 | BeanWrapperImpl(PropertyAccessor) |
K-V 参数 → POJO 属性 |
| 类型转换 | ConversionService(含 N 个 Converter) |
String → Integer/Date/Enum 等 |
| 数据校验 | Validator(Hibernate Validator) |
@Valid / @Validated 注解 |
| 错误收集 | BindingResult |
类型转换失败 / 校验失败时记录 |
| 嵌套对象自动创建 | autoGrowNestedPaths 配置 |
pet.name=Tom → 自动 new Pet() |
WebDataBinder 的生命周期
**每条请求、每个需要绑定的参数,都会创建一个全新的 WebDataBinder 实例。**它是原型(Prototype)级别的,有状态(持有 target 对象和 BindingResult)。但内部的 ConversionService、Validator 等核心工具是容器单例,所以创建成本很低。
五、数据校验机制全景
1. 两种校验触发方式
| 方式 | 注解 | 使用场景 | 异常类型 |
|---|---|---|---|
| 方法参数校验 | @Valid + 参数前 |
Controller 方法参数 | MethodArgumentNotValidException |
| 类级别校验 | @Validated + 类上 |
Controller 类 | ConstraintViolationException |
2. 校验执行时机
请求到达 → 参数解析
↓
对于 @RequestBody:Jackson 先反序列化 → WebDataBinder.validate() 校验
对于 @RequestParam / POJO:WebDataBinder 先绑定 → WebDataBinder.validate() 校验
↓
如果校验失败:
├── BindingResult 收集错误信息
├── 如果方法签名中有 BindingResult 参数 → 不抛异常,由开发者自行处理
└── 如果没有 BindingResult 参数 → 抛出异常(由全局异常处理器捕获)
3. BindingResult 的特殊规则
BindingResult 必须紧跟在被校验的参数后面:
java
@PostMapping("/save")
public String save(@Valid Person person, BindingResult result) { // ✅ 正确
if (result.hasErrors()) { ... }
}
@PostMapping("/save")
public String save(@Valid Person person, String other, BindingResult result) { // ❌ 错误
// BindingResult 没紧跟在 person 后面,Spring 无法关联
}
六、参数绑定异常处理
异常类型一览
| 异常 | 场景 |
|---|---|
MissingServletRequestParameterException |
@RequestParam(required=true) 参数缺失 |
MethodArgumentTypeMismatchException |
类型转换失败(?age=abc → Integer 转换失败) |
HttpMessageNotReadableException |
@RequestBody 的 JSON 格式错误 |
MethodArgumentNotValidException |
@Valid 校验失败 |
BindException |
表单绑定失败 |
统一异常处理示例
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map handleValidException(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(
error -> errors.put(error.getField(), error.getDefaultMessage())
);
return Map.of("code", 400, "errors", errors);
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public Map handleTypeMismatch(MethodArgumentTypeMismatchException e) {
return Map.of("code", 400, "msg", "参数 " + e.getName() + " 类型错误");
}
}
七、总结 ------ 参数绑定体系全景图
HTTP 请求到达
│
┌───────────────┼───────────────┐
│ │ │
有 @RequestBody? 有 @RequestParam? 无注解 POJO?
│ │ │
▼ ▼ ▼
RequestResponseBody RequestParam ServletModelAttribute
MethodProcessor MethodArgument MethodProcessor
(读 JSON 流) Resolver (表单绑定)
│ │
│ └───────┬───────┘
│ │
│ ▼
│ WebDataBinder
│ ├── bind():K-V → 对象属性
│ ├── ConversionService:String → 目标类型
│ ├── validate():@Valid 校验
│ └── BindingResult:收集错误
│
▼
HttpMessageConverter.read()
(Jackson 反序列化)
│
▼
WebDataBinder.validate()
(@Valid 校验,仅此功能)
│
└───────────────┬───────────────┘
│
▼
参数数组组装完成
│
▼
reflection invoke()
Controller 方法执行
核心思想:
参数绑定不是单一组件完成的,而是一套分层的、可插拔的协作体系。理解这个体系的关键是分清两条路径:"流处理路径" (@RequestBody → HttpMessageConverter)和 "KV 参数路径"(@RequestParam/POJO → WebDataBinder + ConversionService)。