本节目标:把
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);
}
}
}
七段职责可以对照成一张表:
| 段 | 调用 | 作用 | 出问题的典型现象 |
|---|---|---|---|
| ① | checkMultipart | multipart/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:
RequestMappingHandlerMapping.afterPropertiesSet()在启动时扫描所有@Controllerbean,对每个方法调用getMappingForMethod(method, handlerType),把@RequestMapping的path/method/params/consumes/produces等信息封装成RequestMappingInfo,登记进内部的MappingRegistry。- 请求进来时,
AbstractHandlerMethodMapping.getHandlerInternal()调用lookupHandlerMethod(lookupPath, request),用lookupPath去MappingRegistry里做匹配。 - 匹配到候选后,若存在「同样具体」的两条映射,会抛
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 | 用在哪 |
|---|---|---|
RequestMappingHandlerAdapter | HandlerMethod | 注解式控制器(绝对主流) |
SimpleControllerHandlerAdapter | Controller 接口实现 | 老式 MVC |
HttpRequestHandlerAdapter | HttpRequestHandler | 静态资源、转发 |
HandlerFunctionAdapter | HandlerFunction | 函数式端点 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())大意是:
- 单一类型优先:
ModelAndViewMethodReturnValueHandler、ModelMethodProcessor、ViewMethodReturnValueHandler、ResponseBodyEmitterReturnValueHandler、StreamingResponseBodyReturnValueHandler、ResponseEntityReturnValueHandler、HttpHeadersReturnValueHandler、CallableMethodReturnValueHandler、DeferredResultMethodReturnValueHandler。 - 注解驱动:
ServletModelAttributeMethodProcessor(false)(处理@ModelAttribute)、RequestResponseBodyMethodProcessor(处理@ResponseBody)。 - 通用兜底:
ViewNameMethodReturnValueHandler(返回String当视图名)、MapMethodProcessor。 - 最终兜底:
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.x | 4.x |
|---|---|---|
| MVC 自动配置模块 | spring-boot-autoconfigure 内 | 独立模块 spring-boot-webmvc |
DispatcherServlet 自动配置 | o.s.b.autoconfigure.web.servlet.DispatcherServletAutoConfiguration | o.s.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration |
| MVC 自动配置 | o.s.b.autoconfigure.web.servlet.WebMvcAutoConfiguration | o.s.boot.webmvc.autoconfigure.WebMvcAutoConfiguration |
| MVC 属性 | ...web.servlet.WebMvcProperties | o.s.boot.webmvc.autoconfigure.WebMvcProperties |
| MVC 注册扩展点 | ...web.servlet.WebMvcRegistrations | o.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 参数解析与消息转换 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。