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