ReturnValueHandler 返回值处理器原理

一、是什么 —— 返回值处理器到底是什么?

1. 一句话定义

返回值处理器(HandlerMethodReturnValueHandler)负责将 Controller 方法返回的任意类型对象,按照特定规则转换,最终写入 HTTP 响应流

2. 用大白话理解

如果说参数解析器(ArgumentResolver)是负责"进门"的安检员,那么返回值处理器(ReturnValueHandler)就是负责"出门"的质检与物流

你的 Controller 可以返回五花八门的结果:

public String page() { return "index"; }       // 视图名
public Person getPerson() { return person; }   // Java 对象(需要转 JSON)
public ResponseEntity<Data> api() { ... }      // 带状态码的响应
public void doSomething() { }                  // 无返回值

但 HTTP 响应只认字节流 + 状态码。返回值处理器就是填补这个鸿沟的翻译官

3. 核心接口

public interface HandlerMethodReturnValueHandler {
    // 谁支持这个返回类型?
    boolean supportsReturnType(MethodParameter returnType);
    
    // 领走任务,开始处理
    void handleReturnValue(Object returnValue, MethodParameter returnType,
                           ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 
        throws Exception;
}

二、为什么 —— 为什么需要这套机制?

1. 多态的返回类型无法用 if-else 硬编码

如果不用策略模式,Spring 需要在核心代码里写:

if (返回值是 String) { 当成视图名处理 }
else if (返回值有 @ResponseBody) { 序列化为 JSON }
else if (返回值是 ResponseEntity) { 提取 body 和 status }
else if (返回值是 void) { 什么都不做 }
// ... 无穷无尽的 if-else

每增加一种返回类型就要改核心代码 → 严重违反开闭原则(OCP)

2. 策略模式解决

Spring 把每种返回类型的处理逻辑封装成独立的策略类,需要时遍历匹配即可。新增返回类型只需新增策略,无需修改核心代码。


三、怎么做 —— 四大组件协作流程

1. 四大核心组件

组件 角色 职责
RequestMappingHandlerAdapter 工厂厂长 启动时装配 15 个处理器
ServletInvocableHandlerMethod 生产线调度 反射调用 Controller,拿到返回值
HandlerMethodReturnValueHandlerComposite 中转站 遍历处理器,找到匹配的
RequestResponseBodyMethodProcessor 专员 处理 @ResponseBody,配合 MessageConverter

2. 第一阶段:初始化(项目启动时)

// RequestMappingHandlerAdapter.afterPropertiesSet()
if (this.returnValueHandlers == null) {
    List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite()
                                   .addHandlers(handlers);
}

默认注册的 15 个返回值处理器:

处理器 处理什么
ModelAndViewMethodReturnValueHandler ModelAndView 返回类型
ModelMethodProcessor Model 返回类型
ViewMethodReturnValueHandler View 返回类型
HttpEntityMethodProcessor HttpEntity / ResponseEntity
RequestResponseBodyMethodProcessor @ResponseBody 注解
ViewNameMethodReturnValueHandler 返回 String → 视图名
MapMethodProcessor 返回 Map
…… ……

同时,给关键处理器注入 MessageConverter 列表

// getDefaultReturnValueHandlers() 内部
handlers.add(new RequestResponseBodyMethodProcessor(
    getMessageConverters(),  // ← 含 MappingJackson2HttpMessageConverter 等
    this.contentNegotiationManager,
    this.requestResponseBodyAdvice
));

3. 第二阶段:请求到达 → 策略匹配

// ServletInvocableHandlerMethod.invokeAndHandle()
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // ① 反射执行
this.returnValueHandlers.handleReturnValue(returnValue, ...);                    // ② 处理返回值

handleReturnValue() 内部:

// HandlerMethodReturnValueHandlerComposite
public void handleReturnValue(...) {
    // ★ 找到匹配的处理器
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type...");
    }
    // ★ 交给它处理
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

匹配逻辑

private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        // ★ 谁支持这个返回类型,谁就领走这个任务
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

4. 第三阶段:@ResponseBody 的专员处理

当我们方法上有 @ResponseBody@RestController 时,命中的是 RequestResponseBodyMethodProcessor

重要澄清(来自闲聊):“我发现进行了两次的消息转换,一个是返回值处理器的时候,另一次是内容协商后的响应消息写入”——这其实是一个错觉!

返回值处理 + 内容协商 + 消息转换,是融为一体的同一个动作。 handleReturnValue() 内部立刻开始了内容协商,紧接着调用 HttpMessageConverter.write() 完成序列化和写出。并不是两次独立的转换。

handleReturnValue() 的关键动作:
// RequestResponseBodyMethodProcessor.handleReturnValue()
@Override
public void handleReturnValue(Object returnValue, ...) {
    // ★ 1. 打上完成标记:"我已经把响应写出去了,后面不用找视图了"
    mavContainer.setRequestHandled(true);
    
    // 2. 创建消息载体
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    
    // ★ 3. 核心!内容协商 + 消息转换 + 写出,一步完成
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

writeWithMessageConverters() 内部是内容协商的全流程(下章详细分析),最终:

for (HttpMessageConverter<?> converter : this.messageConverters) {
    if (converter.canWrite(valueType, selectedMediaType)) {
        // ★ 一次调用完成:Java 对象 → JSON 字节流 → OutputStream
        converter.write(body, selectedMediaType, outputMessage);
        return;
    }
}

5. 对比:非 @ResponseBody 的处理器

如果返回值是 String 且没有 @ResponseBody

// ViewNameMethodReturnValueHandler
@Override
public void handleReturnValue(Object returnValue, ...) {
    String viewName = (String) returnValue;
    // ★ 不写响应!只是把视图名设置到 ModelAndViewContainer
    mavContainer.setViewName(viewName);
    // 后续由 DispatcherServlet 的 processDispatchResult() 进行视图解析和渲染
}

注意mavContainer.setRequestHandled(true) 不会被调用,所以后续会走视图解析流程。


四、HttpMessageConverter 的初始化与智能探测

1. RequestMappingHandlerAdapter 的构造

public RequestMappingHandlerAdapter() {
    this.messageConverters = new ArrayList<>(4);
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());  // ★
}

2. AllEncompassingFormHttpMessageConverter 的智能探测

static {
    jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
                   && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
    gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
    // ...
}

public AllEncompassingFormHttpMessageConverter() {
    if (jackson2Present) {
        addPartConverter(new MappingJackson2HttpMessageConverter());  // ← 自动添加 JSON 支持!
    }
    if (gsonPresent) {
        addPartConverter(new GsonHttpMessageConverter());
    }
    // ...
}

这就解释了为什么引入 Jackson 依赖后,系统就"自动会返回 JSON"——不是因为什么魔法,而是在这个静态代码块中做了类路径探测,然后条件性地注册了对应的 Converter。


五、总结 —— 返回值处理全景图

Controller 方法执行完毕 → 返回一个 Object
    ↓
ServletInvocableHandlerMethod.invokeAndHandle()
    ↓
returnValueHandlers.handleReturnValue(returnValue, ...)
    ↓
selectHandler() → 遍历 15 个处理器 → supportsReturnType()
    ├── 是 @ResponseBody? → RequestResponseBodyMethodProcessor
    │       ├── mavContainer.setRequestHandled(true)  ← 标记"已处理"
    │       └── writeWithMessageConverters()
    │               ├── 内容协商(选 MediaType)        ← 下一章详解
    │               └── HttpMessageConverter.write()   ← 序列化 + 写出
    │
    ├── 是 String? → ViewNameMethodReturnValueHandler
    │       └── mavContainer.setViewName("index")     ← 只设视图名
    │
    ├── 是 ModelAndView? → ModelAndViewMethodReturnValueHandler
    │       └── 合并 Model + 设置 View
    │
    └── 是 ResponseEntity? → HttpEntityMethodProcessor
            └── 设置状态码 + 头信息 + 处理 Body
    ↓
回到 DispatcherServlet
    ├── 如果 requestHandled = true → 直接结束(响应已写入)
    └── 如果 requestHandled = false → processDispatchResult() 视图渲染

一句话总结

返回值处理器就是 Spring MVC 的"策略分发中心"——通过遍历匹配找到合适的处理器,由它决定是把对象转成 JSON 写回、还是跳转页面、还是什么都不做。对于 @ResponseBody返回值处理 + 内容协商 + 消息转换是融为一体的同一个动作,不存在"两次转换"。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐