java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring MVC方法参数与返回值

一文详解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?答案就是两个体系:

它们都是策略模式:一组候选解析器,逐个问「你支持这个参数吗?」支持就由它处理。

二、参数解析器体系

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 常见注解对应的解析器

注解解析器数据来源
@RequestParamRequestParamMethodArgumentResolver查询参数 / 表单字段
@PathVariablePathVariableMethodArgumentResolverURI 模板变量
@RequestBodyRequestResponseBodyMethodProcesser请求体(经 HttpMessageConverter 反序列化)
@RequestHeaderRequestHeaderMethodArgumentResolver请求头
@CookieValueServletCookieValueMethodArgumentResolverCookie
@ModelAttributeModelAttributeMethodProcessor表单绑定到对象
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 / ResponseEntityRequestResponseBodyMethodProcessor经 HttpMessageConverter 写入响应体(不跳视图)
String(逻辑视图名)ViewNameMethodReturnValueHandler当作视图名,交给 ViewResolver
ModelAndViewModelAndViewMethodReturnValueHandler设置视图 + 模型
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)

面试题自测

  1. 方法参数的值是怎么被解析填进来的?核心入口方法?
  2. @RequestBody 和 @ResponseBody 共用了什么机制?
  3. HttpMessageConverter 在什么时机被调用?
  4. 为什么 @ResponseBody 不返回页面?
  5. @PathVariable 和 @RequestParam 的解析器有何不同?
  6. 如果参数没有任何解析器支持,会怎样?

Debug 小技巧

在 InvocableHandlerMethod.getMethodArgumentValues 断点,观察 this.resolvers 列表里有多少个候选解析器;单步进 resolveArgument,看它如何为 @RequestBody 走到 Jackson 反序列化。返回值侧在 RequestResponseBodyMethodProcessor.handleReturnValue 断点,确认 setRequestHandled(true)。

以上就是一文详解Spring MVC中方法参数与返回值的处理机制的详细内容,更多关于Spring MVC方法参数与返回值的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文