请求分发:深入 DispatcherServlet 与响应式 DispatcherHandler
前置知识
- ›第 3 篇:ApplicationContext 刷新 —— DispatcherServlet 在 onRefresh() 期间完成初始化
- ›对 Servlet API 和响应式流有基本了解
请求分发:深入 DispatcherServlet 与响应式 DispatcherHandler
Spring Framework 的 Web 层存在于两个平行世界:DispatcherServlet 负责传统的阻塞式 I/O,DispatcherHandler 负责响应式非阻塞 I/O。两者都以相同的策略架构实现了前端控制器模式——HandlerMapping、HandlerAdapter 以及结果处理——但具体实现却大相径庭。DispatcherServlet 有 1400 余行命令式代码,而 DispatcherHandler 用约 220 行就实现了相同的概念流程。这种差距深刻揭示了响应式编程如何重新分配系统复杂度。
spring-web:共享的 HTTP 抽象层
在深入研究两个分发器之前,有必要先了解模块结构。正如第 1 篇所梳理的,spring-webmvc 和 spring-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-webmvc 将 spring-aop、spring-context 和 spring-expression 作为 API 依赖引入,并依赖 jakarta.servlet-api;而 spring-webflux 将 reactor-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 都支持两种编程模型:
- 注解式 ——
@Controller+@RequestMapping,由RequestMappingHandlerMapping+RequestMappingHandlerAdapter处理 - 函数式 ——
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 扫描,并解析 RuntimeHints、BeanRegistrationAotProcessor 以及全新的 BeanRegistrar API 是如何让这一切成为可能的。