• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
思想人生从关注生活开始
博客园    首页    新随笔    联系   管理    订阅  订阅

Spring MVC 请求全链路源码解析:基于 DispatcherServlet 的责任链执行模型

Spring MVC 作为 Spring 生态中处理 Web 请求的核心模块,其设计精妙地融合了“前端控制器”与“责任链”模式,将 HTTP 请求的处理过程拆解为高度标准化、可插拔的执行链路。理解这一链路,不仅能精准定位 404、500、响应格式异常等“诡异问题”,更是掌握 Spring Web 开发底层原理的关键一步。

本文将从 源码实现角度,完整拆解一个 HTTP 请求从进入容器到返回响应的 8 个核心阶段,深入剖析 DispatcherServlet 如何协调各组件完成请求分发,并揭示常见陷阱背后的真正原因。

一、核心组件与整体架构

在深入链路前,先明确 Spring MVC 的六大核心组件及其职责:

组件职责典型实现
DispatcherServlet 前端控制器,所有请求的统一入口 org.springframework.web.servlet.DispatcherServlet
HandlerMapping 将请求 URI 映射到具体的 Controller 方法 RequestMappingHandlerMapping
HandlerAdapter 适配并执行目标 Handler(如带 @RequestMapping 的方法) RequestMappingHandlerAdapter
HandlerInterceptor 拦截请求生命周期,实现前置/后置/收尾逻辑 自定义实现 HandlerInterceptor 接口
ViewResolver 将逻辑视图名解析为具体视图对象(如 JSP) InternalResourceViewResolver
HttpMessageConverter 序列化/反序列化请求体与响应体(如 JSON/XML) MappingJackson2HttpMessageConverter

注意:自 Spring 5.3 起,HandlerInterceptorAdapter 已被废弃,推荐直接实现 HandlerInterceptor 接口。

这些组件通过 DispatcherServlet 有机串联,形成一条清晰的责任链:

请求 → 路由 → 拦截 → 执行 → 拦截 → 渲染 → 收尾

二、请求执行全链路(8 阶段源码级拆解)

以下以一个标准 GET 请求为例,完整还原其在 Spring MVC 中的生命周期。

阶段 1:请求入口 —— DispatcherServlet.doDispatch()

所有请求首先进入 DispatcherServlet 的 doDispatch() 方法,这是整个处理流程的总控中心:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // ... 初始化变量
    try {
        processedRequest = checkMultipart(request); // 处理文件上传
        mappedHandler = getHandler(processedRequest); // 阶段2:路由匹配
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response); // 返回404
            return;
        }

        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        
        // 执行拦截器 preHandle(阶段3)
        if (!mappedHandler.applyPreHandle(processedRequest, response)) return;

        // 执行 Controller 方法(阶段4)
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        applyDefaultViewName(processedRequest, mv);
        mappedHandler.applyPostHandle(processedRequest, response, mv); // 阶段5

    } catch (Exception ex) {
        dispatchException = ex;
    }

    // 处理结果:渲染视图 / 异常处理(阶段6)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
} finally {
    // 清理资源 + 异步收尾(阶段8)
    if (multipartRequestParsed) cleanupMultipart(processedRequest);
}

此方法是理解整个链路的“钥匙”。

阶段 2:路由匹配 —— HandlerMapping.getHandler()

getHandler() 遍历所有 HandlerMapping(默认优先级:RequestMappingHandlerMapping 最高),尝试匹配请求:

public final HandlerExecutionChain getHandler(HttpServletRequest request) {
    Object handler = getHandlerInternal(request); // 核心匹配逻辑
    if (handler == null) return null;

    // 构建包含拦截器的执行链
    HandlerExecutionChain chain = getHandlerExecutionChain(handler, request);
    
    // 处理 CORS(跨域预检)
    if (CorsUtils.isPreFlightRequest(request)) {
        return null; // 预检请求不继续后续流程
    }
    return chain;
}

RequestMappingHandlerMapping.getHandlerInternal() 会:

  • 去除上下文路径(如 /app/api/user → /api/user)
  • 匹配 @RequestMapping 的 path、method、headers、produces 等条件
  • 封装为 HandlerMethod(含类、方法、参数、注解等元数据)

若无匹配项,getHandler() 返回 null,触发 noHandlerFound() → HTTP 404

阶段 3:拦截器前置 —— preHandle()

HandlerExecutionChain.applyPreHandle() 正序执行所有拦截器的 preHandle():

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
    for (int i = 0; i < interceptors.size(); i++) {
        if (!interceptors.get(i).preHandle(request, response, handler)) {
            triggerAfterCompletion(request, response, null); // 提前收尾
            return false;
        }
        interceptorIndex = i; // 记录已成功执行的拦截器索引
    }
    return true;
}
  • 返回 true:继续链路
  • 返回 false:立即终止,仅执行已成功拦截器的 afterCompletion()

典型用途:权限校验、日志埋点、防重放攻击

阶段 4:方法执行 —— HandlerAdapter.handle()

RequestMappingHandlerAdapter 是处理 @RequestMapping 方法的核心适配器:

protected ModelAndView handleInternal(...) {
    checkRequest(request); // 校验 HTTP 方法、Session 等
    mav = invokeHandlerMethod(request, response, handlerMethod); // 核心
    prepareResponse(response); // 设置缓存头等
    return mav;
}

invokeHandlerMethod() 内部完成三大任务:

  1. 参数解析
    通过 HandlerMethodArgumentResolver 解析:

    • @RequestParam → RequestParamMethodArgumentResolver
    • @RequestBody → RequestResponseBodyMethodProcessor
    • @PathVariable → PathVariableMethodArgumentResolver
  2. 方法调用
    利用反射执行目标 Controller 方法

  3. 返回值处理
    通过 HandlerMethodReturnValueHandler 分支处理:

    • 若方法/类标注 @ResponseBody → 使用 HttpMessageConverter 序列化为 JSON,直接写入 response 输出流
    • 否则 → 封装为 ModelAndView,等待视图渲染

此阶段抛出异常,将进入 HandlerExceptionResolver 异常处理链

阶段 5:拦截器后置 —— postHandle()

Controller 成功执行后,逆序调用 postHandle():

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) {
    for (int i = interceptors.size() - 1; i >= 0; i--) {
        interceptors.get(i).postHandle(request, response, handler, mv);
    }
}
  • 可修改 ModelAndView(如添加全局字段)
  • 异常或异步场景下不会执行

阶段 6:响应处理 —— processDispatchResult()

这是决定最终响应形态的关键分叉点:

private void processDispatchResult(..., ModelAndView mv, Exception ex) {
    if (ex != null) {
        mv = processHandlerException(request, response, handler, ex); // 异常处理
    }

    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response); // 视图渲染
    }

    triggerAfterCompletion(...); // 阶段8
}

两种响应路径:

路径触发条件处理方式
REST API(JSON) 方法/类有 @ResponseBody HttpMessageConverter 直接写入响应体,mv == null 或无视图名
页面渲染(HTML) 返回 String / ModelAndView ViewResolver 解析视图 → forward 到 JSP/Thymeleaf 模板

经典坑点:浏览器默认 Accept: text/html,若接口未指定 produces = "application/json",可能误走视图渲染路径 → 404(因无对应页面)

阶段 7:内容协商 —— ContentNegotiationManager

内容协商决定“返回什么格式”,其优先级为:

@RequestMapping(produces = "...") > Accept Header > 默认配置

ProducesRequestCondition.match() 会校验客户端 Accept 是否兼容接口声明的 produces。

解决方案:

  • 接口级:
    @GetMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE)
    
  • 全局级(推荐):
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.defaultContentType(MediaType.APPLICATION_JSON)
                      .ignoreAcceptHeader(false);
        }
    }

阶段 8:拦截器收尾 —— afterCompletion()

无论成功或失败,最终都会逆序执行 afterCompletion():

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) {
    for (int i = interceptorIndex; i >= 0; i--) {
        try {
            interceptors.get(i).afterCompletion(request, response, handler, ex);
        } catch (Throwable t) {
            logger.error("afterCompletion threw exception", t);
        }
    }
}
  • 用于资源清理(如关闭连接、释放锁)
  • 参数 ex 可判断是否发生异常

执行顺序总结:

  • preHandle:正序(先注册先执行)
  • postHandle / afterCompletion:逆序(先注册后执行)→ 符合“栈”式责任链

三、关键扩展点与实战问题解析

1. 为什么浏览器访问 REST 接口返回 404?

  • 根本原因:内容协商选择 text/html → 触发视图解析 → 无对应页面 → 404
  • 解决方案:强制 produces = "application/json" 或配置全局默认 Content-Type

2. 拦截器执行顺序为何“前正后逆”?

  • 设计哲学:类似“函数调用栈”——先进入的拦截器,最后退出
  • 保证资源释放顺序与申请顺序相反(如 A 获取锁 → B 获取锁 → B 释放 → A 释放)

3. 异常处理链优先级

Spring 默认按以下顺序尝试处理异常:

  1. ExceptionHandlerExceptionResolver(@ExceptionHandler)
  2. ResponseStatusExceptionResolver(@ResponseStatus)
  3. DefaultHandlerExceptionResolver(处理 400/405/415 等标准错误)

可通过 @Order 或自定义 HandlerExceptionResolver 插入更高优先级处理器。

四、执行链路全景图

66b1f1b599deebc2f796f7e374404ce3

五、结语:从“会用”到“精通”的跨越

Spring MVC 的请求处理链路,是一套高度内聚、低耦合、可扩展的经典设计范式。它不仅是一个 Web 框架,更是一本“如何构建可维护系统”的教科书。

掌握这条链路,意味着你能:

  • 精准定位问题:快速判断异常发生在路由、参数绑定、还是响应渲染阶段;
  • 灵活定制行为:通过拦截器、消息转换器、异常处理器等扩展点,打造专属 Web 能力;
  • 规避隐性陷阱:不再被“内容协商”“视图解析”等非业务逻辑困扰。

真正的框架高手,不是记住 API,而是理解请求的“一生”。
当你能在脑海中完整复现 doDispatch() 的每一步,Spring MVC 便不再是“黑盒”,而成为你手中的利器。

💡 建议实践:在 IDE 中打断点跟踪 DispatcherServlet.doDispatch(),配合本文阅读,效果翻倍。

posted @ 2026-03-19 15:52  JackYang  阅读(0)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3