Spring MVC 是建立在 Servlet API 之上的请求驱动型 Web 框架,其核心设计围绕 前端控制器模式(Front Controller) 展开。所有进入应用的 HTTP 请求都由一个统一的入口——DispatcherServlet 接管,它负责协调各个专用组件完成请求解析、处理器调用、视图渲染等步骤。理解整个处理流程,是掌握 Spring MVC 开发与排错的关键。
12.1.1 Spring MVC 架构概览
在整个 MVC 体系中,组件各司其职,DispatcherServlet 作为总调度,既不亲自处理业务,也不直接生成响应,而是委托给一系列接口完成。典型架构如图所示:
HTTP 请求 → DispatcherServlet → HandlerMapping → Controller (Handler)
↓
HandlerAdapter
↓
拦截器 pre/post
↓
ViewResolver ← ModelAndView
↓
View 渲染
↓
HTTP 响应
- HandlerMapping:根据请求 URL、方法、参数等信息匹配具体的处理器(Handler),通常是控制器中的方法。
- HandlerAdapter:实际调用处理器,因为处理器可以多种多样(
@Controller、HttpRequestHandler、Servlet 等),适配器统一了调用方式。 - HandlerInterceptor:拦截器链,可以在处理器前后执行通用逻辑(如登录检查、日志记录)。
- ViewResolver:根据逻辑视图名解析为具体的视图技术(JSP、Thymeleaf、JSON 等)。
- View:负责将模型数据渲染到响应中。
这些组件通过 IoC 容器装配在一起,Spring Boot 的自动配置会提供一套默认实现,开发时只需关注控制器本身。
12.1.2 DispatcherServlet 的初始化与核心方法
DispatcherServlet 本质上是一个 Servlet,在初始化阶段 (init()) 会完成 Spring 上下文的加载以及策略组件的初始化。其处理入口为 service() 方法,最终流转到 doDispatch()。
doDispatch() 是所有请求处理的核心,代码精简后的关键步骤如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 检查是否为文件上传请求,如果是则封装为 MultipartHttpServletRequest
HttpServletRequest processedRequest = checkMultipart(request);
// 2. 根据请求查找 HandlerExecutionChain(处理器 + 拦截器链)
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3. 为当前 Handler 查找对应的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. 执行拦截器的 preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; // 拦截器返回 false,请求终止
}
// 5. 通过适配器真正调用处理器,返回 ModelAndView
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 6. 如果存在默认视图名则应用
applyDefaultViewName(processedRequest, mv);
// 7. 执行拦截器的 postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 8. 处理结果(解析视图、渲染)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
这个流程清晰地揭示了请求从进入到离开的完整生命周期,下面逐一展开各组件的工作细节。
12.1.3 HandlerMapping:请求匹配到处理器
HandlerMapping 的核心任务是返回一个 HandlerExecutionChain 对象,它包含:
- 处理器本身(Handler),例如我们定义的
@Controller中的某个方法。 - 适用于该请求的所有拦截器(
HandlerInterceptor)。
Spring MVC 内置了多种 HandlerMapping 实现:
- RequestMappingHandlerMapping:最常用的一种,处理
@Controller和@RequestMapping注解。它在容器启动时扫描所有 Bean 中的@RequestMapping注解,建立请求路径到处理方法的映射关系。 - BeanNameUrlHandlerMapping:将 URL 与 Bean 的名称直接绑定,例如
/hello对应名为/hello的 Bean。 - SimpleUrlHandlerMapping:允许通过 XML 或 Properties 明确指定 URL 到处理器的映射,常用于静态资源或老式配置。
以 RequestMappingHandlerMapping 为例,当请求 /orders/123 到达时,它会根据 URL 模式、请求方法(GET/POST)、参数条件等匹配到 OrderController.getOrder(@PathVariable Long id) 方法。
如果有多个 HandlerMapping 共存,DispatcherServlet 会按顺序遍历,返回第一个非空的匹配结果。Spring Boot 默认仅注册 RequestMappingHandlerMapping,足以覆盖绝大多数场景。
12.1.4 HandlerAdapter:适配多种处理器类型
找到处理器之后,需要使用合适的适配器去执行。为什么需要适配器?因为处理器的形态并不统一:
@Controller注解类的方法,需要参数解析、返回值处理(@ResponseBody等)。- 实现了
HttpRequestHandler接口的 Bean,直接操作HttpServletRequest/HttpServletResponse。 - 实现了
Servlet接口的 Bean(较少用)。
HandlerAdapter 接口定义了三个核心方法:
supports(Object handler):判断是否支持该处理器。handle(request, response, handler):实际调用处理器。getLastModified(request, handler):处理Last-Modified头,支持缓存控制。
最核心的适配器是 RequestMappingHandlerAdapter,它与 RequestMappingHandlerMapping 配套,专门处理 @RequestMapping 方法。在处理时会完成一系列关键操作:
- 参数解析:通过
HandlerMethodArgumentResolver解析控制器方法参数,例如将@RequestParam String name绑定到请求参数、将@RequestBody Order order解析为 JSON 对象、将HttpSession直接注入等。 - 数据绑定与校验:若参数标注了
@Valid或@Validated,触发 JSR-303 校验。 - 返回值处理:通过
HandlerMethodReturnValueHandler处理不同的返回值,如返回字符串代表视图名、@ResponseBody转为 JSON、ResponseEntity可自定义头与状态码、Callable/DeferredResult实现异步处理。
这种适配器模式使得扩展新的处理器类型变得非常容易,也无需改动 DispatcherServlet 主体流程。
12.1.5 HandlerInterceptor:拦截器链
HandlerInterceptor 提供了三个钩子方法,可以在处理器前后植入横切逻辑:
preHandle(request, response, handler):处理器执行前调用,返回false则终止整个请求,常用于权限校验、登录检查。postHandle(request, response, handler, modelAndView):处理器执行后、视图渲染前调用,可对ModelAndView进行修改(如追加公共数据)。afterCompletion(request, response, handler, ex):请求完成(视图渲染结束)后调用,适合资源清理、日志记录,即使出现异常也会执行。
在 doDispatch 流程中,applyPreHandle 会依次执行所有拦截器的 preHandle;若任一返回 false,则后续拦截器不再执行,直接结束请求。业务方法正常返回后,applyPostHandle 逆序执行 postHandle。最终在 afterCompletion 阶段,无论成功或异常,都会执行对应的清理逻辑。
使用拦截器而不是直接在控制器中写通用逻辑,可以保持代码整洁和可复用:
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (request.getSession().getAttribute("user") == null) {
response.setStatus(401);
return false;
}
return true;
}
}
然后在配置类中注册拦截器并指定拦截路径:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/public/**");
}
}
12.1.6 处理结果与异常解析
控制器返回的 ModelAndView 并非直接渲染,而是要经过视图解析。ViewResolver 根据逻辑视图名(如 "orderDetail")匹配真实视图。常见的 ViewResolver 有:
- InternalResourceViewResolver:针对 JSP,将逻辑名转换为
/WEB-INF/views/下的.jsp路径。 - ThymeleafViewResolver:Thymeleaf 模板引擎,解析逻辑名为 Thymeleaf 模板文件。
- BeanNameViewResolver:将视图名作为 Bean 名称查找。
- ContentNegotiatingViewResolver:根据请求的
Accept头或文件扩展名选择最合适的视图(如 HTML、JSON、XML)。
对于 REST 接口,场景稍有不同。通常使用 @ResponseBody 或 @RestController,此时处理器返回的不是视图名,而是直接由 HttpMessageConverter 将返回值序列化为 JSON/XML 写入响应体。在这种情况下,ModelAndView 为 null,processDispatchResult 直接认为请求已完成。
如果在处理过程中抛出异常,DispatcherServlet 会遍历注册的 HandlerExceptionResolver:
- ExceptionHandlerExceptionResolver:处理
@ExceptionHandler注解的方法。 - ResponseStatusExceptionResolver:处理
@ResponseStatus注解的异常,返回对应 HTTP 状态码。 - DefaultHandlerExceptionResolver:处理 Spring MVC 内部异常,转换为标准状态码。
开发者最常用的是在控制器内或 @ControllerAdvice 全局类中定义 @ExceptionHandler 方法,统一处理特定异常并返回友好的错误响应。
12.1.7 带着流程图走一遍真实请求
用一个具体场景串联整个流程:用户请求 GET /orders/1001,期望返回该订单的 JSON 数据。
- 请求到达
DispatcherServlet,触发doDispatch。 RequestMappingHandlerMapping匹配到OrderController.getOrder(Long id)方法,返回HandlerExecutionChain(含处理器和拦截器列表)。- 执行拦截器
preHandle,例如校验 Token,通过后继续。 - 通过
HandlerMethodArgumentResolver解析{id}路径变量,将1001注入参数。 - 调用
OrderController.getOrder(1001)方法,内部从数据库查询订单,转为订单 DTO 返回。 - 因为方法标注了
@ResponseBody,HandlerMethodReturnValueHandler使用MappingJackson2HttpMessageConverter将 DTO 序列化为 JSON,写入响应体。 - 拦截器
postHandle执行(此时ModelAndView为 null)。 processDispatchResult检测到ModelAndView为空,认为处理已完成。- 拦截器
afterCompletion执行,完成日志记录。 - 浏览器收到 200 状态码与 JSON 响应。
整个流程分工明确,每个组件只关注自身的职责,这正是 Spring MVC 高扩展性的基础。在实际开发中,掌握这个处理链对于调试请求映射失败、拦截器失效、异常处理不生效等问题至关重要。