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

相关推荐
葫芦和十三3 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp3 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑4 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯4 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan7 小时前
多Agent之间的区别
后端
青石路8 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充9 小时前
1.面向对象设计思想
后端
IT_陈寒9 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro10 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗10 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端