当前位置: 首页 > news >正文

深入 Spring MVC 返回值处理器

深入 Spring MVC 返回值处理器

在 Spring MVC 框架中,控制器方法的返回值处理是连接业务逻辑与响应生成的关键环节。无论是传统的视图渲染还是前后端分离的 JSON 响应,都依赖于一套灵活且高效的返回值处理机制。本文将从底层原理出发,结合完整的代码案例,深入剖析 Spring MVC 返回值处理器的工作流程、设计模式及实践应用。

代码地址

一、核心概念与设计模式

我们需要先理解 Spring MVC 返回值处理机制的核心组件设计模式,这是理解后续流程的基础。

1.1 核心组件

Spring MVC 通过以下组件协同完成返回值处理:

组件类名核心职责关键作用
HandlerMethodReturnValueHandler返回值处理器接口定义 “是否支持某返回值类型” 和 “如何处理该返回值” 的标准
HandlerMethodReturnValueHandlerComposite处理器组合器管理所有处理器,按优先级匹配并调用合适的处理器(链模式核心)
ModelAndViewContainer模型与视图容器存储处理过程中的模型数据、视图名及请求处理状态(状态载体)
HttpMessageConverterHTTP 消息转换器将 Java 对象序列化为 JSON/XML 等响应体(如MappingJackson2HttpMessageConverter
ViewResolver视图解析器将视图名转换为实际视图对象(如 FreeMarkerView、JspView)

1.2 设计模式

返回值处理机制深度依赖两种设计模式,这也是其灵活性的根源:

  • 策略模式:每个HandlerMethodReturnValueHandler实现类对应一种返回值处理策略(如RequestResponseBodyMethodProcessor处理@ResponseBodyModelAndViewMethodReturnValueHandler处理ModelAndView)。
  • 链模式HandlerMethodReturnValueHandlerComposite维护一个处理器列表,按优先级依次匹配,找到第一个支持当前返回值类型的处理器后执行(避免 if-else 判断,便于扩展)。

二、底层工作流程全解析

Spring MVC 的返回值处理流程嵌入在DispatcherServlet的请求分发逻辑中,核心对应doDispatch()方法的 “处理返回值” 阶段。我们通过 “初始化→方法执行→返回值处理→视图渲染” 四个阶段,拆解完整链路。

2.1 整体流程概览

下图展示了从请求到响应的全链路中,返回值处理的位置与核心步骤:

在这里插入图片描述

2.2 阶段 1:初始化(容器启动时)

在 Spring 容器启动阶段,会完成返回值处理器、视图解析器等组件的初始化,为后续请求处理做准备。对应代码中的static初始化块:

static {// 1. 初始化Spring上下文(加载FreeMarker配置、注册Bean)initApplicationContext();// 2. 注册返回值处理器(按优先级排序)initReturnValueHandlers();// 3. 创建控制器实例initTestController();
}
关键步骤:返回值处理器的优先级排序

HandlerMethodReturnValueHandlerComposite会按 “特殊类型优先、注解驱动其次、兜底最后” 的规则注册处理器,优先级决定了匹配顺序(先匹配到的处理器将被使用)。常见处理器的优先级如下:

  1. ModelAndViewMethodReturnValueHandler(处理ModelAndView
  2. ViewNameMethodReturnValueHandler(处理 String 视图名)
  3. ServletModelAttributeMethodProcessor(false)(处理带@ModelAttribute的非简单类型)
  4. HttpEntityMethodProcessor(处理HttpEntity/ResponseEntity
  5. HttpHeadersReturnValueHandler(处理HttpHeaders
  6. RequestResponseBodyMethodProcessor(处理@ResponseBody
  7. ServletModelAttributeMethodProcessor(true)(兜底:处理带@ModelAttribute的简单类型)

为什么@ResponseBody的优先级低于ModelAndView
因为ModelAndView是更原始的 MVC 返回类型,而@ResponseBody是前后端分离场景的扩展,Spring 需保证对传统 MVC 的兼容性。

2.3 阶段 2:控制器方法执行(请求处理时)

当请求到达后,HandlerAdapter会通过反射调用控制器方法,获取返回值。对应代码中的:

// 执行控制器方法,获取返回值
Object returnValue = targetMethod.invoke(testController);

例如,调用testResponseBodyAnnotatedReturn()方法会返回User对象:

@ResponseBody
public User testResponseBodyAnnotatedReturn() {return new User("钱七", 50); // 返回User对象
}

2.4 阶段 3:返回值处理(核心逻辑)

返回值处理是整个流程的核心,由HandlerMethodReturnValueHandlerComposite主导,分为 “匹配处理器”“执行处理逻辑” 两步。

3.4.1 步骤 1:匹配处理器

HandlerMethodReturnValueHandlerComposite遍历处理器列表,调用supportsReturnType()方法判断是否支持当前返回值类型:

// 遍历处理器,找到第一个支持的处理器
for (HandlerMethodReturnValueHandler handler : handlers) {if (handler.supportsReturnType(returnType)) {return handler;}
}

例如:

  • 返回@ResponseBody注解的User对象时,RequestResponseBodyMethodProcessorsupportsReturnType()会返回true(检测到@ResponseBody注解)。
  • 返回ModelAndView时,ModelAndViewMethodReturnValueHandler会匹配成功。
3.4.2 步骤 2:执行处理逻辑

匹配到处理器后,调用handleReturnValue()方法执行具体逻辑。不同处理器的逻辑差异较大,我们以两种典型场景为例:

场景 A:处理@ResponseBody(前后端分离)

RequestResponseBodyMethodProcessor的处理逻辑如下:

  1. 忽略视图渲染流程,标记ModelAndViewContainerrequestHandledtrue
  2. 调用HttpMessageConverter(如MappingJackson2HttpMessageConverter)将User对象序列化为 JSON。
  3. 设置响应头Content-Type: application/json,将 JSON 写入响应体。

核心代码片段:

// RequestResponseBodyMethodProcessor.handleReturnValue()
Object outputValue = ...; // 处理器返回值
HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
// 用Jackson将对象序列化为JSON
messageConverter.write(outputValue, mediaType, outputMessage);
// 标记请求已处理,跳过视图渲染
mavContainer.setRequestHandled(true);
场景 B:处理ModelAndView(传统视图渲染)

ModelAndViewMethodReturnValueHandler的处理逻辑如下:

  1. ModelAndView中提取视图名和模型数据。
  2. 将模型数据存入ModelAndViewContainer
  3. 将视图名设置到ModelAndViewContainer,不标记requestHandled(需后续视图渲染)。

核心代码片段:

// ModelAndViewMethodReturnValueHandler.handleReturnValue()
ModelAndView mav = (ModelAndView) returnValue;
// 合并模型数据到容器
mavContainer.addAllAttributes(mav.getModel());
// 设置视图名
if (StringUtils.hasText(mav.getViewName())) {mavContainer.setViewName(mav.getViewName());
}
// 不标记requestHandled,需视图渲染
3.4.3 返回值处理流程细节

下图展示了返回值处理的详细逻辑(以HandlerMethodReturnValueHandlerComposite为核心):
在这里插入图片描述

2.5 阶段 4:视图渲染(传统 MVC 场景)

若返回值需要视图渲染(如ModelAndView、String 视图名),DispatcherServlet会触发视图渲染流程,核心由ViewResolverView完成。

4.1 关键步骤
  1. 生成默认视图名:若未显式指定视图名(如返回非简单类型对象),DefaultRequestToViewNameTranslator会基于请求路径生成默认视图名(如/user/listuser_list)。
  2. 解析视图ViewResolver(如FreeMarkerViewResolver)根据视图名拼接前缀(classpath:templates/)和后缀(.ftl),生成模板路径(如case_21/model_and_view_return.ftl),并创建View对象。
  3. 渲染视图View对象(如FreeMarkerView)将模型数据注入模板,生成 HTML 并写入响应体。
4.2 视图渲染流程

在这里插入图片描述

例如,testModelAndViewReturn()方法返回的ModelAndView指定了视图名case_21/model_and_view_returnFreeMarkerViewResolver会解析为classpath:templates/case_21/model_and_view_return.ftl,并将user模型数据注入模板,生成如下 HTML:

<!doctype html>
<html lang="zh">
<head><meta charset="UTF-8"><title>ComplexObjectReturn测试</title>
</head>
<body>
<h1>Hello! ${user.name}</h1>
<p>您的年龄:${user.age}</p>
</body>
</html>

三、典型返回值类型的处理逻辑对比

不同返回值类型对应不同的处理器和流程,我们通过表格总结常见场景的核心差异:

返回值类型对应的处理器是否需要视图渲染响应体类型核心逻辑
ModelAndViewModelAndViewMethodReturnValueHandlerHTML提取视图名和模型数据,触发视图渲染
String(视图名)ViewNameMethodReturnValueHandlerHTML将 String 作为视图名,后续渲染流程
@ModelAttribute对象ServletModelAttributeMethodProcessor(false)HTML对象加入模型,生成默认视图名
非简单类型对象(无注解)ServletModelAttributeMethodProcessor(false)HTML默认视为@ModelAttribute对象处理
HttpEntity/ResponseEntityHttpEntityMethodProcessorJSON/XML序列化 body 为响应体,设置响应头
HttpHeadersHttpHeadersReturnValueHandler仅设置响应头,无响应体
@ResponseBody对象RequestResponseBodyMethodProcessorJSON/XML序列化对象为响应体,跳过视图

注意:String 返回值有歧义 —— 若方法加@ResponseBody,则由RequestResponseBodyMethodProcessor处理(响应 String 文本);否则由ViewNameMethodReturnValueHandler处理(视为视图名)。

四、实践:自定义返回值处理器

理解底层原理后,我们可以通过自定义返回值处理器扩展 Spring MVC 的能力。例如,实现一个Result对象处理器,自动将业务结果包装为统一 JSON 格式。

4.1 步骤 1:定义统一返回体Result

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {private int code; // 状态码:200成功,500失败private String message; // 提示信息private T data; // 业务数据public static <T> Result<T> success(T data) {return new Result<>(200, "success", data);}
}

4.2 步骤 2:实现自定义处理器ResultReturnValueHandler

public class ResultReturnValueHandler implements HandlerMethodReturnValueHandler {private final MappingJackson2HttpMessageConverter converter;public ResultReturnValueHandler(MappingJackson2HttpMessageConverter converter) {this.converter = converter;}// 支持返回值类型为Result的方法@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return returnType.getParameterType().equals(Result.class);}// 处理Result返回值:序列化为JSON,标记请求已处理@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 标记请求已处理,跳过视图渲染mavContainer.setRequestHandled(true);// 获取响应对象HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);assert response != null;response.setContentType("application/json;charset=UTF-8");// 序列化Result为JSONconverter.write(returnValue, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));}
}

4.3 步骤 3:注册自定义处理器

initReturnValueHandlers()中加入自定义处理器(注意优先级,需在兜底处理器之前):

private static void initReturnValueHandlers() {returnValueHandlerComposite = new HandlerMethodReturnValueHandlerComposite();MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();returnValueHandlerComposite.addHandlers(List.of(// ... 其他处理器 ...new ResultReturnValueHandler(jacksonConverter), // 自定义处理器new ServletModelAttributeMethodProcessor(true) // 兜底处理器));
}

4.4 步骤 4:使用自定义处理器

// 控制器方法返回Result对象
public Result<User> testResultReturn() {User user = new User("赵八", 35);return Result.success(user);
}

请求后会返回统一 JSON 格式:

{"code": 200,"message": "success","data": {"name": "赵八","age": 35}
}

五、总结

Spring MVC 的返回值处理机制是其灵活性和扩展性的核心体现,通过策略模式 + 链模式实现了对多种返回值类型的统一管理。核心要点如下:

  1. 流程驱动:从 “初始化处理器→匹配处理器→执行处理逻辑→视图渲染” 形成闭环,每个阶段职责明确。
  2. 扩展友好:通过实现HandlerMethodReturnValueHandler接口,可轻松扩展自定义返回值类型。
  3. 场景适配:区分传统视图渲染和前后端分离场景,兼顾兼容性和灵活性。

理解这一机制不仅能帮助我们快速定位问题(如返回值未按预期处理),还能基于业务需求进行定制化扩展,充分发挥 Spring MVC 的威力。


文章转载自:

http://0wXm7ZuP.yzxhk.cn
http://lEWw9eeA.yzxhk.cn
http://DoZjQO6X.yzxhk.cn
http://cguK3CVV.yzxhk.cn
http://wfWJloIW.yzxhk.cn
http://LnW6u7mB.yzxhk.cn
http://IiHHpXFy.yzxhk.cn
http://8mDcjzCy.yzxhk.cn
http://rX5jdRyx.yzxhk.cn
http://1j5foypT.yzxhk.cn
http://IlMBVnNq.yzxhk.cn
http://tzpwTtw2.yzxhk.cn
http://GZOTfkqN.yzxhk.cn
http://ZDjr2ozZ.yzxhk.cn
http://9440ajpJ.yzxhk.cn
http://iMwA5iHb.yzxhk.cn
http://lZY9tMb0.yzxhk.cn
http://WPKXQnWb.yzxhk.cn
http://yHdm98UV.yzxhk.cn
http://cDyCvPD7.yzxhk.cn
http://hVmGSubX.yzxhk.cn
http://OcIBq0ai.yzxhk.cn
http://Ku7oEHfr.yzxhk.cn
http://gECdstLr.yzxhk.cn
http://ZsKR3vCG.yzxhk.cn
http://iYgsB5La.yzxhk.cn
http://zPh4ipSu.yzxhk.cn
http://IqOw120d.yzxhk.cn
http://3Mad6Bx2.yzxhk.cn
http://ST1ZeuV6.yzxhk.cn
http://www.dtcms.com/a/383211.html

相关文章:

  • 黑马JavaWeb+AI笔记 Day05 Web后端基础(JDBC)
  • Open3D 射线投射(Ray Casting,Python)
  • RL【10-1】:Actor - Critic
  • 计算机视觉(opencv)实战二十一——基于 SIFT 和 FLANN 的指纹图像匹配与认证
  • 纯`css`固定标题并在滚动时为其添加动画
  • 金融科技:银行中的风险管理
  • 【办公类-113-01】20250914小2班生日手机备忘录提示、手机同屏到电脑UIBOT(双休日前移、节假日前移)
  • K8s学习笔记(二) Pod入门与实战
  • 如何下载Jemeter测试工具;如何汉化Jmeter2025最新最全教程!
  • 子网划分专项训练-2,eNSP实验,vlan/dhcp,IP规划、AP、AC、WLAN无线网络
  • 【LLM】大模型训练中的稳定性问题
  • Electron第一个应用
  • 企业设备维护成本预测模型全解析
  • 【数据结构】二叉树的概念
  • 架构思维: 高并发场景下的系统限流实战
  • 【开题答辩全过程】以 SpringBoot的乡村扶贫系统为例,包含答辩的问题和答案
  • Git 打标签完全指南:从本地创建到远端推送
  • RabbitMQ如何保障消息的可靠性
  • window显示驱动开发—枚举显示适配器的子设备
  • 《嵌入式硬件(九):基于IMX6ULL的蜂鸣器操作》
  • 《嵌入式硬件(十二):基于IMX6ULL的时钟操作》
  • Redis最佳实践——性能优化技巧之监控与告警详解
  • PySpark基础例题(包含map、reduceByKey、filter、sortBy等算子)
  • 导购APP佣金模式的分布式锁实现:基于Redis的并发控制策略
  • 运维自动化工具Ansible大总结20250914
  • Linux 库开发入门:静态库与动态库的 2 种构建方式 + 5 个编译差异 + 3 个加载技巧,新手速看
  • Effective Python 第28条:Python列表推导式的简洁与复杂性管理
  • 【MySQL】从零开始学习MySQL:基础与安装指南
  • 基于STM32的病人监护系统
  • Python与Go结合