[ SpringMVC ] SpringMVC如何通过是否有@RestController注解来判断返回ModelAndView还是Json

news/2025/3/17 16:18:43/文章来源:https://www.cnblogs.com/MingHaiZ/p/18776728

引言

之前在面试的遇到面试官问我SpringMVC的执行流程,我那时候回答的是SpringMVC的DispatcherServlet的dodispatch方法找到ControllerMethod之后将返回值通过convert成Json返回响应体,事后想了一下回答的其实并不正确,因为SpringMVC之前学习的时候有使用ModelAndView返回视图,我回答的只是前后端分离的情况, 现在回头学习的时候就在想,DispatchServlet是如何通过一个@RestController(@ResponseBody和@Controller)就能知道返回的是响应体而不是ModelAndView的,打开源码并找到了思路之后写下了这篇博客以记录一下(平时学习的时候看那些网上的资料或者书籍,感觉学习完再写博客就像抄书一样,不如想起来的时候再去网站看,自己琢磨了一会琢磨出来的东西还是比较适合记录一下)

调试过程

因为最近在实习,所以就直接拿实习时做的一个练习小模块来实验,这里用的时SpringBoot3和JDK21,来看一下Controller和Method
img

这里可以看见是个@RestController注解的Controller (这里一些太简单的东西就不讲这么细了,因为大部分是写出来给自己看的,如果你看到了这篇博客并且能理解那就是我的荣幸XD)

这时候我们找到DispatchServlet的doDispatch方法并打上断点
img

这里顺带可以复习一下SpringMVC前面的执行流程,一个Request到doDispatch方法的时候是通过mappedHandler = getHandler(processedRequest);来获取HandlerExecutionChain执行链
img

这里返回HandlerExecutionChain之后再根据当前的Handler确定HandlerAdapter
img
img

这里的supports(handler)方法就是判断当前的adapter是否适配当前的handler
img

这里的上面一个方法是是执行拦截器interceptor,就是应用已注册拦截器的 preHandle 方法。
img

这里不是重点看一下就好了,重点在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这个方法上,这里我们也可以看见注释Actually invoke the handler,也就是真正执行handler的地方,让我们进去细看
img
img

我们来到了方法handleInternal,这里this.synchronizeOnSession为false直接来到注释// No synchronization on session demanded at all...的方法 mav = invokeHandlerMethod(request, response, handlerMethod);当中
img
img

这里关键的方法是invocableMethod.invokeAndHandle(webRequest, mavContainer);,让我们进入到方法内部去看看
img

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);是获取执行后的返回值
img

这里是我们自定义的一个ResponseVO,在获取这个resultValue之后,会对ResponseStatus和ResponseStatusReason进行几次判断然后设置RequestHandled的boolean值进行设置,那么这个RequestHandled是什么呢?让我们点进去看一下源码的注解解释
img

接下来我们的代码走到了这里
img

也就是说这里处理器并没有完全处理请求,这时候有个断言来检查当前是否有handlers,这里通过之后进入try语句块,让我们进入这个方法

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

img

这里我们能能看到这里在选择HandlerMethodReturnValueHandler
img

当遍历到RequestResponseBodyMethodProcessor 这个MethodProcessor的时候可以看到这个类是支持这个返回ReturnType的,为什么呢?让我们来进入第二个if判断条件的handler.supportsReturnType(returnType) 当中
img

这里的判断条件就是你的类或方法是否有@ResponseBody注解,如果有的话就返回true,所以这里if判断通过返回当前HandlerMethodReturnValueHandler,所以返回的就是RequestResponseBodyMethodProcessor

让我们返回到handleReturnValue方法上,我们可以看到当selectHandler了之后如果当前的handler还是null的话就会直接抛出IllegalArgumentException

这里我们的handler不为null则直接调用handler的方法handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
img

这里就将RequestHandled给设置为true也就是说处理完毕了,接下来执行到writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);

writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage) 方法的作用是通过已注册的 HttpMessageConverter 将返回值转换为指定的数据格式,并直接写入 HTTP 响应体

这里我们直接定位到这个方法里的
img

这里通过for遍历已经注册的HttpMessageConverter ,这里满足条件的是MappingJackson2HttpMessageConverter 之后将converterTypeToUse设置为

ConverterType.GENERIC ,继续往下走,判断converterTypeToUse不为null之后将body处理完之后进入switch语句,然后将消息write进body之后返回.

到这里我们的doDispatch就算执行完毕了,调用return getModelAndView(mavContainer, modelFactory, webRequest);这时候我们会发现,前后端分离使用响应体的话doDispatch就直接返回null了,这里不用担心,因为消息已经写入了ResponseBody,接下来将正常的流程走完就没事了.

总结梳理

  1. 请求入口
    请求到达 DispatcherServletdoDispatch 方法,开始处理流程。
  2. 获取处理器链
    通过 getHandler() 获取 HandlerExecutionChain,确定具体的 Controller 方法。
  3. 选择适配器
    使用 HandlerAdapter(如 RequestMappingHandlerAdapter)执行目标方法。适配器通过 supports(handler) 判断是否支持当前处理器。
  4. 执行 Controller 方法
    调用 invokeHandlerMethod 执行 Controller 方法,获取返回值。
    关键判断点:若方法或类标注了 @ResponseBody(或组合注解 @RestController),会触发以下流程:
    • 返回值由 RequestResponseBodyMethodProcessor 处理。
    • 通过 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)将返回值序列化为 JSON,直接写入响应体。
    • 跳过视图解析ModelAndView 返回 null
  5. 返回值处理逻辑
    • 存在 @ResponseBody/@RestController
      • 遍历 HandlerMethodReturnValueHandler,匹配到 RequestResponseBodyMethodProcessor
      • 调用 writeWithMessageConverters 将返回值写入响应体,响应流程结束。
    • @ResponseBody
      • 返回 ModelAndView,后续由视图解析器处理视图渲染。
  6. 结果返回
    • 若响应体已写入(JSON 场景),doDispatch 返回 null,无需视图解析。
    • 若返回 ModelAndView,继续执行视图渲染流程。

这次研究过后感觉自己的Debug调试源码能力得到了锻炼,还有就是加深了对SpringMVC的理解,因为平时基本都是前后端分离的项目所以问到这个执行流程的时候就直接把ModelAndView视图解析阶段给跳过了,继续加油学习! 看Spring源码还是挺复杂的对我目前的阶段来说,因为封装太多东西了,慢慢学习不浮躁!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/900426.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从HR+AI到AI+HR,企业人力资源AI进程已过半

一、人力资源管理智能化应势而上,核心价值受企业管理层肯定 过往各项研究表明,AI 已经被广泛应用于企业经营的各个环节中。根据易路于2023 年发布的《AI 在企业人力资源中的应用白皮书1.0》(以下简称《白皮书1.0》),我们可以明确感受到:AI 已不同程度应用于招聘管理、员工…

multi-object tracking in the dark

创新点:构建LMOT数据集:开发了一种双摄像头系统,同步采集明暗视频帧,形成高度对齐的低光视频对,并提供高质量的多目标跟踪标注。该数据集包含大量城市户外场景视频,涵盖多种动态物体,为模型训练和评估提供了丰富的数据支持。 提出LTrack方法:引入自适应低通下采样模块(…

LGP11831_1 [UPTS 2025] 追忆 学习笔记

LGP11831_1 [UPTS 2025] 追忆 学习笔记 Luogu Link 前言 又幻想了……唉! 幻想自己场切这道题,最后标准分上升至 \(\text{598pts}\),翻掉了 \(\text{yyz}\),不至于一点脸不要。 本题解基本借鉴这篇题解。 题意简述 给定一个 \(n\) 点 \(m\) 边的简单有向图 \(G\),有 \(m\)…

sql 在两个数据表中,A表存在字段以逗号分隔存储B表的多id对象,进行关联查询

A 表:B表:关联查询 需求为,查询出A表的数据列表,需要将A表关联B表的数据id,概要通过B表的 name 进行输出显示 SELECT A.id,A.name,A.creator,A.created_at,GROUP_CONCAT(B.name SEPARATOR , ) AS B_names -- 将 c_name 合并为逗号分隔的字符串 FROM xf_service_type A LE…

算力市场何以拥有巨大潜力

算力市场未来确实具有巨大的潜力,这一判断基于多个方面的因素: 一、算力成为经济增长的主要驱动力 随着全球数字化转型的迅猛推进,算力已成为推动经济增长的关键引擎。各国纷纷加大在算力基础设施方面的投入,以期为经济发展注入新的活力。例如,欧盟委员会批准了一项名为“…

算法备案拟公示内容编写指南

除了自评估报告,算法备案复审中的拟公示内容也是难度颇大的一份材料,导致很多开发者的算法备案申请被驳回。今天我就提供一份简易模板供大家学习参考(请结合实际情况撰写,不要照抄,不要买模板,否则会判定真实性存疑或高度雷同,影响备案)。(各类文件套模板都会判定该真…

CH585 RF_Basic例程讲解含单向和双向发送

CH585_RF基础通讯例程见下图路径:1、RF初始化参数配置/******************************************************************************** @fn RFRole_Init** @brief RF应用层初始化** @param None.** @return None.*/ void RFRole_Init(void) {rfTaskID = TMOS_…

uniapp整合SQLite(Android)

一、勾选SQLite数据库选项 (1)HBuilder工具打开项目 (2)项目/manifest.json =>App模块配置 => 勾选SQLite(数据库)二、封装sqlite.ts 在项目根目录下创建sqlite/sqlite.ts// 数据库名称 const dbName = scan/*** 数据库地址* @type {String} 推荐以下划线为开头 _d…

No.68 Vue---vue3新特性

一、vue3新特性 1.1 六大亮点二、组合API(setup)2.1 ref或者reactive 1、创建项目 vue create vue-demo5 2、进入文件,启动服务。 3、 2.2 methods中定义的方法写在setup() 2.3setup()中使用props和context 在2.x中,组件的方法中可以通过this获取到当前组件的实例,并执…

2025年2月国产数据库大事记-墨天轮

​本文为墨天轮社区整理的2025年2月国产数据库大事件和重要产品发布消息,一起看看2月有哪些大事发生~本文为墨天轮社区整理的2025年2月国产数据库大事件和重要产品发布消息。 目录2025年2月国产数据库大事记 TOP10 2025年2月国产数据库大事记(时间线) 产品/版本发布 兼容认证…

Mybatis-入门

配置:JDBC:原始HDBC的问题:数据库连接池:lombok: