Spring Boot 从“会用”到“精通”:ReturnValueHandler原理

ReturnValueHandler 返回值处理器原理

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

1. 一句话定义

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

2. 用大白话理解

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

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

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

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

3. 核心接口

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

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

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

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

java 复制代码
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. 第一阶段:初始化(项目启动时)

java 复制代码
// 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 列表

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

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

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

handleReturnValue() 内部:

java 复制代码
// 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);
}

匹配逻辑

java 复制代码
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() 的关键动作:
java 复制代码
// 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() 内部是内容协商的全流程(下章详细分析),最终:

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

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

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

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

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


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

1. RequestMappingHandlerAdapter 的构造

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

2. AllEncompassingFormHttpMessageConverter 的智能探测

java 复制代码
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返回值处理 + 内容协商 + 消息转换是融为一体的同一个动作,不存在"两次转换"。

相关推荐
snow@li2 小时前
数据库:MySQL vs PostgreSQL 详尽对比(2026版)
java·mysql·postgresql
丑过三八线2 小时前
Runc 深度解析:从原理到实操
java·linux·开发语言·docker·容器·rpc
伊布拉西莫2 小时前
Flask 请求生命周期
后端·python·flask
STDD2 小时前
ntfy 自托管推送通知服务搭建:一条 curl 命令向手机发送通知
java·开发语言·智能手机
英豪1632 小时前
@Target + @Retention + isAnnotationPresent + getAnnotation
后端
黄同学real2 小时前
HJL WebAPI 项目日志入库实战:从建表到自动清理
后端
孟陬2 小时前
国外技术周刊 #140:在 Jeff Bezos 的私密 Campfire 峰会上,我学到了关于亿万富翁的事
前端·后端