一个 HTTP 请求进入 Spring MVC 应用后,大致经历了哪些主要步骤?
直接进入干货分享环节:假设我们的 Spring MVC 应用配置了 DispatcherServlet 作为前端控制器,并且映射路径为 /。
-
请求到达 Web 容器 (如 Tomcat):
- 客户端(例如浏览器)发送一个 HTTP 请求(比如
GET /myapp/users/123)。 - Web 容器(Tomcat, Jetty 等)接收到这个请求。
- 客户端(例如浏览器)发送一个 HTTP 请求(比如
-
Web 容器路由到
DispatcherServlet:- Web 容器根据应用的部署描述符 (
web.xml或 Java Servlet 容器初始化器配置) 中DispatcherServlet的<servlet-mapping>(例如/或/app/*),将该请求交给对应的DispatcherServlet实例处理。
- Web 容器根据应用的部署描述符 (
-
DispatcherServlet接收请求:DispatcherServlet作为请求的统一入口点,开始处理该请求。
-
查找 Handler (处理器):
DispatcherServlet查询其配置的所有HandlerMapping实现(按顺序)。HandlerMapping的任务是根据请求的信息(如 URL 路径/users/123,HTTP 方法GET等)查找能够处理该请求的 Handler(通常是一个 Controller 类中的特定方法,封装为HandlerMethod对象)。- 如果找到合适的 Handler,
HandlerMapping会返回一个HandlerExecutionChain对象。这个对象不仅包含了找到的 Handler 本身,还包含了应用于该 Handler 的所有拦截器 (HandlerInterceptor) 列表。 - 如果没有找到 Handler,通常会返回 404 Not Found 错误。
-
获取 HandlerAdapter (处理器适配器):
DispatcherServlet拿到了HandlerExecutionChain后,需要调用其中的 Handler。但 Handler 的类型可能多种多样(例如基于注解的HandlerMethod,旧式的实现了Controller接口的类等)。- 为了以统一的方式调用不同类型的 Handler,
DispatcherServlet会查询其配置的所有HandlerAdapter实现。 - 它会找到支持当前 Handler 类型(例如
HandlerMethod)的那个HandlerAdapter(例如RequestMappingHandlerAdapter)。
-
执行拦截器的
preHandle方法:HandlerAdapter在真正调用 Handler 方法之前,会按照HandlerExecutionChain中定义的顺序,依次调用所有拦截器的preHandle(request, response, handler)方法。- 这些拦截器可以执行一些预处理逻辑,如权限检查、日志记录等。
- 如果任何一个
preHandle方法返回false,则请求处理流程在此中断,DispatcherServlet会认为请求已被处理(拦截器可能直接生成了响应),然后会反向执行已执行过的拦截器的afterCompletion方法,然后结束。
-
调用 Handler (Controller 方法):
- 如果所有拦截器的
preHandle都返回true,HandlerAdapter就会调用 Handler(即 Controller 方法)。 - 在调用之前,
HandlerAdapter内部会利用HandlerMethodArgumentResolver来解析 Controller 方法的参数。这包括:- 从请求中提取数据(如
@RequestParam,@PathVariable,@RequestBody等)。 - 进行数据绑定(将请求参数映射到方法参数对象)。
- 进行数据类型转换。
- 执行数据校验(如果使用了
@Valid等)。
- 从请求中提取数据(如
- Controller 方法执行应用程序的核心业务逻辑,可能会调用 Service 层、与数据库交互等。
- 如果所有拦截器的
-
Handler 方法返回结果:
- Controller 方法执行完毕后会返回一个结果。这个结果可能是:
ModelAndView对象:包含了逻辑视图名和模型数据。String:通常代表逻辑视图名。void:表示 Controller 自己处理了响应(例如直接操作HttpServletResponse)。- 一个普通对象 (POJO):如果方法或类上有
@ResponseBody注解,这个对象会被序列化后写入响应体。 ResponseEntity:可以更精细地控制响应状态码、头和响应体。- 其他(如
Callable,DeferredResult用于异步处理)。
- Controller 方法执行完毕后会返回一个结果。这个结果可能是:
-
执行拦截器的
postHandle方法:HandlerAdapter在成功调用完 Handler 方法后(但在视图渲染之前),会按照HandlerExecutionChain中定义的逆序,依次调用所有拦截器的postHandle(request, response, handler, modelAndView)方法。- 这些拦截器可以修改
ModelAndView对象(例如添加一些公共的模型属性)或执行其他后处理逻辑。 - 注意: 如果 Handler 方法执行过程中抛出了异常,
postHandle方法不会被执行。
-
处理 Handler 结果 / 视图解析 (如果需要):
DispatcherServlet接收HandlerAdapter返回的结果。- 如果结果是
ModelAndView或 String (逻辑视图名):DispatcherServlet会查询所有配置的ViewResolver实现(按顺序)。ViewResolver根据逻辑视图名解析得到一个具体的View接口的实例(如JstlView,ThymeleafView)。这个View对象知道如何渲染特定的视图技术(如 JSP, Thymeleaf)。
- 如果结果是
@ResponseBody或ResponseEntity:- 这个步骤会被跳过。结果会直接交给后续步骤进行响应体写入。
-
视图渲染 (如果需要):
- 如果上一步解析得到了
View实例,DispatcherServlet会调用该View实例的render(model, request, response)方法。 View会使用传递过来的模型数据 (Model) 来渲染最终的输出(例如,JSP 引擎执行 JSP 文件生成 HTML)。- 渲染结果会被写入
HttpServletResponse的输出流。
- 如果上一步解析得到了
-
响应体写入 (对于
@ResponseBody/ResponseEntity):- 如果 Controller 返回的是需要直接写入响应体的对象 (
@ResponseBody或ResponseEntity的 Body),DispatcherServlet(或者说HandlerAdapter内部的HandlerMethodReturnValueHandler) 会使用注册的HttpMessageConverter(如MappingJackson2HttpMessageConverter) 将该对象序列化(例如转为 JSON 字符串)并写入HttpServletResponse的输出流。
- 如果 Controller 返回的是需要直接写入响应体的对象 (
-
执行拦截器的
afterCompletion方法:- 无论请求处理是否成功(即无论是否发生异常,只要对应的
preHandle返回了true),在视图渲染完成或响应体写入完成后,DispatcherServlet都会按照HandlerExecutionChain中定义的逆序,依次调用所有拦截器的afterCompletion(request, response, handler, ex)方法。 - 这个方法通常用于资源清理工作(例如释放某些资源)。参数
ex会包含处理过程中发生的异常(如果没有异常则为null)。
- 无论请求处理是否成功(即无论是否发生异常,只要对应的
-
异常处理:
- 如果在上述任何步骤(除了拦截器的
preHandle返回 false 之后)发生了异常,DispatcherServlet会捕获这个异常。 - 它会查询所有配置的
HandlerExceptionResolver实现,找到能够处理该异常的 Resolver。 HandlerExceptionResolver会处理异常,可能会将用户导向一个错误页面,或者返回一个包含错误信息的特定响应(例如 JSON 格式的错误信息)。
- 如果在上述任何步骤(除了拦截器的
-
响应返回给客户端:
DispatcherServlet将最终的 HTTP 响应(包含状态码、头信息、响应体)通过 Web 容器返回给客户端。
这个流程虽然看起来复杂,但每个组件职责清晰,使得 Spring MVC 框架非常灵活和可扩展。DispatcherServlet 在其中扮演了至关重要的中央协调者角色。
