一个 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
在其中扮演了至关重要的中央协调者角色。