Read OSS

请求分发:深入 DispatcherServlet 与响应式 DispatcherHandler

高级

前置知识

  • 第 3 篇:ApplicationContext 刷新 —— DispatcherServlet 在 onRefresh() 期间完成初始化
  • 对 Servlet API 和响应式流有基本了解

请求分发:深入 DispatcherServlet 与响应式 DispatcherHandler

Spring Framework 的 Web 层存在于两个平行世界:DispatcherServlet 负责传统的阻塞式 I/O,DispatcherHandler 负责响应式非阻塞 I/O。两者都以相同的策略架构实现了前端控制器模式——HandlerMappingHandlerAdapter 以及结果处理——但具体实现却大相径庭。DispatcherServlet 有 1400 余行命令式代码,而 DispatcherHandler 用约 220 行就实现了相同的概念流程。这种差距深刻揭示了响应式编程如何重新分配系统复杂度。

spring-web:共享的 HTTP 抽象层

在深入研究两个分发器之前,有必要先了解模块结构。正如第 1 篇所梳理的,spring-webmvcspring-webflux 都依赖于 spring-web——这个共享模块提供了通用的 HTTP 抽象。

spring-web/spring-web.gradle#L1-L9

spring-webmvc/spring-webmvc.gradle#L6-L13

spring-webflux/spring-webflux.gradle#L6-L10

flowchart TD
    subgraph "spring-web (shared)"
        HC["HttpMessageConverter"]
        RC["RestClient"]
        HA["HTTP abstractions"]
    end
    
    subgraph "spring-webmvc"
        DS["DispatcherServlet"]
        SA["Servlet API"]
        SE["spring-expression"]
        SA2["spring-aop"]
    end
    
    subgraph "spring-webflux"
        DH["DispatcherHandler"]
        R["reactor-core"]
    end
    
    DS --> HC
    DS --> SA
    DS --> SE
    DS --> SA2
    DH --> HC
    DH --> R

两者依赖关系的核心差异在于:spring-webmvcspring-aopspring-contextspring-expression 作为 API 依赖引入,并依赖 jakarta.servlet-api;而 spring-webfluxreactor-core 作为 API 依赖,并将 spring-context 保持为可选依赖。WebFlux 刻意保持轻量——它甚至可以在没有完整 application context 的情况下运行。

DispatcherServlet.doDispatch():MVC 请求流程

Spring MVC 的核心是 DispatcherServlet.doDispatch()——每一个 HTTP 请求都由这个方法来处理:

spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java#L935-L1004

sequenceDiagram
    participant Client
    participant DS as DispatcherServlet
    participant HM as HandlerMapping
    participant HEC as HandlerExecutionChain
    participant HA as HandlerAdapter
    participant Handler as @Controller Method
    participant VR as ViewResolver

    Client->>DS: HTTP Request
    DS->>DS: checkMultipart(request)
    
    DS->>HM: getHandler(request)
    HM-->>DS: HandlerExecutionChain<br/>(handler + interceptors)
    
    DS->>HEC: applyPreHandle(request, response)
    Note right of HEC: Each interceptor's preHandle()<br/>Can reject the request
    
    DS->>HA: getHandlerAdapter(handler)
    DS->>HA: handle(request, response, handler)
    HA->>Handler: invoke @RequestMapping method
    Handler-->>HA: Return value
    HA-->>DS: ModelAndView
    
    DS->>HEC: applyPostHandle(request, response, mv)
    
    DS->>DS: processDispatchResult()
    DS->>VR: resolveViewName(viewName)
    VR-->>DS: View
    DS->>DS: view.render(model, request, response)
    
    DS-->>Client: HTTP Response

让我们逐步梳理代码中的关键环节:

第 951 行 —— Handler 查找getHandler(processedRequest) 遍历所有已注册的 HandlerMapping bean,逐一询问它们是否能匹配当前请求。第一个返回非空结果的优先胜出。RequestMappingHandlerMapping 负责解析 @RequestMapping/@GetMapping 注解。

第 957 行 —— 拦截器预处理HandlerExecutionChain 将 handler 与一组 HandlerInterceptor 实例绑定在一起。applyPreHandle() 依次调用每个拦截器的 preHandle() 方法,任何一个返回 false 都会立即拒绝该请求。

第 962–963 行 —— Handler 适配getHandlerAdapter() 为当前 handler 类型找到合适的适配器。RequestMappingHandlerAdapter 负责处理 @Controller 方法——它解析方法参数(@RequestBody@PathVariable 等),调用方法,并将返回值处理为 ModelAndView

第 970 行 —— 拦截器后处理:handler 成功执行后,applyPostHandle() 给拦截器一个在视图渲染前修改 ModelAndView 的机会。

第 980 行 —— 结果处理processDispatchResult() 同时处理正常流程(视图渲染)和异常情况(委托给 HandlerExceptionResolver)。

值得注意的是大量的异常处理逻辑——第 982–988 行的外层 try-catch 确保 triggerAfterCompletion() 一定会被调用以执行清理,第 989–1003 行的 finally 块则处理异步请求场景。这种防御性编码是必要的,因为 DispatcherServlet 处于框架代码与用户代码的交界处,任何地方都可能出错。

DispatcherHandler:响应式的镜像

再来看响应式的对应实现:

spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java#L72-L151

整个 handle() 方法是一条完整的响应式 pipeline:

public Mono<Void> handle(ServerWebExchange exchange) {
    if (this.handlerMappings == null) {
        return createNotFoundError();
    }
    if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
        return handlePreFlight(exchange);
    }
    return Flux.fromIterable(this.handlerMappings)
            .concatMap(mapping -> mapping.getHandler(exchange))
            .next()
            .switchIfEmpty(createNotFoundError())
            .onErrorResume(ex -> handleResultMono(exchange, Mono.error(ex)))
            .flatMap(handler -> handleRequestWith(exchange, handler));
}
sequenceDiagram
    participant Client
    participant DH as DispatcherHandler
    participant HM as HandlerMapping (Flux)
    participant HA as HandlerAdapter
    participant HRH as HandlerResultHandler

    Client->>DH: ServerWebExchange
    
    DH->>HM: Flux.fromIterable(handlerMappings)<br/>.concatMap(mapping.getHandler())
    HM-->>DH: Mono~Handler~
    
    DH->>DH: .next() — take first match
    DH->>DH: .switchIfEmpty(404 error)
    
    DH->>HA: handleRequestWith(exchange, handler)
    Note right of HA: adapter.handle() returns<br/>Mono~HandlerResult~
    HA-->>DH: Mono~HandlerResult~
    
    DH->>HRH: handleResult(exchange, result)
    HRH-->>DH: Mono~Void~
    
    DH-->>Client: Response written reactively

代码量的差距并非意味着 DispatcherHandler 做的事情更少。它完成的是完全相同的工作——handler 查找、适配、结果处理、错误处理。差异在于方式

关注点 DispatcherServlet DispatcherHandler
Handler 查找 for 循环 + null 检查 Flux.concatMap().next()
错误处理 嵌套 try-catch .onErrorResume()
异步支持 WebAsyncManager + 特殊分支 Mono/Flux 原生支持
拦截器 显式调用 applyPreHandle()/applyPostHandle() WebFilter 链(独立于分发器)
视图渲染 内联的 processDispatchResult() 委托给 HandlerResultHandler

响应式编程将错误处理、异步行为和组合逻辑内化到 pipeline 操作符本身。DispatcherHandler 不需要 try-catch,因为错误以信号的形式在响应式流中传播;也不需要 WebAsyncManager,因为所有操作本来就是非阻塞的。

提示: MVC 与 WebFlux 的拦截器机制存在显著差异。MVC 使用 HandlerInterceptor,提供 preHandle/postHandle/afterCompletion 回调;WebFlux 则使用 WebFilter——这是一种更通用的过滤器链,在分发器找到 handler 之前就已执行。从 MVC 迁移到 WebFlux 时,拦截器需要重写为 filter。

无处不在的策略模式:可插拔的组件

两个分发器都通过策略模式完成配置。在初始化阶段(MVC 对应 refresh() 第 9 步的 onRefresh(),WebFlux 对应 setApplicationContext()),它们从 application context 中发现策略 bean:

spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java#L115-L134

classDiagram
    class DispatcherServlet {
        -handlerMappings: List~HandlerMapping~
        -handlerAdapters: List~HandlerAdapter~
        -handlerExceptionResolvers: List~HandlerExceptionResolver~
        -viewResolvers: List~ViewResolver~
    }
    
    class HandlerMapping {
        <<interface>>
        +getHandler(request) HandlerExecutionChain
    }
    
    class HandlerAdapter {
        <<interface>>
        +supports(handler) boolean
        +handle(request, response, handler) ModelAndView
    }
    
    class RequestMappingHandlerMapping {
        Resolves @RequestMapping
    }
    
    class RequestMappingHandlerAdapter {
        Invokes @Controller methods
    }
    
    class RouterFunctionMapping {
        Resolves RouterFunction routes
    }
    
    class HandlerFunctionAdapter {
        Invokes functional handlers
    }

    DispatcherServlet --> HandlerMapping
    DispatcherServlet --> HandlerAdapter
    HandlerMapping <|.. RequestMappingHandlerMapping
    HandlerMapping <|.. RouterFunctionMapping
    HandlerAdapter <|.. RequestMappingHandlerAdapter
    HandlerAdapter <|.. HandlerFunctionAdapter

这种基于策略的设计使整个分发机制具备了可插拔性。你可以添加自定义的 HandlerMapping 实现来实现不同的请求匹配方式(例如基于请求头而非路径进行路由),也可以添加自定义的 HandlerAdapter 实现来支持新的 handler 类型。

MVC 和 WebFlux 都支持两种编程模型:

  1. 注解式 —— @Controller + @RequestMapping,由 RequestMappingHandlerMapping + RequestMappingHandlerAdapter 处理
  2. 函数式 —— RouterFunction + HandlerFunction,由 RouterFunctionMapping + HandlerFunctionAdapter 处理

函数式模型完全绕过了注解处理,效率略高,且对 AOT 完全友好(无需反射来发现路由)。

初始化桥梁:onRefresh() 与 setApplicationContext()

两个分发器是如何与第 3 篇中的 refresh 生命周期建立连接的?

DispatcherServlet 覆写了 onRefresh()——即 refresh 序列的第 9 步。由于 DispatcherServlet 继承自 FrameworkServlet,而 FrameworkServlet 又继承自 HttpServletBean,Web application context 在启动时会调用 onRefresh(),进而触发 initStrategies() 从 context 中发现所有的 handler mapping、adapter、view resolver 和 exception resolver。

DispatcherHandler 的路径更为简洁:它实现了 ApplicationContextAware 接口,context 在 bean 初始化期间会调用 setApplicationContext(),从而触发 initStrategies()

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
    initStrategies(applicationContext);
}

DispatcherHandler 第 115–134 行的 initStrategies() 方法清晰直接——它只是按类型查找 bean,按 @Order 排序,然后存储为不可变列表。没有兜底默认值,没有 XML 配置支持,也没有 DispatcherServlet.properties 文件。WebFlux 是从零开始、遵循现代约定构建的。

下一步

至此,我们已经完整覆盖了 Spring MVC 和 WebFlux 的请求生命周期。在最后一篇文章中,我们将探讨 Spring 最新也最具前瞻性的子系统:Ahead-of-Time 编译。我们将追踪 AOT 引擎如何将我们一路探索的这个充满动态性、大量依赖反射的框架,转化为适合 GraalVM native image 的静态生成代码——用构建时代码生成取代运行时的 classpath 扫描,并解析 RuntimeHintsBeanRegistrationAotProcessor 以及全新的 BeanRegistrar API 是如何让这一切成为可能的。