跳到主要内容
约 7 分钟2 张卡

Spring MVC 请求生命周期

Spring MVC 请求生命周期描述一次 HTTP 请求怎样到达 Controller,再将结果写回客户端。本文从 Servlet 容器接收请求开始,沿过滤链和 MVC 分派介绍同步调用顺序,再说明异步续接与请求耗时。

1. 面试核心​

先记住这几条,再读正文
  • 入口链路。 Servlet 容器接收 HTTP 请求,按映射让请求经过 Filter 链,再进入 DispatcherServlet。
  • 处理器调用。 DispatcherServlet 通过 HandlerMapping 找到处理器和拦截器,再由 HandlerAdapter 解析参数并调用 Controller。卡 · Servlet 容器
  • 响应写出。 @ResponseBody 返回值由消息转换器按协商的媒体类型写出,视图名由 ViewResolver 解析后交给 View 渲染。
  • 异步派发。 MVC 异步释放初始请求线程,结果就绪后通过 ASYNC 派发继续处理返回值。卡 · 异步请求
  • 完整请求耗时。 序列化发生在 Controller 返回后,也计入完整请求耗时。

2. Servlet 容器​

请求到达 MVC 之前,服务器需要先处理连接和 HTTP 消息。Tomcat、Jetty 等服务器解析请求行和请求头,并提供读取请求体的入口。Servlet 容器据此构造 request/response 对象,再按线程模型安排请求执行。

MVC 接收这些 Servlet API 对象,负责应用层分派。TCP 连接与 HTTP 报文解析由服务器承担。排查慢请求时,应区分服务器容量与应用容量:

  • 连接数限制服务器同时维护多少连接。
  • accept 队列保存等待应用接受的连接,请求过多时会在入口等待。
  • 请求线程决定容器怎样安排请求执行,keep-alive 则决定连接能否继续复用。
  • 数据库连接池和业务执行器由应用配置,需要单独观察使用量与等待时间。

3. Filter 链​

容器按照 URL 和派发类型选择 Filter,过滤器调用 chain.doFilter() 后,请求才会继续进入下一项。Spring Security 也通过 Servlet 过滤链接入,在请求进入 MVC 前执行安全检查。

请求包装、拒绝响应和返回后的清理见 Spring 拦截机制。

4. DispatcherServlet​

请求通过过滤链后,由 DispatcherServlet 协调 MVC 组件。handler 指实际处理请求的对象,adapter 则封装它的调用方式。下面按顺序说明一次同步请求怎样完成:

  1. HandlerMapping 根据请求查找处理器和匹配的拦截器。
  2. DispatcherServlet 选择支持该处理器的 HandlerAdapter。
  3. 拦截器执行 preHandle,决定是否继续调用。
  4. 适配器用参数解析器、数据绑定和消息转换器准备参数,再调用 Controller。
  5. 返回值处理器写出响应正文,或准备视图与模型数据。
  6. 拦截器执行 postHandle,处理同步调用的返回结果。
  7. 有视图时执行渲染,随后调用 afterCompletion 完成清理。

映射、参数解析或 Controller 调用等阶段抛出异常后,会进入 HandlerExceptionResolver 异常解析链。解析器会尝试将异常转换成错误响应。

各策略组件的职责见 DispatcherServlet。

5. 返回值处理​

Controller 返回 Java 对象后,MVC 还需要把它转换成 HTTP 响应。以下方法在 @RestController 中返回订单对象,因此结果会被作为响应正文处理:

@RestController
class OrderController {
@GetMapping("/orders/{id}")
OrderResponse get(@PathVariable long id) {
return service.find(id);
}
}

返回值处理器根据协商的媒体类型选择 HttpMessageConverter,也就是消息转换器。比如协商结果为 JSON 时,合适的转换器会将 OrderResponse 序列化为 JSON,再写入响应体。

不同声明会改变返回值的含义,需要区分视图名、响应正文和 ResponseEntity:

  • 普通 @Controller 方法返回 String 时,可将字符串解释为视图名。
  • 使用 @ResponseBody 或 @RestController 时,String 也作为正文写出。
  • 返回 ResponseEntity 时,除了正文,还可指定状态码和响应头。

视图名交给 ViewResolver 解析,得到 View 后再渲染页面。JSON 序列化则发生在 Controller 返回之后,仍属于 MVC 返回值处理阶段。如果序列化失败且响应尚未提交,MVC 还能尝试通过异常解析器生成错误响应。

6. 异步请求​

Controller 需要等待耗时任务时,可以先返回一个异步结果对象,让初始请求线程退出。Callable 与 DeferredResult 都支持稍后提供单次结果,区别在于由谁生产结果:

返回类型结果生产方式
CallableMVC 将任务交给配置的 AsyncTaskExecutor
DeferredResult应用从其他线程调用 setResult 或 setErrorResult

两者都会开启 Servlet async,也就是 Servlet 的异步请求机制,让初始请求线程返回容器。结果准备好后,MVC 发起 ASYNC 派发,将同一请求再次交给容器处理。这次框架重新定位 handler,然后用已有结果继续返回值处理,Controller 方法无需再执行一次。

初始线程会调用异步拦截器的 afterConcurrentHandlingStarted,此时跳过普通的 postHandle 与 afterCompletion。最终派发再进入完成流程。发生超时时,还需要相应的异步回调处理超时结果。

由于请求会切换线程,清理逻辑需要覆盖每次线程退出。具体规则见 Spring 拦截机制。

Callable 抛错,或应用调用 setErrorResult(exception) 后,异常会随异步派发进入 MVC 异常解析。独立 @Async 后台任务则通过 Future 等返回结果或异步异常处理器报告失败,需要调用方单独处理。

7. 请求日志与指标​

请求日志需要说明哪个请求耗时、在哪里耗时。入口日志至少保留以下信息:

  • 请求 ID,用于关联同一请求的多条日志。
  • HTTP 方法和路径模板,用于确定接口。
  • 响应状态码,用于区分成功与失败。
  • 完整耗时与响应大小,用于判断处理和写出成本。

指标应使用 /users/{id} 这样的路径模板。若直接把每个用户 ID 放进标签,就会产生大量不同标签值,这称为高基数,会增加指标存储与查询成本。

服务端处理耗时包含 Filter、参数读取、业务执行和响应序列化,异步请求还要计入等待结果的时间。Controller 方法计时只反映其中一段。

定位慢请求时,可通过调用追踪或性能分析区分各阶段耗时。数据库等待应单独观察,入口前的连接等待也要结合客户端与容器指标判断。这样才能区分业务执行慢、排队慢和响应写出慢。

8. 异步执行容量​

MVC 异步可以释放 Servlet 请求线程,但剩余工作仍要消耗资源。Callable 中的阻塞任务会占用执行器线程,正在执行的数据库操作也继续占用连接。因此,异步接口需要同时约束执行器、超时与下游容量。

AsyncTaskExecutor 是 MVC 执行异步任务的执行器,应配置线程和队列容量,以及任务过多时的拒绝策略。还需设置异步超时,并处理超时后才返回的任务结果。HTTP 超时结束响应时,后台任务仍需单独取消或收尾,才能释放其占用的资源。

MVC 消息写出仍采用阻塞 I/O。WebFlux 使用不同的响应式执行模型,不经过这套 Servlet DispatcherServlet 调用链。Java 21 虚拟线程也可以承载传统 Servlet 同步代码和阻塞任务。选择时应结合阻塞特征与框架支持,下游数据库连接等容量仍需单独限制。

9. 常见问题​

9.1 Controller 是 Servlet 吗?​

注解 Controller 是由适配器调用的 Spring Bean。DispatcherServlet 才是 Servlet 前端控制器,它负责接收容器分派的请求,再协调 Controller 调用。

9.2 一个请求一定只执行一次 Filter 吗?​

10 · 面试题卡片​

10.1 一次 HTTP 请求在 Spring MVC 中怎样到达 Controller​

出现公司:滴滴、远景能源

考察重点:按请求顺序说明容器与 MVC 组件的分工,并解释参数准备、返回值转换和异常解析发生在哪个阶段。

相关内容:Servlet 容器至返回值处理。

参考回答

请求由服务器解析,经 Servlet 容器安排执行,再通过 Filter 链进入 DispatcherServlet。

DispatcherServlet 用 HandlerMapping 找到 handler 和拦截器,再选择支持它的 HandlerAdapter。

preHandle 放行后,适配器准备参数并调用 Controller。参数准备会用到参数解析器、数据绑定和消息转换器。方法返回后,返回值处理器写出正文或准备视图,随后执行后置回调、视图渲染和完成回调。

异常进入解析器链,异步结果通过后续派发续接。参数读取和响应序列化都占用时间,所以完整请求耗时要覆盖 Controller 方法之外的阶段。

10.2 Controller 返回 Callable 后,结果怎样写回,Controller 会再次执行吗?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:解释初始线程何时释放、结果由谁生成,以及异步派发怎样继续处理已有结果。

相关内容:异步请求。

参考回答

Callable 的结果通过 ASYNC 派发交回 MVC,Controller 方法无需再次执行。

MVC 开启 Servlet async 后,把 Callable 交给执行器,初始请求线程就可以返回容器。

任务完成后,MVC 保存结果并发起 ASYNC 派发。框架重新找到 handler,再使用已有结果继续返回值转换和响应写出。如果 Callable 抛出异常,异常也通过这次派发交给 MVC 解析器。

11. 参考资料​