一文详解Spring MVC中方法参数与返回值的处理机制
作者:不才不才不不才
想知道Spring MVC如何自动将URL参数、请求体JSON填进方法参数,以及@ResponseBody为何不返回页面,本文深入解析HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两大体系,需要的朋友可以参考下
一、引子:为什么一个注解就能拿到参数
你写一个方法:
@PostMapping("/user/{id}")
public UserDTO getUser(@PathVariable Long id, @RequestBody UserForm form) { ... }
Spring 怎么知道把 URL 里的 {id} 填进 id、把请求体 JSON 反序列化成 UserForm?答案就是两个体系:
HandlerMethodArgumentResolver:决定「方法参数从哪来、怎么转」HandlerMethodReturnValueHandler:决定「返回值往哪去、怎么写」
它们都是策略模式:一组候选解析器,逐个问「你支持这个参数吗?」支持就由它处理。
二、参数解析器体系
2.1 调用入口
// InvocableHandlerMethod.java
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// ① 解析所有参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// ② 反射调用
return doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
// ③ 遍历所有 ArgumentResolver,找到支持该参数的那一个
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException("No suitable resolver for " + parameter);
}
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.binderFactory);
}
return args;
}
2.2 常见注解对应的解析器
| 注解 | 解析器 | 数据来源 |
|---|---|---|
@RequestParam | RequestParamMethodArgumentResolver | 查询参数 / 表单字段 |
@PathVariable | PathVariableMethodArgumentResolver | URI 模板变量 |
@RequestBody | RequestResponseBodyMethodProcesser | 请求体(经 HttpMessageConverter 反序列化) |
@RequestHeader | RequestHeaderMethodArgumentResolver | 请求头 |
@CookieValue | ServletCookieValueMethodArgumentResolver | Cookie |
@ModelAttribute | ModelAttributeMethodProcessor | 表单绑定到对象 |
HttpServletRequest 等 | ServletRequestMethodArgumentResolver | 容器原生对象 |
重点:@RequestBody 和 @ResponseBody 都靠 HttpMessageConverter(JSON 用 MappingJackson2HttpMessageConverter)做序列化/反序列化。这也是为什么引入 spring-boot-starter-web 默认就能收发包 JSON。
2.3 @RequestBody 反序列化核心
// AbstractMessageConverterMethodArgumentResolver
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) {
MediaType contentType = inputMessage.getHeaders().getContentType();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canRead(targetType, contentType)) { // 能读这个类型+媒体类型
return converter.read(targetClass, inputMessage); // Jackson 反序列化
}
}
throw new HttpMediaTypeNotSupportedException("不支持的媒体类型");
}
三、返回值处理器体系
3.1 常见返回值处理
| 返回值类型 | 处理器 | 行为 |
|---|---|---|
@ResponseBody / ResponseEntity | RequestResponseBodyMethodProcessor | 经 HttpMessageConverter 写入响应体(不跳视图) |
String(逻辑视图名) | ViewNameMethodReturnValueHandler | 当作视图名,交给 ViewResolver |
ModelAndView | ModelAndViewMethodReturnValueHandler | 设置视图 + 模型 |
void + 已直接写出 | StreamingResponseBody 等 | 流式写出 |
3.2 @ResponseBody 如何跳过视图
// RequestResponseBodyMethodProcessor(同时实现 ArgumentResolver 和 ReturnValueHandler)
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException {
mavContainer.setRequestHandled(true); // ★ 标记请求已处理,跳过视图渲染
// 经 HttpMessageConverter 把 returnValue 序列化写入 response
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
结论:@ResponseBody 的本质就是 setRequestHandled(true) + 用 HttpMessageConverter 写响应体,从而绕过第 15 篇说的 ViewResolver。这就是为什么 REST 接口不返回页面。
四、参数解析与返回值处理流程图

五、常见误区
| 误区 | 正解 |
|---|---|
@RequestParam 能收请求体 JSON | 不能,它读查询/表单;JSON body 用 @RequestBody |
@ResponseBody 也走 ViewResolver | 不,setRequestHandled(true) 跳过视图 |
| 一个参数只能被一个解析器处理 | 是「第一个 supports 的」生效,顺序由注册顺序决定 |
没有 HttpMessageConverter 也能收 JSON | 不能,需引入 Jackson(spring-webmvc 默认带) |
| 返回值处理与参数解析是同一套 | 不是,是两个独立体系(ArgumentResolver / ReturnValueHandler) |
面试题自测
- 方法参数的值是怎么被解析填进来的?核心入口方法?
@RequestBody和@ResponseBody共用了什么机制?HttpMessageConverter在什么时机被调用?- 为什么
@ResponseBody不返回页面? @PathVariable和@RequestParam的解析器有何不同?- 如果参数没有任何解析器支持,会怎样?
Debug 小技巧
在 InvocableHandlerMethod.getMethodArgumentValues 断点,观察 this.resolvers 列表里有多少个候选解析器;单步进 resolveArgument,看它如何为 @RequestBody 走到 Jackson 反序列化。返回值侧在 RequestResponseBodyMethodProcessor.handleReturnValue 断点,确认 setRequestHandled(true)。
以上就是一文详解Spring MVC中方法参数与返回值的处理机制的详细内容,更多关于Spring MVC方法参数与返回值的资料请关注脚本之家其它相关文章!
