SpringMVC 处理请求的流程是怎样的?
面试考察点
- 核心组件认知:面试官不仅仅是想知道流程顺序,更想知道你是否清楚
DispatcherServlet、HandlerMapping、HandlerAdapter、ViewResolver等核心组件各自的职责。 - 源码级理解:能否说出
DispatcherServlet的doDispatch()方法中的关键调用链路,而不是只停留在表面背诵。 - 扩展点掌握:是否了解
拦截器(Interceptor)和过滤器(Filter)在请求处理流程中的位置,以及如何利用它们做定制化处理。
核心答案
SpringMVC 处理请求的核心是 DispatcherServlet(前端控制器),它是一个标准的 "模板方法" 模式。整个流程可以概括为 9 个步骤:
| 步骤 | 组件 | 做了什么 |
|---|---|---|
| 1 | 用户 | 发送请求到 DispatcherServlet |
| 2 | HandlerMapping | 根据 URL 找到对应的 Handler(Controller 方法) |
| 3 | DispatcherServlet | 拿到 Handler 对应的 HandlerAdapter |
| 4 | HandlerAdapter | 执行拦截器的 preHandle() |
| 5 | HandlerAdapter | 执行 Handler(Controller 方法),返回 ModelAndView |
| 6 | DispatcherServlet | 执行拦截器的 postHandle() |
| 7 | ViewResolver | 如果有视图,解析视图名称为具体 View |
| 8 | View | 渲染视图(模型数据填充到视图) |
| 9 | DispatcherServlet | 执行拦截器的 afterCompletion(),返回响应 |
前后端分离项目(REST API)中,步骤 7 和 8 通常跳过,@ResponseBody 直接将返回值序列化为 JSON 写回客户端。
深度解析
一、完整请求处理流程图
上图展示了 SpringMVC 处理一个 HTTP 请求的完整生命周期。按阶段拆解:
- 阶段一(请求分发):所有请求统一由
DispatcherServlet接收。它是整个流程的 "总调度",请求来了之后,它不自己干活,而是委派给各个组件去处理。 - 阶段二(查找 Handler):
DispatcherServlet调用HandlerMapping,根据请求的 URL、HTTP Method 等信息,找到对应的 Controller 方法。返回的是一个HandlerExecutionChain,里面包含了目标 Handler 和配置在该 Handler 上的拦截器链。 - 阶段三(适配执行):拿到 Handler 后,
DispatcherServlet会再找一个HandlerAdapter来执行它。为什么要多一层适配器?因为 Handler 可能有不同的类型(@RequestMapping方法、Controller接口实现类、HttpRequestHandler等),适配器负责统一调用方式。 - 阶段四(拦截器前置处理):执行所有拦截器的
preHandle()方法。如果任何一个返回false,请求直接中断,跳到afterCompletion()。 - 阶段五(执行业务逻辑):
HandlerAdapter通过反射调用 Controller 方法,执行业务逻辑,处理参数绑定、数据校验等。返回值可能是ModelAndView,也可能是直接的对象(@ResponseBody场景)。 - 阶段六(拦截器后置处理):执行所有拦截器的
postHandle()方法,可以对ModelAndView做进一步修改。 - 阶段七(视图解析或 JSON 序列化):如果是传统 MVC 返回视图名,
ViewResolver把逻辑视图名解析为具体的View对象;如果是@ResponseBody,HttpMessageConverter直接把返回对象序列化为 JSON。 - 阶段八(视图渲染):
View对象把 Model 数据填充到模板中,生成最终的 HTML。 - 阶段九(最终回调):执行拦截器的
afterCompletion(),用于资源清理、日志记录等。无论请求成功还是异常,这个方法都会执行。
二、核心组件详解
面试的时候不能光背流程,面试官一定会追问各个组件是干什么的:
| 组件 | 职责 | 常见实现 |
|---|---|---|
DispatcherServlet | 前端控制器,统一接收和分发请求 | Spring 内置 |
HandlerMapping | 根据请求 URL 找到对应的 Handler | RequestMappingHandlerMapping |
HandlerAdapter | 适配并执行不同类型的 Handler | RequestMappingHandlerAdapter |
Handler | 具体的业务处理器 | 你写的 @RequestMapping 方法 |
HandlerInterceptor | 拦截器,在 Handler 执行前后做拦截处理 | 自定义实现 HandlerInterceptor |
ViewResolver | 视图解析器,把视图名解析为 View 对象 | InternalResourceViewResolver |
View | 视图,负责渲染最终页面 | JstlView、ThymeleafView |
HttpMessageConverter | 消息转换器,负责请求/响应的序列化与反序列化 | MappingJackson2HttpMessageConverter |
三、源码层面的 doDispatch()
整个流程的源码入口在 DispatcherServlet.doDispatch(),核心逻辑就这几行(简化版):
// DispatcherServlet.java(简化版源码)
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 1. 根据 request 找到 Handler(包含拦截器链)
HandlerExecutionChain mappedHandler =
getHandler(request);
// 2. 找到对应的 HandlerAdapter
HandlerAdapter handlerAdapter =
getHandlerAdapter(mappedHandler.getHandler());
// 3. 执行拦截器 preHandle()
if (!mappedHandler.applyPreHandle(request, response)) {
return; // 返回 false,中断请求
}
// 4. 执行 Handler(Controller 方法)
ModelAndView mv = handlerAdapter.handle(request, response,
mappedHandler.getHandler());
// 5. 执行拦截器 postHandle()
mappedHandler.applyPostHandle(request, response, mv);
// 6. 处理视图渲染 / JSON 序列化
processDispatchResult(request, response, mappedHandler, mv);
}
源码其实很清晰,就是一条线:找 Handler → 找适配器 → 拦截器前置 → 执行业务 → 拦截器后置 → 处理结果。
四、前后端分离 vs 传统 MVC
现在大部分项目都是前后端分离的,这个流程会简单很多:
// 传统 MVC:返回视图名,需要 ViewResolver 解析
@GetMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", userService.getById(1L));
return "userDetail"; // → ViewResolver → userDetail.html
}
// 前后端分离:直接返回 JSON,跳过 ViewResolver 和 View 渲染
@GetMapping("/user")
@ResponseBody // 或者类上用 @RestController
public User getUser() {
return userService.getById(1L);
// → HttpMessageConverter → JSON 写回响应
}
前后端分离场景下,@ResponseBody(或 @RestController)会让流程在步骤 5 之后直接走 HttpMessageConverter,把返回对象序列化为 JSON 写回客户端,跳过 ViewResolver 和 View 渲染这两个步骤。
五、拦截器 vs 过滤器
面试的时候经常会被问到拦截器和过滤器的区别,因为它们都在请求处理流程中起作用:
请求 → Filter① → Filter② → DispatcherServlet → Interceptor① → Interceptor② → Handler
响应 ← Filter① ← Filter② ← DispatcherServlet ← Interceptor① ← Interceptor② ← Handler
| 对比维度 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 规范 | Servlet 规范 | Spring MVC 规范 |
| 作用范围 | 所有请求(包括静态资源) | 只针对 DispatcherServlet 处理的请求 |
| 执行时机 | 在 DispatcherServlet 之前 | 在 Handler 执行前后 |
| 获取 Bean | 不方便(Servlet 容器管理) | 可以直接 @Autowired 注入 Spring Bean |
| 典型场景 | 编码设置、跨域处理、XSS 过滤 | 登录校验、权限控制、日志记录 |
选择建议:需要用到 Spring 容器中的 Bean 的场景用拦截器;纯粹的 HTTP 层面的处理用过滤器。大部分业务场景用拦截器就够了。
面试高频追问
-
追问一:
DispatcherServlet是线程安全的吗?DispatcherServlet本身是单例的,但它是无状态的(不会在成员变量中保存请求数据),所以是线程安全的。每个请求通过方法参数(HttpServletRequest、HttpServletResponse)传递数据,不共享状态。
-
追问二:Spring MVC 如何处理异常?
DispatcherServlet在doDispatch()中有 try-catch,捕获到异常后会交给HandlerExceptionResolver处理。可以自定义@ControllerAdvice+@ExceptionHandler来统一处理异常,返回自定义的错误响应。
-
追问三:如何实现一个登录拦截器?
- 实现
HandlerInterceptor接口,在preHandle()中检查 session 或 token,未登录则重定向或返回 401。然后在WebMvcConfigurer中注册拦截器并配置拦截路径。
- 实现
常见面试变体
- "说一下
DispatcherServlet的工作流程?" - "Spring MVC 的九大组件是什么?"
- "
HandlerMapping和HandlerAdapter的作用是什么?" - "Spring MVC 拦截器和过滤器有什么区别?"
记忆口诀
收 → 找 → 配 → 拦 → 执 → 拦 → 解 → 渲 → 回
翻译:接收请求 → 找 Handler → 配适配器 → 拦截器前置 → 执行 Handler → 拦截器后置 → 解析视图 → 渲染视图 → 最终回调
前后端分离项目记住一句话:走到第 5 步直接 JSON 序列化返回,后面两个拦的(
ViewResolver、View)都跳过。
总结
SpringMVC 的请求处理流程本质就是 DispatcherServlet 的模板方法模式:统一接收请求 → HandlerMapping 找处理器 → HandlerAdapter 执行处理器 → 拦截器链前后拦截 → 处理结果(视图渲染或 JSON 序列化)。面试时把这条主线讲清楚,再配上拦截器和过滤器的区别,基本就能拿满分。
