前言
一个完整的请求响应在MVC中是什么样的?用户访问 /user/info 时后台发生了什么?M-V-C分别在哪?
这些就是这篇文章的重点,希望对你有帮助
提醒:建议大家阅读类似这种源码流程时,不能把完全记住或死记硬背流程视为重点。 真正有价值的是通过这些流程,让我们弄清楚:我们日常使用的注解、配置、模块究竟在哪一步起作用、与哪些组件协作、发生了哪些链式调用。 理清这些“串联关系”,能帮助我们:明确定位、快速排查问题以及启发性能和流程优化思路。
一、简单说说SpringMVC 框架
SpringMVC 是基于 Servlet 的轻量级 Web 框架,用于处理 HTTP 请求,实现请求分发、参数绑定、视图解析等功能,帮助开发者更轻松地构建 Web 应用
二、执行流程的涉及的组件
下面将会介绍一些流程具体流程中涉及的组件,比较多但是不难,可以结合箭头流程图反复观看(截图对照会更有效率哦)
为了大家更容易理解,每个组件我都给了一个简单比喻,比喻可以帮助我们非常快速的理解未知,如果你已经很理解各个组件可以选择性观看,因为比喻是一定存在局限性的。
-
DispatcherServlet:前端控制器
作用: SpringMVC 的核心调度器,拦截所有请求,负责协调 HandlerMapping、HandlerAdapter、ViewResolver 等组件完成整个请求处理流程。
简单比喻: 请求总指挥官,像机场塔台,负责指挥整个航线流程,决定飞机去哪、谁来接、怎么处理。 -
HandlerMapping:处理器映射器
作用: 根据请求路径查找对应的处理器(Controller 方法)和匹配的拦截器,返回一个执行链(HandlerExecutionChain)。
简单比喻: 像高德地图,根据出发地(URL)给出终点(Controller)及沿途要经过的所有检查站(拦截器)。 -
HandlerExecutionChain:处理器执行链
作用: 封装一个 handler(控制器方法)和对应的所有拦截器,形成一个链式结构。
简单比喻: 就像一条通往目的地的高速公路,终点是控制器,中间设有多个安检点(拦截器)。 -
HandlerInterceptor:处理器拦截器
作用: 实现请求的预处理(preHandle)、后处理(postHandle)和完成回调(afterCompletion),可用于权限校验、日志记录等。
简单比喻: 守门员,决定你能不能进入、进去后是否被监控、出来后是否清理现场。 -
HandlerAdapter:处理器适配器
作用: 用来执行具体的处理器(如 Controller 方法),屏蔽不同类型处理器的差异。
简单比喻: 万能插头适配器,让各种插头(不同风格的 Controller)都能正常通电工作。 -
Controller(HandlerMethod):控制器方法
作用: 接收请求参数、执行业务逻辑,并返回视图名或数据结果。
简单比喻: 厨师,拿到订单(请求)后炒菜(业务逻辑),把结果打包好(返回视图或 JSON)交给用户。 -
HandlerExceptionResolver:异常处理器
作用: 处理控制器方法或参数解析过程中抛出的异常,执行 @ExceptionHandler/@ControllerAdvice 的逻辑,生成最终响应。
简单比喻: 救火队员,一旦某处着火(出异常),就立刻响应处理并善后。 -
ViewResolver:视图解析器
作用: 将 Controller 返回的逻辑视图名(如 “user/info”)解析为具体的物理视图路径(如 “/WEB-INF/views/user/info.jsp”)。
简单比喻: 快递地址翻译官,把“张三公司”这样的别名翻译成“北京市朝阳区xxx号”这样的真实地址。 -
View:视图对象
作用: 根据模型数据和模板合并渲染页面(HTML)或输出数据(如 JSON),将结果写入响应流。
简单比喻: 打印机,接收内容和模板,把它们合成为最终输出的纸张(HTML页面或数据流)。 -
HttpMessageConverter:消息转换器
作用: 用于将 Controller 返回的对象转换为 JSON、XML 等格式,写入响应体中。
简单比喻: 翻译官,把 Java 世界的话翻译成浏览器或前端能看懂的语言(如 JSON 文本)。
三、具体执行流程(源码级别)
用户发起 HTTP 请求(如 /user/info)
↓
DispatcherServlet#doDispatch() ← 核心调度入口
↓
【HandlerMapping】获取请求映射处理器和拦截器链
→ getHandler(request)
(返回一个处理器执行链HandlerExecutionChain ,包括根据当前请求路径查找匹配的目标处理器方法及其对应的所有拦截器,并形成一个链)
→ 返回 HandlerExecutionChain {
handler = Controller 方法(HandlerMethod) ← Controller 层(C)
interceptors = 所有匹配的拦截器列表
}
↓
【拦截器预处理阶段】
→ handlerExecutionChain.applyPreHandle(request, response)
↓
依次执行每个 HandlerInterceptor.preHandle()
├─ 返回 false:中断请求链
→ handlerExecutionChain.triggerAfterCompletion()
→ 逆序执行已通过的拦截器的 afterCompletion()
回到 → DispatcherServlet.doDispatch() return ,响应结束 , 控制器不会执行
↓
├─ 返回 true:继续
所有拦截器通过(preHandle 都是 true)→ 继续往下执行
↓
【HandlerAdapter适配处理器 调用 Controller】
→ getHandlerAdapter(handler) → 底层通常是 RequestMappingHandlerAdapter(多态)
↓
RequestMappingHandlerAdapter.handle(request, response, handler)
↓
通过反射执行 @Controller 中的方法(自己写的方法) ← Controller 层(C)
→ 解析参数(@RequestParam/@RequestBody 等)
→ 执行业务逻辑
→ 返回值(视图名 + model,或对象本身)
├─ 返回 String + model → 封装为 ModelAndView
├─ 返回 @ResponseBody 对象 → 封装为 ResponseBodyEmitter 等
【Controller 方法内或参数解析过程中若抛出异常】
↓
进入异常处理流程:
→ DispatcherServlet 捕获异常
→ 遍历容器中所有 HandlerExceptionResolver
→ ExceptionHandlerExceptionResolver 检查 @ControllerAdvice/@ExceptionHandler
→ 如果匹配,调用异常处理方法,返回 ModelAndView 或 @ResponseBody 响应
→ 继续走渲染/响应流程
↓
【执行拦截器 postHandle()】
→ handlerExecutionChain.applyPostHandle()
→ 顺序执行所有 interceptor.postHandle()
↓
【视图解析(ViewResolver)】
→ 判断返回的是逻辑视图名?还是 JSON?
① 视图渲染流程(页面返回):
→ DispatcherServlet.resolveViewName(viewName, locale)
→ ViewResolver视图解析器(如 ThymeleafViewResolver)
→ 得出真实物理路径:
/WEB-INF/templates/xxx.html
→ 并返回 View 对象(ThymeleafView / JstlView 等) ← View 层(V)
↓
View#render渲染(model, request, response)
→ 模板引擎合并视图 + model 数据 ← Model 层(M)
→ 输出最终完整 HTML ,调用 response.getWriter().write(html)写入输出流
② JSON 响应流程(API 接口):
→ 检测到 @ResponseBody 或 @RestController
→ HttpMessageConverter#write()
→ 将对象序列化为 JSON,调用 response.getWriter().write(json)写入输出流
↓
【请求处理完成】
→ 调用 handlerExecutionChain.triggerAfterCompletion()
→ 逆序执行所有 interceptor.afterCompletion()
↓
DispatcherServlet.doDispatch() 方法执行完毕
↓
最终响应返回给浏览器(HTML / JSON)
源码位置
1.DispatcherServlet#doDispatch()
2.RequestMappingHandlerAdapter.handle(request, response, handler)
这里ha源码中是HandlerAdapter,但实际上去执行的实现类通常是RequestMappingHandlerAdapter
3.View#render
三、超简略流程
- 接收请求
- 解析处理器执行链
- 执行拦截器的 preHandle(正序)
- 调用 Controller 方法
- 执行拦截器的 postHandle(正序)
- 视图解析与渲染
- 执行拦截器的 afterCompletion(逆序)
- 返回响应结果
四、思考/误区
最后给大家一些我学习时记录的思考/误区。
思考1:思考整个流程:
SpringMVC 的整个请求处理流程,是不是“先处理完用户的业务需求”,然后“将处理结果以页面或 JSON 的形式响应给用户”。
从开发角度来看,Controller 执行结束已经完成了功能;
而从用户角度来看,只有在浏览器成功看到页面或拿到接口返回结果时,才真正“感知到完成”。
答案是正确的
思考2.为什么拦截器的preHandle方法顺序执行,postHandlde和afterHandle逆序执行?
解答:因为preHandle 是从前往后逐个判断是否放行,postHandle / afterCompletion 是对已通过的拦截器做“清理/收尾/回调”工作,因此必须逆序执行,才能形成一种“先进后出”的闭环控制。
这是一个典型的“责任链 + 栈结构”模型,preHandle 是“推进阶段”:一层一层放行,只要有一个拦截器不放行就终止流程,而postHandle / afterCompletion 是“回溯阶段”:调用顺序要与进入顺序相反,形成前后呼应、逻辑闭环。
类比理解:
preHandle(正序):
安检员A → 安检员B → 安检员C → Controller执行(真正的业务)
postHandle / afterCompletion(逆序):
安检员C ← 安检员B ← 安检员A ← 返回响应
误区1. HandlerMapping解析处理器执行链
根据当前请求路径查找匹配的目标处理器方法及其对应的所有拦截器,封装为处理器执行链
错误的认为:这里会获取所有的Controller,然后后面会选择分发
错误的认为:这里会获取所有的拦截器,实际上SpringMVC 中的拦截器是基于路径匹配按需激活的