《Spring Boot 高级》4.1 DispatcherServlet 处理链

把一次请求进入 DispatcherServlet.doDispatch() 后的调用链逐段拆开:getHandler 如何遍历 HandlerMapping、HandlerAdapter 为什么要多一层适配、HandlerExecutionChain 怎么挂上拦截器、返回值如何经 HandlerMethodReturnValueHandler 落地,并标出 4.x 模块化后的新包路径。

本节目标:把 DispatcherServlet.doDispatch() 内部的七段调用链讲透,理解 HandlerMapping / HandlerAdapter / HandlerExecutionChain 三者各管一段的原因,并把断点与扩展点定位到具体方法。
适用版本:Spring Boot 4.1.x(Java 21)

4.1 DispatcherServlet 处理链

实战卷解决的是「怎么配」——@RestController 怎么映射路径、@RequestBody 怎么接 JSON、拦截器怎么注册。本节解决「为什么这样配」:一个 HTTP 请求被 Tomcat 交给 Spring 之后,框架内部到底按什么顺序、经过哪些对象、每一步把控制权交给谁。本章三节统一围绕一个图书借阅服务 LibraryController 演进,它暴露 GET /books/{isbn}、POST /books 与 GET /books/search 三个端点,外加一个审计拦截器。

排障时最常遇到的两类问题——「接口 404 但映射看着没问题」「拦截器没进 preHandle」——答案全在这条链路上。把 doDispatch() 拆开,你就能一眼看出请求是在哪一步「掉」的。

4.1.1 doDispatch() 的七段结构

DispatcherServlet 继承自 HttpServlet,请求最终落到它覆写的 doService(),再由 doService() 调用 doDispatch()。下面这段是 Spring Framework 7.0.9 的 doDispatch() 主干(已从本机 spring-webmvc-7.0.9.jar 反编译核实):

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            processedRequest = checkMultipart(request);          // ① 文件上传预处理
            multipartRequestParsed = (processedRequest != request);

            mappedHandler = getHandler(processedRequest);        // ② 找 handler + 拦截器链
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);      //    404 走这里
                return;
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;                                          // ③ 任一 preHandle 返回 false 即中断
            }

            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // ④ 选适配器
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // ⑤ 真正调用

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;                                          //    异步已启动,交还容器
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv); // ⑥ postHandle
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new ServletException("Handler dispatch failed: " + err, err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); // ⑦
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    finally {
        if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
            cleanupMultipart(processedRequest);
        }
    }
}

七段职责可以对照成一张表:

段调用作用出问题的典型现象
①checkMultipartmultipart/form-data 时把请求包装成 MultipartHttpServletRequest上传接口拿不到 MultipartFile
②getHandler遍历 HandlerMapping 找 handler 并组装拦截器链404(mappedHandler == null)
③applyPreHandle正序执行所有 preHandle拦截器没生效
④getHandlerAdapter找能处理该 handler 的适配器No adapter for handler
⑤ha.handle参数解析 + 方法调用 + 返回值处理400 / 415 / 500
⑥applyPostHandle逆序执行所有 postHandle拿不到 ModelAndView
⑦processDispatchResult渲染视图或走异常解析异常没被 @ExceptionHandler 捕获

注意第 ③ 段返回 false 时 doDispatch() 直接 return——既不进 ⑤,也不进 ⑥,更不触发 ⑦ 的 afterCompletion。这是「preHandle 返回 false 后请求被静默截断」的根源,也是很多人以为拦截器「没生效」的原因:其实它生效了,只是主动拦住了。

4.1.2 HandlerMapping 如何按 @RequestMapping 找到 handler

getHandler 本身极短,它只是遍历容器里注册的 HandlerMapping,谁先返回非空就用谁:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

这些 HandlerMapping 的来源在 initHandlerMappings() 里分两步:先从容器里捞出所有 HandlerMapping 类型的 bean 并按 AnnotationAwareOrderComparator 排序;只有当容器里一个都没有时,才回退到 DispatcherServlet.properties 里那三个默认实现(BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping)。Spring Boot 应用走的是第一步——这些 bean 由 WebMvcConfigurationSupport 注册,所以属性文件里的默认值通常不会用到。

三个默认实现分别对应「bean 名当 URL 的老式控制器」「注解式控制器」「函数式端点」,注解控制器日常走的是 RequestMappingHandlerMapping。

真正按 @RequestMapping 匹配的实现在 AbstractHandlerMethodMapping:

  1. RequestMappingHandlerMapping.afterPropertiesSet() 在启动时扫描所有 @Controller bean,对每个方法调用 getMappingForMethod(method, handlerType),把 @RequestMapping 的 path/method/params/consumes/produces 等信息封装成 RequestMappingInfo,登记进内部的 MappingRegistry。
  2. 请求进来时,AbstractHandlerMethodMapping.getHandlerInternal() 调用 lookupHandlerMethod(lookupPath, request),用 lookupPath 去 MappingRegistry 里做匹配。
  3. 匹配到候选后,若存在「同样具体」的两条映射,会抛 IllegalStateException(Ambiguous mapping);否则把最佳匹配的 RequestMappingInfo 交给 handleMatch(),最终返回一个 HandlerMethod 对象——它封装了 bean 实例、方法、参数元数据。

HandlerMethod 是理解后续所有环节的钥匙:它不是 Object,而是「哪个 bean 的哪个方法」,参数解析、返回值处理、异常解析都围绕它展开。

4.1.3 HandlerAdapter 为什么必须多一层

拿到 HandlerMethod 之后,doDispatch() 没有直接反射调用,而是先 getHandlerAdapter(handler):

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler + "]");
}

这一层适配是 Spring MVC 最经典的设计决策。DispatcherServlet 只依赖两个接口:

public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

Object handler 意味着 handler 可以是任意类型——注解方法、老式 Controller 实现、HttpRequestHandler、函数式端点,甚至一个普通 Servlet。每种 handler 配一个适配器即可,DispatcherServlet 完全不用改:

适配器支持的 handler用在哪
RequestMappingHandlerAdapterHandlerMethod注解式控制器(绝对主流)
SimpleControllerHandlerAdapterController 接口实现老式 MVC
HttpRequestHandlerAdapterHttpRequestHandler静态资源、转发
HandlerFunctionAdapterHandlerFunction函数式端点 RouterFunction

RequestMappingHandlerAdapter 自己不直接实现 HandlerAdapter,而是继承 AbstractHandlerMethodAdapter,由后者实现 supports()(判断 handler instanceof HandlerMethod)与 handle()(把 handler 强转成 HandlerMethod 后转调 handleInternal())。这就是为什么 getHandlerAdapter 里那句 adapter.supports(handler) 能精确选中它。

4.1.4 HandlerExecutionChain 与拦截器的挂载时机

HandlerExecutionChain 是「handler + 拦截器列表」的组合体。拦截器不是请求时才去容器里查的,而是在 HandlerMapping.getHandler() 内部就挂好了。AbstractHandlerMapping.getHandler() 的收尾是:

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

而 getHandlerExecutionChain() 遍历该 mapping 配置的 adaptedInterceptors:非 MappedInterceptor 的直接 chain.addInterceptor(),MappedInterceptor 则要 matches(request) 命中路径模式才加。MappedInterceptor 正是 WebMvcConfigurer.addInterceptors() 注册拦截器时用的包装——所以 addPathPatterns() 的路径过滤发生在组装阶段,而不是执行阶段。

链上的三个执行入口(HandlerExecutionChain 的方法,包级可见,由 DispatcherServlet 调用):

  • applyPreHandle(request, response):正序执行,任一返回 false 就停止并立即触发已执行过的拦截器的 afterCompletion,返回 false。
  • applyPostHandle(request, response, mv):逆序执行,仅在 handler 正常返回后调用。
  • triggerAfterCompletion(request, response, ex):逆序执行,无论成功失败都调用(除非第 ③ 段就 return 了)。

拦截器接口 HandlerInterceptor 的三个方法在 7.0.9 里都是 default 方法(不必全实现):

default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;

注意 preHandle 的第三个参数就是 Object handler(实际是 HandlerMethod),所以拦截器里可以 instanceof HandlerMethod 拿到目标方法上的注解——这是写审计拦截器的常用手法。

4.1.5 返回值经 HandlerMethodReturnValueHandler 落地

ha.handle(...) 内部并不直接写响应。以 RequestMappingHandlerAdapter 为例,它把 HandlerMethod 包成 ServletInvocableHandlerMethod,再调 invokeAndHandle():

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);
    // ...(空返回值 / @ResponseStatus 短路判断)
    this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

returnValueHandlers 是一个 HandlerMethodReturnValueHandlerComposite,它持有一串处理器,按顺序找第一个 supportsReturnType() 为真的来执行。默认顺序(来自 7.0.9 的 getDefaultReturnValueHandlers())大意是:

  1. 单一类型优先:ModelAndViewMethodReturnValueHandler、ModelMethodProcessor、ViewMethodReturnValueHandler、ResponseBodyEmitterReturnValueHandler、StreamingResponseBodyReturnValueHandler、ResponseEntityReturnValueHandler、HttpHeadersReturnValueHandler、CallableMethodReturnValueHandler、DeferredResultMethodReturnValueHandler。
  2. 注解驱动:ServletModelAttributeMethodProcessor(false)(处理 @ModelAttribute)、RequestResponseBodyMethodProcessor(处理 @ResponseBody)。
  3. 通用兜底:ViewNameMethodReturnValueHandler(返回 String 当视图名)、MapMethodProcessor。
  4. 最终兜底:ServletModelAttributeMethodProcessor(true)。

这解释了一个常见困惑:为什么 @RestController 方法返回 String 会写成 JSON 文本而不是去找视图?因为 RequestResponseBodyMethodProcessor.supportsReturnType() 检查到 @ResponseBody(@RestController 自带)就为真,排在 ViewNameMethodReturnValueHandler 之前。

4.1.6 4.x 的包路径变化

写 4.x 时不能照抄 3.x 的包名。DispatcherServlet、HandlerMapping、HandlerAdapter、HandlerExecutionChain、HandlerInterceptor 这些 Spring Framework 的类包路径没变,仍是 org.springframework.web.servlet.*(Framework 7.0.9)。变的是 Spring Boot 的自动配置:

内容3.x4.x
MVC 自动配置模块spring-boot-autoconfigure 内独立模块 spring-boot-webmvc
DispatcherServlet 自动配置o.s.b.autoconfigure.web.servlet.DispatcherServletAutoConfigurationo.s.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration
MVC 自动配置o.s.b.autoconfigure.web.servlet.WebMvcAutoConfigurationo.s.boot.webmvc.autoconfigure.WebMvcAutoConfiguration
MVC 属性...web.servlet.WebMvcPropertieso.s.boot.webmvc.autoconfigure.WebMvcProperties
MVC 注册扩展点...web.servlet.WebMvcRegistrationso.s.boot.webmvc.autoconfigure.WebMvcRegistrations

(上述 4.x 类名已用 unzip -l spring-boot-webmvc-4.1.1.jar 核实存在。)另外 starter 名也从 spring-boot-starter-web 改为 spring-boot-starter-webmvc,旧名仍可解析但已废弃。

4.1.7 知道之后能做什么

排障:确认 handler 到底有没有被匹配。 开启 logging.level.org.springframework.web=DEBUG,AbstractHandlerMapping.getHandler() 会打印 Mapped to ...;若这行不出现,说明请求根本没进 handler 匹配(可能是路径不对、spring.mvc.servlet.path 变了,或静态资源处理器先截了)。

排障:看清请求停在哪一段。 在 DispatcherServlet.doDispatch() 的第 ②③④⑤ 行下断点,或用 IDEA 的「Evaluate」看 mappedHandler、mappedHandler.getInterceptorList() 是否为空。mappedHandler == null 就是 404,getInterceptorList() 为空就是拦截器没挂上。

扩展:换掉默认的 handler mapping/adapter。 注册一个 WebMvcRegistrations bean,覆写 getRequestMappingHandlerMapping() 或 getRequestMappingHandlerAdapter() 返回自定义子类,即可在框架装配前插入自己的逻辑(例如给所有 mapping 加统一前缀)。WebMvcAutoConfiguration 内部会优先取这个 bean(7.0.9 源码已确认)。

验证:观察映射表。 引入 spring-boot-starter-actuator 后访问 /actuator/mappings,能看到所有 RequestMappingHandlerMapping 登记的 RequestMappingInfo 与对应的 HandlerMethod——这是排查「映射到底注册没注册」最快的办法。注意该端点在 4.x 下由 spring-boot-webmvc 的 DispatcherServletsMappingDescriptionProvider 提供。

小结

  • doDispatch() 是七段结构:多部件预处理 → 找 handler → preHandle → 选适配器 → 调用 → postHandle → 结果处理;preHandle 返回 false 会短路掉 ⑤⑥⑦。
  • HandlerMapping 把 @RequestMapping 编译成 RequestMappingInfo 存进注册表,请求时用 lookupPath 匹配出 HandlerMethod。
  • HandlerAdapter 是「让 DispatcherServlet 只认 Object handler」的适配层,注解控制器由 RequestMappingHandlerAdapter 承担。
  • 拦截器在 HandlerMapping 组装 HandlerExecutionChain 时挂载,MappedInterceptor 的路径过滤也在那时完成。
  • 返回值由 HandlerMethodReturnValueHandler 链按顺序处理,@ResponseBody 命中 RequestResponseBodyMethodProcessor,排在视图名处理器之前。

handler 找到了、方法也调到了,但「参数怎么从 HTTP 变成方法入参」还没讲——这正是下一节的主题。

阅读导航:上一节:3.3 代理失效的边界 · 下一节:4.2 参数解析与消息转换 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计