概述
本文是 Spring 知识体系 Web 与响应式表现层系列的第 2 篇。 前文《Spring MVC 启动全景》详细拆解了 DispatcherServlet 的初始化过程、父子容器的创建以及九大策略组件的预热。本文将在这些组件准备就绪的基础上,追踪一个 HTTP 请求从进入 doDispatch() 开始,到最终响应返回客户端的完整内部旅程。通过深入 HandlerMapping、HandlerAdapter、ViewResolver 三大核心策略的匹配与调用逻辑,揭示 Spring MVC 如何通过策略模式与适配器模式,实现对不同类型 Handler 和视图技术的统一调度。
如果说 DispatcherServlet 是 Spring MVC 的大脑,那么 HandlerMapping、HandlerAdapter 和 ViewResolver 就是它的三头六臂。一个 HTTP 请求经过 Filter 链到达 DispatcherServlet 后,首先由 HandlerMapping 根据 URL、请求方法等条件定位到具体的 Controller 方法;然后由 HandlerAdapter 负责调用该方法,并处理复杂的参数绑定与返回值解析;最后由 ViewResolver 将逻辑视图名转换为具体的视图对象,完成响应渲染。本文将沿 doDispatch 方法这条主线,深入剖析每个环节的核心源码,让读者透过 Spring MVC 的表层魔幻,看清其内部精密的调度机制。
核心要点
- doDispatch 骨架:DispatcherServlet 将请求处理的完整生命周期封装在一个模板方法中。
- HandlerMapping 映射:
RequestMappingHandlerMapping在启动时预先建立 URL 模式与 Controller 方法的映射表,运行时快速匹配。 - HandlerAdapter 适配:通过适配器模式,让 DispatcherServlet 可以统一调用不同类型的 Handler(@Controller、HttpRequestHandler、Servlet 等)。
- ViewResolver 解析:通过责任链模式,多个 ViewResolver 依次尝试解析视图名,直至成功。
- 拦截器与异常处理:AOP 思想在 Spring MVC 中的完美复刻——HandlerInterceptor 提供前置、后置、完成后的钩子,其执行时机在异常时具有特殊性;HandlerExceptionResolver 提供统一的异常处理。
文章组织架构图
flowchart TD
subgraph 全链路请求处理
direction TB
1[1. doDispatch 总览<br/>请求处理骨架]
2[2. HandlerMapping<br/>请求到处理器的映射]
3[3. HandlerAdapter<br/>处理器的统一适配与调用]
4[4. HandlerInterceptor<br/>拦截器链的执行]
5[5. ViewResolver<br/>视图解析与内容协商]
6[6. HandlerExceptionResolver<br/>统一异常处理]
7[7. 全链路时序图<br/>一个 Request 的完整旅程]
8[8. 生产事故排查专题]
9[9. 面试高频专题]
end
1 --> 2 --> 3 --> 4 --> 5 --> 6
2 --> 7
3 --> 7
4 --> 7
5 --> 7
6 --> 7
7 --> 8 --> 9
架构图说明
- 总览说明:全文 9 个模块从
doDispatch骨架出发,依次深入映射、适配、拦截、视图、异常五大处理环节,最后通过全链路时序图、事故排查和面试专题完成知识闭环。架构图以纵向递进和横向串联的方式展示了请求处理的完整生命周期。 - 逐模块说明:模块 1 展示了
doDispatch方法的模板模式设计,是整个请求处理的总控室。模块 2 和 3 分别体现了策略模式和适配器模式在 Handler 定位与调用中的精妙运用。模块 4 揭示了 AOP 思想在 MVC 层的拦截增强。模块 5 和 6 则展示了责任链模式在视图解析和异常处理中的实现。模块 7 将所有组件串联,提供全局视角。模块 8 和 9 将理论落地到工程实践和面试准备。 - 关键结论:Spring MVC 的请求处理是策略模式、适配器模式、责任链模式的集大成应用,理解
doDispatch方法是掌握 Spring MVC 的关键。 它将看似复杂的请求处理分解为一系列可插拔的策略组件,通过模板方法将固定流程与可变策略解耦,体现了框架设计的最高水准。
1. doDispatch 总览:DispatcherServlet 的请求处理骨架
DispatcherServlet.doDispatch() 方法是 Spring MVC 请求处理的核心,它实现了一套模板方法模式,定义了请求处理的骨架流程,而将具体的 Handler 映射、适配调用、视图解析等步骤委派给相应的策略组件。我们先直接呈现其核心源码骨架,再逐一拆解。
// org.springframework.web.servlet.DispatcherServlet
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 {
// 1. 检查并处理文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 2. 通过 HandlerMapping 策略定位处理器执行链
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3. 通过 HandlerAdapter 策略查找支持该处理器的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理 GET/HEAD 请求的 Last-Modified 缓存
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 4. 执行拦截器前置钩子 (preHandle)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 5. 适配器实际调用处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 若需要异步处理,则返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 6. 若 ModelAndView 不为 null 且没有 View 名称,设置默认视图名
applyDefaultViewName(processedRequest, mv);
// 7. 执行拦截器后置钩子 (postHandle)
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 8. 处理分发结果:异常解析与视图渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
// 9. 触发拦截器的 afterCompletion (异常最终出口)
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
// 清理文件上传上下文
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
源码解读:
- 整个方法是一个巨大的模板,定义了请求分发的标准步骤:检查文件上传 →
getHandler映射 →getHandlerAdapter适配 →applyPreHandle拦截前置 →ha.handle调用 →applyPostHandle拦截后置 →processDispatchResult结果处理(含异常分解与视图渲染)。 - 注意到
processDispatchResult的调用位于内层 try 之后,这意味着无论处理器是否抛出异常,都会进入该方法进行统一的异常处理和视图解析。异常变量dispatchException作为参数传入,如果非空,processDispatchResult内部会将其转交给HandlerExceptionResolver链处理。 mappedHandler.applyPreHandle返回false时直接返回,意味着当前拦截器阻断了请求,后续的处理器调用、拦截器后置以及processDispatchResult都不会执行。但注意:afterCompletion仍然会在finally或triggerAfterCompletion处得到调用(当applyPreHandle返回 false 时,DispatcherServlet会调用triggerAfterCompletion)。- 模板方法模式在这里体现得淋漓尽致:流程骨架固定,但每一步的具体实现都委托给可插拔的策略组件。
1.1 doDispatch 全链路泳道图
flowchart TD
subgraph DispatcherServlet
A[收到 HTTP 请求] --> B[doDispatch 开始]
end
B --> C{multipart?}
C -->|是| D[checkMultipart 包装请求]
C -->|否| E[保持原请求]
D --> F[getHandler: 遍历 HandlerMapping 链]
E --> F
F --> G{HandlerExecutionChain 存在?}
G -->|否| H[noHandlerFound 返回404]
G -->|是| I[getHandlerAdapter: 遍历 HandlerAdapter 链]
I --> J{Adapter 支持?}
J -->|否| K[抛出 ServletException]
J -->|是| L[applyPreHandle: 执行拦截器 preHandle]
L --> M{preHandle 返回 true?}
M -->|false| N[triggerAfterCompletion 并返回]
M -->|true| O[Adapter.handle 调用处理器]
O --> P[applyPostHandle: 执行拦截器 postHandle]
P --> Q[processDispatchResult]
Q --> R{有异常?}
R -->|是| S[HandlerExceptionResolver 链]
R -->|否| T[ViewResolver 链视图渲染]
S --> T
T --> U[triggerAfterCompletion: 执行 afterCompletion]
U --> V[响应返回客户端]
N --> U
H --> U
K --> U
style N fill:#f96,stroke:#333
style U fill:#9f6,stroke:#333
- 图表主旨概括:该泳道图以
doDispatch方法为骨架,完整展示了一个请求从进入到最终返回的所有分支路径,重点标出了异常和拦截器阻断时afterCompletion依然执行的场景。 - 逐层/逐元素分解:流程依次分为文件上传检查、HandlerMapping 链查找、HandlerAdapter 适配、前置拦截、处理器调用、后置拦截、结果处理(包含异常解析和视图渲染)、最终拦截器完成回调。每个节点对应
doDispatch源码中的一个方法调用。 - 设计原理映射:
getHandler体现了策略模式(遍历 HandlerMapping 列表找到匹配的策略),getHandlerAdapter则是适配器模式的查找过程(遍历 Adapter 列表找到支持当前 Handler 的适配器),applyPreHandle/applyPostHandle/afterCompletion是 AOP 环绕通知的变体。整个doDispatch方法是模板方法模式的体现。 - 工程联系与关键结论:理解
doDispatch的执行顺序是诊断 Spring MVC 请求问题的前提。例如拦截器postHandle未执行但afterCompletion执行了,往往暗示处理器抛出了异常。 掌握了这个骨架,就能快速定位请求链路中的问题组件。
2. HandlerMapping:请求到处理器的映射
HandlerMapping 的策略是:根据传入的 HttpServletRequest 返回一个包含 Handler 和拦截器列表的 HandlerExecutionChain。其接口定义极为简洁:
public interface HandlerMapping {
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
Spring MVC 内置了多个实现,它们通过不同的策略将 URL 映射到处理器:
RequestMappingHandlerMapping:最核心的实现,基于@RequestMapping注解,将 URL 模式、HTTP 方法、参数、请求头等条件映射到HandlerMethod(包装了 Controller 方法)。BeanNameUrlHandlerMapping:将 URL 直接映射到与 URL 同名的 Bean(例如/myController映射到名为/myController的 Bean)。SimpleUrlHandlerMapping:允许在配置文件中直接定义 URL 到 Bean 的映射集合。RouterFunctionMapping(Spring 5.x 函数式 Web):支持 RouterFunction 映射。
本文聚焦于 RequestMappingHandlerMapping,因为它承载了现代 Spring MVC 绝大部分的映射逻辑。
2.1 映射表的核心数据结构:MappingRegistry
AbstractHandlerMethodMapping 内部维护了一个 MappingRegistry,它是映射关系的核心存储,关键结构如下:
- urlLookup:
Map<String, List<T>>,根据请求路径快速定位匹配的模式列表。 - mappingLookup:
Map<T, MappingRegistration<T>>,根据映射条件对象(如RequestMappingInfo)定位注册信息。 - mappingRegistry 还包含
corsLookup、nameLookup等辅助查找。
2.2 启动阶段:映射建立的完整流程
RequestMappingHandlerMapping 实现了 InitializingBean 接口,在初始化阶段(afterPropertiesSet)触发 initHandlerMethods() 方法,扫描容器中所有的 Bean。我们通过一个序列图来展示这一过程。
sequenceDiagram
participant Container as Spring 容器
participant RHM as RequestMappingHandlerMapping
participant Registry as MappingRegistry
participant Handler as Controller Bean
Container->>RHM: afterPropertiesSet()
activate RHM
RHM->>RHM: initHandlerMethods()
RHM->>Container: getBeanNamesForType(Object.class)
Container-->>RHM: allBeanNames
loop 遍历所有 Bean 名称
RHM->>Container: getBean(beanName)
Container-->>RHM: handlerBean
alt Bean 类上或方法上标注 @Controller/@RequestMapping
RHM->>RHM: detectHandlerMethods(handlerBean)
loop 遍历 Bean 的所有方法
RHM->>RHM: 提取 @RequestMapping 信息<br/>构建 RequestMappingInfo
RHM->>RHM: 创建 HandlerMethod 对象
RHM->>Registry: register(mapping, handler, method)
Registry-->>RHM: 注册到 urlLookup/mappingLookup
end
else 不是处理器
RHM->>RHM: 跳过
end
end
deactivate RHM
- 图表主旨概括:该序列图展示了
RequestMappingHandlerMapping在应用启动时,通过InitializingBean钩子扫描所有 Spring Bean,识别带有@Controller和@RequestMapping的类和方法,并将其映射关系注册到MappingRegistry中的过程。 - 逐层/逐元素分解:
afterPropertiesSet作为 Bean 生命周期回调触发初始化;detectHandlerMethods利用反射提取注解元数据,构建HandlerMethod(封装了 Bean 实例和方法引用);最终注册到urlLookup和mappingLookup中,实现运行时 O(1) 或少次遍历的快速匹配。 - 设计原理映射:该过程是策略模式中“环境初始化策略”的体现,将复杂多变的映射规则在启动时一次性计算好,运行时直接匹配。同时利用了 Spring Bean 生命周期的扩展点(
InitializingBean)进行预热,类似前文提到的九大组件初始化。 - 工程联系与关键结论:由于所有映射信息都在启动阶段构建完成并发布到只读映射表,运行时无需再修改,因此自然不存在并发竞争;该过程通过
synchronized块保证初始化的可见性,之后对映射表的访问为只读多线程安全,这为高并发下的请求匹配提供了保障。
下面我们看关键的源码片段来印证初始化过程:
// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = obtainApplicationContext().getType(beanName);
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
} catch (Throwable ex) {
throw new IllegalStateException(...);
}
});
// 注册映射
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
解读:initHandlerMethods 通过 getCandidateBeanNames 获取所有 Bean,isHandler 检查是否标注 @Controller 或 @RequestMapping,detectHandlerMethods 使用反射提取方法注解,构建 RequestMappingInfo,最后调用 registerHandlerMethod 存入 MappingRegistry。整个过程在单线程启动中完成,且使用 synchronized 确保可见性。
2.3 运行时匹配逻辑
请求到来时,AbstractHandlerMethodMapping.lookupHandlerMethod 负责根据请求路径找到最匹配的 HandlerMethod。主要步骤如下:
- 根据请求路径从
urlLookup获取匹配的RequestMappingInfo列表。 - 遍历列表,使用
RequestMappingInfo.getMatchingCondition(request)进一步匹配 HTTP 方法、参数、请求头等。 - 如果匹配到多个,使用比较器(如路径模式深度)选出最佳匹配。
- 返回对应的
HandlerMethod,并包装进HandlerExecutionChain。
其他 HandlerMapping 如 BeanNameUrlHandlerMapping 则简单粗暴:直接将 URL 路径作为 Bean 名称从容器中获取,如果存在则作为 Handler 返回。
3. HandlerAdapter:处理器的统一适配与调用
HandlerMapping 返回的 Handler 对象类型是不固定的:可以是 HandlerMethod(@Controller 方法),可以是 HttpRequestHandler,可以是 Servlet,甚至是任何自定义对象。如何用一种统一的方式调用这些五花八门的处理器?HandlerAdapter 接口给出了答案:
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
supports判断当前适配器是否支持该 Handler 类型。handle执行处理器。
Spring MVC 内置了多个适配器:
RequestMappingHandlerAdapter:支持HandlerMethod(基于@RequestMapping注解的方法)。HttpRequestHandlerAdapter:支持HttpRequestHandler接口的实现。SimpleControllerHandlerAdapter:支持Controller接口(老式 Spring MVC 控制器)。SimpleServletHandlerAdapter:支持Servlet。
DispatcherServlet.getHandlerAdapter 遍历 handlerAdapters 列表,调用每个适配器的 supports,返回第一个支持的。我们深入 RequestMappingHandlerAdapter 看看它是如何执行处理器方法的。
3.1 RequestMappingHandlerAdapter 的核心执行链
sequenceDiagram
participant DS as DispatcherServlet
participant RHA as RequestMappingHandlerAdapter
participant HAC as WebDataBinderFactory<br/>ModelFactory<br/>HandlerMethodArgumentResolverComposite
participant HM as HandlerMethod<br/>(Controller 方法)
participant HR as HandlerMethodReturnValueHandlerComposite
participant V as View
DS->>RHA: handle(request, response, handler)
activate RHA
RHA->>RHA: handleInternal(request, response, handlerMethod)
RHA->>RHA: invokeHandlerMethod(request, response, handlerMethod)
RHA->>RHA: 注册 Model 属性设置
RHA->>RHA: 创建 ServletInvocableHandlerMethod
RHA->>HAC: 配置 ParameterResolver, ReturnValueHandler
RHA->>HM: invokeAndHandle(webRequest, mavContainer)
activate HM
HM->>HM: 参数解析 (ArgumentResolver)
HM->>HM: 反射调用 Controller 方法
HM-->>HM: 返回结果
HM->>HR: handleReturnValue(returnValue, ...)
HR-->>HM: 处理结果(写入 Response 或填充 Model)
HM-->>RHA: return ModelAndView or null
deactivate HM
RHA-->>DS: return ModelAndView
deactivate RHA
- 图表主旨概括:该序列图聚焦
RequestMappingHandlerAdapter如何将一个HandlerMethod适配为一个标准的handle调用,展示了从参数解析、方法调用到返回值处理的完整内部流程。 - 逐层/逐元素分解:
handleInternal完成了SessionAttributes和ModelFactory的准备工作;invokeHandlerMethod创建ServletInvocableHandlerMethod对象,并为其注入参数解析器(HandlerMethodArgumentResolverComposite)和返回值处理器(HandlerMethodReturnValueHandlerComposite);最终调用invokeAndHandle,反射执行 Controller 方法,并将返回值交给返回值处理器(如@ResponseBody则使用HttpMessageConverter写入响应体)。 - 设计原理映射:适配器模式的核心在于
supports和handle。DispatcherServlet不需要关心 Handler 的具体类型,只需调用一致化的接口。在RequestMappingHandlerAdapter内部,又应用了策略模式(参数解析器策略、返回值处理器策略),将变化点进一步隔离。 - 工程联系与关键结论:
HandlerAdapter的存在使得 Spring MVC 能够同时支持 @Controller 注解、HttpRequestHandler、老式 Controller 等多种处理器风格,这就是适配器模式带来的多态威力。拓展自定义协议处理器时,只需实现 Handler 和对应的 Adapter 即可无缝集成。
3.2 源码:从 handle 到 invokeAndHandle
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 检查是否已存在 SessionAttributes 要求
// ...
// 实际调用
mav = invokeHandlerMethod(request, response, handlerMethod);
// 处理 Cache-Control 头部等
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 创建数据绑定工厂、Model工厂等
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 设置参数解析器(策略组合)
invocableMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers());
// 设置返回值处理器(策略组合)
invocableMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers());
// ... 设置异步支持等
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 调用目标方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
解读:invokeHandlerMethod 方法构建了执行所需的全部上下文,将参数解析器和返回值处理器注入 ServletInvocableHandlerMethod,这两个组合对象内部封装了多种策略,将在参数解析篇和返回值处理篇详述。最后的 invokeAndHandle 是整个调用链的终点,它利用反射调用目标方法并处理返回值。
3.3 内联示例:自定义 HandlerAdapter
为了体验适配器模式,我们创建一个自定义的 Handler(即一个普通的 POJO)和对应的 Adapter。
// 自定义处理器
public class MyCustomHandler {
public String handleRequest(String name) {
return "Hello, " + name;
}
}
// 自定义适配器
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyCustomHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof MyCustomHandler;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MyCustomHandler customHandler = (MyCustomHandler) handler;
String name = request.getParameter("name");
String result = customHandler.handleRequest(name);
response.getWriter().write(result);
return null; // 表示已直接处理响应
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
配置 HandlerMapping 和 Adapter 到 Spring 容器,当一个请求匹配到 MyCustomHandler 时,DispatcherServlet 就会通过 MyCustomHandlerAdapter 来调用它,全程无需修改 doDispatch 代码,体现了开闭原则。
4. HandlerInterceptor:拦截器链的执行
HandlerInterceptor 提供了三个切入点:
preHandle:处理器执行前执行。返回true放行,false拦截。postHandle:处理器执行后、视图渲染前执行(当处理器抛出异常时不执行)。afterCompletion:整个请求完成后执行(无论是否发生异常,只要preHandle返回了true就会执行)。
拦截器链封装在 HandlerExecutionChain 中,我们直击其执行逻辑的源码:
// org.springframework.web.servlet.HandlerExecutionChain
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable ex2) {
logger.error(...);
}
}
}
源码解读:
applyPreHandle顺序执行拦截器,一旦某个preHandle返回false,立即调用triggerAfterCompletion(仅对已执行preHandle成功的拦截器调用afterCompletion)并停止后续流程。applyPostHandle逆序执行所有拦截器的postHandle,但在doDispatch中,这段代码被放置在ha.handle()之后,如果处理器抛出异常,异常会在内层 catch 被捕获,applyPostHandle不会被执行。triggerAfterCompletion总是对外已执行过preHandle的拦截器调用afterCompletion,这是在最终出口处保证资源清理。在doDispatch中,当处理器抛出异常时,processDispatchResult内部的triggerAfterCompletion依然会被调用。
4.1 拦截器链执行层次图(异常路径标注)
flowchart TD
A[请求] --> B[preHandle 1]
B -->|true| C[preHandle 2]
B -->|false| F[triggerAfterCompletion 仅含1]
C -->|true| D[preHandle 3]
C -->|false| G[triggerAfterCompletion 1,2]
D -->|true| E[处理器执行]
D -->|false| H[triggerAfterCompletion 1,2,3]
E --> I{处理器成功?}
I -->|成功| J[逆序 postHandle 3,2,1]
I -->|异常| K[跳过 postHandle<br/>直接进入异常处理]
J --> L[视图渲染]
K --> L
L --> M[逆序 afterCompletion 3,2,1]
F --> M
G --> M
H --> M
style K fill:#ff6b6b,stroke:#333
style J fill:#77dd77,stroke:#333
- 图表主旨概括:该流程图清晰展示了多个拦截器在
preHandle、postHandle及afterCompletion阶段的执行顺序与条件,特别强调了异常时postHandle被跳过而afterCompletion必然执行的差异。 - 逐层/逐元素分解:
preHandle顺序执行(1→2→3),postHandle逆序执行(3→2→1),afterCompletion也是逆序,但仅对preHandle返回true的拦截器执行。流程图中用红色和绿色区分异常与成功路径。 - 设计原理映射:拦截器链是责任链模式与 AOP 思想的结合。每个拦截器是责任链上的一个节点,
preHandle控制是否继续传递;postHandle和afterCompletion则提供了横切关注点的织入时机。 - 工程联系与关键结论:许多开发者误以为
postHandle和afterCompletion都会被调用,但实际上当处理器抛出异常时,postHandle不会执行,此时所有资源清理、日志记录必须放在afterCompletion中,否则可能导致连接泄漏或不准确的监控数据。 这一特性在压测和故障排查中尤为重要。
4.2 内联示例:验证拦截器异常时的行为
public class TimingInterceptor implements HandlerInterceptor {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
startTime.set(System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
long duration = System.currentTimeMillis() - startTime.get();
System.out.println("postHandle: 请求耗时 " + duration + " ms");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
long duration = System.currentTimeMillis() - startTime.get();
System.out.println("afterCompletion: 请求耗时 " + duration + " ms");
if (ex != null) {
System.out.println("发生了异常: " + ex.getMessage());
}
startTime.remove();
}
}
写一个会抛出异常和正常的 Controller,观察输出即可验证 postHandle 在异常时缺失,而 afterCompletion 始终执行。
4.3 与 Servlet Filter 的对比
- Filter:由 Servlet 容器管理,可拦截所有请求(包括静态资源),基于
doFilter链式调用,可以修改请求和响应对象。 - Interceptor:由 Spring MVC 管理,只能拦截经过
DispatcherServlet的请求,能访问 Handler 对象和ModelAndView,与 Spring 容器紧密集成,可以注入其他 Bean。
5. ViewResolver:视图解析与内容协商
当处理器返回一个逻辑视图名(如 "userList")或 ModelAndView 之后,DispatcherServlet 将调用 ViewResolver 链将其解析为具体的 View 对象并渲染。接口如下:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
解析过程采用的是责任链模式:多个 ViewResolver 按 Order 顺序依次尝试解析,直到某个返回非空的 View。
5.1 内容协商:ContentNegotiatingViewResolver
该解析器不直接解析视图,而是根据请求的 Accept 头或请求参数(如 ?format=json),将所有请求委托给其它 ViewResolver 解析出的候选视图列表进行内容协商,选择最匹配客户端期望媒体类型的 View(例如 JSON 视图、XML 视图)。其内部维护了一份 viewResolvers 列表,形成嵌套的责任链。
5.2 InternalResourceViewResolver 与 JSP 转发
InternalResourceViewResolver 将逻辑视图名解析为 InternalResourceView(一般用于 JSP),通过 RequestDispatcher.forward 进行服务器端转发,其默认的 UrlBasedViewResolver 机制会为视图名添加前后缀,如 /WEB-INF/views/ 和 .jsp。
5.3 ViewResolver 责任链序列图(含 Order 错误演示)
sequenceDiagram
participant DS as DispatcherServlet
participant CNVR as ContentNegotiatingViewResolver (Order=1)
participant TVR as ThymeleafViewResolver (Order=2)
participant IRVR as InternalResourceViewResolver (Order=3)
participant View
DS->>CNVR: resolveViewName("userList", locale)
CNVR->>CNVR: 确定请求媒体类型 (Accept: text/html)
CNVR->>TVR: resolveViewName("userList", locale) [委托]
TVR-->>CNVR: View (Thymeleaf)
CNVR->>DS: return ThymeleafView
DS->>View: render
Note over DS,IRVR: 错误配置:IRVR Order=1, TVR Order=2
DS->>IRVR: resolveViewName("userList", locale)
IRVR->>IRVR: 构建 InternalResourceView (存在)
IRVR-->>DS: return InternalResourceView
DS->>View: render (InternalResourceView)
View->>IRVR: forward /WEB-INF/views/userList.jsp
IRVR-->>View: 若 JSP 不存在或依赖循环 → StackOverflow
- 图表主旨概括:该序列图展示了两个场景:正常情况下的内容协商视图解析链,以及
Order设置错误导致InternalResourceViewResolver提前解析并可能引发死循环的异常场景。 - 逐层/逐元素分解:
ContentNegotiatingViewResolver作为门面,根据Accept头或参数决定“最佳”视图;它通常委托给其它具体 ViewResolver。当InternalResourceViewResolver的 Order 被提前,它可能会强行将任何逻辑视图名解析为 JSP,即使该视图不存在或需要转发到另一个路径,频繁的 forward 可能导致无限循环。 - 设计原理映射:责任链模式在这里表现为 ViewResolver 列表的顺序处理,Order 值决定了它们在链中的位置。Spring 通过
Ordered接口和@Order注解统一管理组件的优先级。 - 工程联系与关键结论:生产环境中,多个 ViewResolver 共存时必须明确指定 Order,尤其要警惕
InternalResourceViewResolver,其默认 Order 较低,若将其提升至最前,可能导致所有请求都试图转发到 JSP,引发视图解析死循环或 StackOverflow 错误。 正确的做法是让具体模板引擎的解析器优先,并设置viewNames限制InternalResourceViewResolver的解析范围。
5.4 源码:ViewResolver 责任链的调用
// org.springframework.web.servlet.DispatcherServlet
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
解读:顺序遍历 viewResolvers 列表,调用 resolveViewName,第一个非空 View 即被采用。这里的顺序是由 Order 决定的,DispatcherServlet 在初始化时会对策略列表排序。
6. HandlerExceptionResolver:统一异常处理
当处理器执行过程中抛出异常时,异常会被 doDispatch 捕获并传入 processDispatchResult 方法:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
// 特殊处理
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 视图渲染 ...
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
processHandlerException 遍历 HandlerExceptionResolver 列表(同样是责任链),依次调用 resolveException,直至某个返回非空的 ModelAndView。
6.1 三大核心异常解析器
- ExceptionHandlerExceptionResolver:扫描
@ControllerAdvice和@ExceptionHandler注解,匹配异常类型,通过反射调用异常处理方法。内部同样使用了参数解析和返回值处理,与RequestMappingHandlerAdapter类似。 - ResponseStatusExceptionResolver:处理带有
@ResponseStatus注解的异常,设置响应状态码和原因。 - DefaultHandlerExceptionResolver:处理 Spring MVC 内部的标准异常,如
NoHandlerFoundException(404)、HttpRequestMethodNotSupportedException(405) 等,转换为对应的 HTTP 状态码。
异常解析器链的执行顺序同样由 Order 控制,通常 ExceptionHandlerExceptionResolver 优先级最高,因为它需要精确匹配。
7. 全链路时序图:一个 Request 的完整旅程
sequenceDiagram
participant Client as 客户端
participant Filter as Servlet Filter
participant DS as DispatcherServlet
participant HM as HandlerMapping
participant HI as HandlerInterceptor
participant HA as HandlerAdapter
participant H as Controller
participant HER as HandlerExceptionResolver
participant VR as ViewResolver
participant V as View
Client->>Filter: HTTP Request
Filter->>DS: chain.doFilter()
activate DS
DS->>DS: doDispatch()
DS->>HM: getHandler(request)
HM-->>DS: HandlerExecutionChain
DS->>DS: getHandlerAdapter(handler)
DS->>HI: applyPreHandle()
HI-->>DS: true
DS->>HA: handle(request, response, handler)
activate HA
HA->>H: 反射调用方法
H-->>HA: 返回结果/异常
deactivate HA
alt 处理器正常返回
DS->>HI: applyPostHandle()
DS->>VR: resolveViewName(viewName)
VR-->>DS: View 对象
DS->>V: render(model, request, response)
V-->>Client: 响应体
else 处理器抛出异常
DS->>HI: (postHandle 被跳过)
DS->>HER: processHandlerException(request,response,handler,ex)
HER-->>DS: ModelAndView (异常视图)
DS->>VR: resolveViewName(异常视图名)
VR-->>DS: View
DS->>V: render()
V-->>Client: 错误响应
end
DS->>HI: triggerAfterCompletion(ex?)
deactivate DS
- 图表主旨概括:该时序图串联了从前端过滤器、
DispatcherServlet、映射、拦截器、适配器、处理器、异常解析器到视图解析器的完整请求生命周期,并清晰标出了正常与异常两大分支。 - 逐层/逐元素分解:请求经过 Filter 后进入
doDispatch,通过映射找到执行链,适配器调用处理器;如果处理器抛出异常,拦截器postHandle不执行,而是进入异常解析器链,最终渲染错误视图。无论哪条路径,afterCompletion总是最后执行。 - 设计原理映射:整条链路是模板方法模式(
doDispatch)与策略模式(HandlerMapping、HandlerAdapter、ViewResolver、ExceptionResolver)的协同工作。责任链则出现在拦截器链和各解析器链中。 - 工程联系与关键结论:拥有全链路视图有助于快速诊断问题:例如 404 错误可能发生在 HandlerMapping 阶段,415 错误发生在 Adapter 的消息转换阶段,异常页面展示异常则来自 HandlerExceptionResolver。理解每个阶段的责任边界是排错的基石。
8. 生产事故排查专题
8.1 事故一:请求返回 415 Unsupported Media Type
现象:前端 POST JSON 数据到 /api/user,后端返回 415 状态码,但 Controller 方法明确标注 @PostMapping 且参数有 @RequestBody。
排查思路:
- 检查请求头
Content-Type是否为application/json,若是,继续下一步。 - 查看
DispatcherServlet日志,发现异常发生在HandlerAdapter.handle内部。 RequestMappingHandlerAdapter调用invokeHandlerMethod,其中参数解析器RequestResponseBodyMethodProcessor尝试读取请求体,依赖HttpMessageConverter进行反序列化。- 发现项目中引入了自定义的
HttpMessageConverter,但没有注册MappingJackson2HttpMessageConverter,或者因为错误配置了WebMvcConfigurer.configureMessageConverters导致默认的 JSON 转换器被移除。 根因:RequestMappingHandlerAdapter在解析@RequestBody参数时,遍历消息转换器列表,找不到支持application/json的转换器,抛出HttpMediaTypeNotSupportedException,最终被DefaultHandlerExceptionResolver处理为 415 响应。 解决:确保MappingJackson2HttpMessageConverter存在于 Spring 容器,或者在扩展configureMessageConverters时不要替换整个列表,应使用extendMessageConverters。 最佳实践:在自定义消息转换器时,务必了解configureMessageConverters和extendMessageConverters的区别,前者会覆盖默认转换器列表,后者仅在默认列表后追加。
8.2 事故二:视图解析死循环导致 StackOverflow
现象:应用启动后,访问某个页面,应用无响应,最终抛出 java.lang.StackOverflowError,堆栈中反复出现 InternalResourceViewResolver 和 RequestDispatcher.forward。
排查思路:
- 分析堆栈,发现
InternalResourceView.render->request.getRequestDispatcher->forward,然后再次进入DispatcherServlet,再次解析视图,形成无限递归。 - 检查 Spring MVC 配置,发现配置了多个 ViewResolver:
ThymeleafViewResolver和InternalResourceViewResolver,但InternalResourceViewResolver的 Order 被设置为HIGHEST_PRECEDENCE。 - Thymeleaf 解析器未被使用,所有请求直接落到
InternalResourceViewResolver。 - 更致命的是,
InternalResourceViewResolver未配置viewNames限制,且逻辑视图名对应了一个存在的 JSP 文件,但这 JSP 内部又包含forward或重定向到另一个未被 Thymeleaf 处理的路径,导致循环。 根因:责任链顺序错误,InternalResourceViewResolver过早介入,且视图转发导致再次请求相同的DispatcherServlet,再次解析,形成无限循环。 解决:将ThymeleafViewResolver的 Order 设置得更小(优先级更高),并为InternalResourceViewResolver设置viewNames属性,如viewNames="jsp/*",限制其只处理特定名称的视图。 最佳实践:当并存多种视图技术时,必须通过Order明确优先级,并利用viewNames属性缩小特定解析器的适用范围,防止解析器跨职责解析引发转发循环。
9. 面试高频专题
-
描述一个 HTTP 请求到达 DispatcherServlet 后的完整处理流程。
- 标准回答:请求入口为
doDispatch,首先检查 multipart,然后通过 HandlerMapping 获取包含拦截器的执行链,接着找到合适的 HandlerAdapter 调用处理器,处理器返回 ModelAndView,执行拦截器后置,最后通过 ViewResolver 解析视图并渲染。异常时跳过 postHandle 但保证 afterCompletion。 - 追问1:如果未找到 HandlerMapping 返回了 null 会怎样?→
noHandlerFound方法触发,默认返回 404。可自定义配置throwExceptionIfNoHandlerFound来抛出异常。 - 追问2:HandlerMapping 如何与 HandlerInterceptor 组合?→
HandlerExecutionChain包含一个 Handler 和一个拦截器列表,映射时一同返回。 - 追问3:多个 HandlerMapping 顺序如何?→ 根据 Order 排序,
RequestMappingHandlerMapping默认 Order 为 0,最先尝试。 - 加分回答:可结合
ContentNegotiatingViewResolver说明内容协商如何影响视图选择。
- 标准回答:请求入口为
-
HandlerMapping 的作用是什么?Spring MVC 中有哪些常见的实现?
- 标准回答:负责根据请求找到处理器。常见的有
RequestMappingHandlerMapping(注解驱动)、BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping以及函数式 Web 的RouterFunctionMapping。 - 追问1:
RequestMappingHandlerMapping何时建立映射?→ 在InitializingBean.afterPropertiesSet阶段扫描容器。 - 追问2:如何自定义 HandlerMapping?→ 实现
HandlerMapping接口并注册。 - 追问3:如果两个请求匹配到同一个 Controller 方法,会发生什么?→ 正常执行,每次请求都会创建新的线程,方法会并发调用(除非有同步控制)。
- 加分回答:提及
MatchableHandlerMapping接口,可用于模式匹配。
- 标准回答:负责根据请求找到处理器。常见的有
-
HandlerAdapter 为什么要存在?直接用 HandlerMapping 返回 Handler 不行吗?
- 标准回答:因为 Handler 的类型多样,统一调用需要适配器模式。Adapter 让 DispatcherServlet 无需知道 Handler 的具体类型。
- 追问1:
HandlerAdapter如何扩展?→ 实现supports和handle。 - 追问2:
SimpleControllerHandlerAdapter适配了哪种 Handler?→ 适配实现了Controller接口的处理器。 - 追问3:如果没有适配器支持某 Handler 会怎样?→ 抛出
ServletException,提示 No adapter。 - 加分回答:结合 Spring 扩展点,可以设计出一个支持 RPC 调用的 Adapter。
-
HandlerInterceptor 和 Filter 的区别与联系。
- 标准回答:Filter 属于 Servlet 容器,可拦截所有请求;Interceptor 属于 Spring,只能拦截经过 DispatcherServlet 的请求,且能访问 Handler 和 ModelAndView。
- 追问1:Filter 能实现类似 preHandle 的功能吗?→ 可以,但需要手动传递状态。
- 追问2:为什么 postHandle 在异常时不执行?→ 源码设计,try-catch 包围了处理器调用,异常会跳到 catch 块。
- 追问3:如何保证 afterCompletion 一定执行?→ 放在 finally 或 triggerAfterCompletion 中调用。
- 加分回答:执行顺序上,Filter 包裹 Interceptor。
-
多个 ViewResolver 的解析顺序如何确定?若顺序不当会怎样?
- 标准回答:通过
Order接口确定,值越小越优先。顺序不当可能导致 Thymeleaf 解析器被跳过,InternalResourceViewResolver 强行解析导致 404 或死循环。 - 追问1:
ContentNegotiatingViewResolver内部如何委托?→ 它不直接解析,而是委托给其内部维护的 ViewResolver 列表。 - 追问2:如何让 JSP 解析器只处理部分视图?→ 设置
viewNames属性为正则。 - 追问3:视图解析器返回 null 后会怎样?→ 继续尝试下一个解析器。
- 加分回答:重定向视图
RedirectView不是通过 ViewResolver 解析,而是由 Controller 返回redirect:前缀。
- 标准回答:通过
-
Spring MVC 是如何处理异常的?有哪些方式?
- 标准回答:通过
HandlerExceptionResolver链处理。主要实现有ExceptionHandlerExceptionResolver(@ExceptionHandler)、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver。也可以实现HandlerExceptionResolver接口自定义。 - 追问1:
@ControllerAdvice全局异常处理优先级如何?→ 根据@Order和距离。 - 追问2:如果异常未被任何解析器处理会怎样?→ 异常会抛给容器,容器处理为 500。
- 追问3:异常解析后还能修改响应状态吗?→ 可以,解析器返回
ModelAndView后仍可设置。 - 加分回答:可以通过
ResponseEntityExceptionHandler来简化 REST 异常处理。
- 标准回答:通过
-
如何自定义一个 HandlerMapping?
- 标准回答:实现
HandlerMapping接口,在getHandler中自定义逻辑,返回HandlerExecutionChain。 - 追问1:需要注册到 Spring 吗?→ 只需将其作为一个 Bean,
DispatcherServlet会自动发现。 - 追问2:能动态改变映射吗?→ 可以,但需注意线程安全。
- 追问3:如何配合自定义 Adapter?→ 一起注册。
- 加分回答:可以实现
MatchableHandlerMapping支持模式匹配。
- 标准回答:实现
-
@RequestMapping 注解的方法是如何被扫描和注册的?
- 标准回答:在 Bean 初始化阶段,
RequestMappingHandlerMapping扫描所有 Bean,通过MethodIntrospector.selectMethods找出标注了@RequestMapping的方法,提取RequestMappingInfo,注册到MappingRegistry。 - 追问1:支持
@RequestMapping注解的注解(如@GetMapping)吗?→ 支持,它们被@RequestMapping元注解标注。 - 追问2:注册时如何处理多个匹配?→ 使用比较器选择最具体的匹配。
- 追问3:扫描过程中有同步控制吗?→ 启动时单线程,运行时映射只读。
- 加分回答:可以提到
RequestMappingInfo的构建过程和条件匹配。
- 标准回答:在 Bean 初始化阶段,
-
DispatcherServlet 的 doDispatch 方法中,如果没有找到对应的 HandlerAdapter 会怎样?
- 标准回答:会抛出
ServletException,提示No adapter for handler。 - 追问1:如何避免?→ 确保 HandlerMapping 返回的 Handler 有对应的 Adapter。
- 追问2:自定义 Handler 如何配上 Adapter?→ 实现
HandlerAdapter。 - 追问3:异常最终如何呈现?→ 被容器处理为 500。
- 加分回答:在设计上,
getHandlerAdapter方法会遍历所有 Adapter,任何一个supports返回 true 即选中。
- 标准回答:会抛出
-
ContentNegotiatingViewResolver 的工作机制。
- 标准回答:它根据
Accept头或参数确定请求的媒体类型,然后收集所有其他 ViewResolver 解析出的视图,筛选出匹配该媒体类型的视图,按最佳匹配选择。 - 追问1:如果找不到匹配媒体类型的视图会怎样?→ 返回 406 Not Acceptable。
- 追问2:可以设置默认视图吗?→ 可以,设置
defaultViews。 - 追问3:内容协商策略可以自定义吗?→ 可以,设置
ContentNegotiationManager。 - 加分回答:Spring Boot 自动配置通常已经设定了适合 REST 的 CNVR。
- 标准回答:它根据
-
一个请求返回了 404 错误,可能是哪些组件出了问题?
- 标准回答:可能是 HandlerMapping 没有找到匹配的处理器(URL 写错、请求方法不对),或
noHandlerFound触发了 404;也可能是找到了 Handler 但视图解析器解析不到视图资源(如 JSP 文件不存在)。 - 追问1:如何区分是映射问题还是视图问题?→ 查看异常堆栈,映射问题通常抛出
NoHandlerFoundException或直接从noHandlerFound返回;视图问题则在渲染时抛出。 - 追问2:
noHandlerFound默认返回 404,如何自定义?→ 配置DispatcherServlet.setThrowExceptionIfNoHandlerFound(true),再用全局异常处理。 - 追问3:静态资源 404 怎么处理?→ 通常绕过 DispatcherServlet,由容器 Default Servlet 处理。
- 标准回答:可能是 HandlerMapping 没有找到匹配的处理器(URL 写错、请求方法不对),或
-
简述 HandlerExceptionResolver 的优先级链条。
- 标准回答:按照 Order 排序,默认为
ExceptionHandlerExceptionResolver(0),ResponseStatusExceptionResolver(1),DefaultHandlerExceptionResolver(2)。前者处理@ExceptionHandler,中间处理@ResponseStatus注解,后者处理 Spring 标准异常。 - 追问1:如果高优先级解析器处理了异常但返回 null,会怎样?→ 继续下一个解析器。
- 追问2:如何添加自定义解析器?→ 实现接口并注入 Bean。
- 追问3:异常解析器链和视图解析器链有何不同?→ 异常解析器解析异常为 ModelAndView,视图解析器解析视图名为 View。
- 标准回答:按照 Order 排序,默认为
-
Spring MVC 的请求处理流程中,哪些地方可以用策略模式来解释?
- 标准回答:HandlerMapping 策略、HandlerAdapter 策略、ViewResolver 策略、HandlerExceptionResolver 策略全部是策略模式的应用。
- 追问1:策略模式的优势?→ 可插拔,易于扩展,符合开闭原则。
- 追问2:Spring 如何实现策略的装配?→ 通过 List 注入和 Order 排序。
- 追问3:除了策略模式还有什么模式?→ 模板方法(doDispatch)、责任链(拦截器、解析器链)、适配器(HandlerAdapter)。
-
如果 Controller 方法抛出异常,拦截器的 afterCompletion 还会执行吗?postHandle 呢?为什么?
- 标准回答:afterCompletion 会执行,postHandle 不会。源码保证:异常被 catch 后直接跳过 postHandle 调用,而 afterCompletion 在
processDispatchResult的尾部或triggerAfterCompletion中被调用。 - 追问1:如果 interceptors 的 preHandle 返回 false,afterCompletion 会执行吗?→ 会,但仅对已执行的拦截器。
- 追问2:afterCompletion 里的 Exception 参数是什么?→ 如果有异常,就是抛出的异常对象,否则为 null。
- 追问3:如何利用这个特性?→ 将资源清理代码放在 afterCompletion 中保证执行。
- 标准回答:afterCompletion 会执行,postHandle 不会。源码保证:异常被 catch 后直接跳过 postHandle 调用,而 afterCompletion 在
-
(系统设计题) 设计一个支持多协议(HTTP、gRPC、Dubbo)的请求分发器,要求能够根据请求类型自动选择对应的 Handler 和 Adapter。请参考 Spring MVC 的设计,利用策略模式和适配器模式给出核心架构,并说明 HandlerMapping 和 HandlerAdapter 如何扩展。
- 标准回答:定义一个通用的请求上下文(如
RpcContext),包含协议类型、请求参数等。设计RpcHandlerMapping接口,每种协议实现一个 Mapping(例如HttpHandlerMapping、GrpcHandlerMapping),策略链遍历匹配。Handler 是自定义接口(如RpcHandler)。RpcHandlerAdapter利用supports判断 Handler 类型并统一调用。整个分发器类似于一个轻量级的DispatchServlet。 - 追问1:如何动态注册新的协议?→ 通过 SPI 或 Spring Bean 动态添加 Mapping 和 Adapter 到列表。
- 追问2:如何实现拦截器?→ 定义
RpcInterceptor接口,链式调用。 - 追问3:出现协议不支持怎么办?→ 最后一个 Mapping 可返回一个默认 Handler,由 Adapter 抛出不支持异常。
- 加分回答:可以使用
Ordered控制 Mapping 尝试顺序,达到类似 Spring MVC 的效果。
- 标准回答:定义一个通用的请求上下文(如
附录:Spring MVC 请求处理关键接口速查表
| 组件 | 核心接口 | 关键实现类 | 设计模式 |
|---|---|---|---|
| 前端控制器 | DispatcherServlet | 模板方法 | |
| 处理器映射 | HandlerMapping | RequestMappingHandlerMapping、BeanNameUrlHandlerMapping | 策略模式 |
| 处理器适配 | HandlerAdapter | RequestMappingHandlerAdapter、HttpRequestHandlerAdapter | 适配器模式 |
| 拦截器 | HandlerInterceptor | 自定义 | AOP/责任链 |
| 视图解析 | ViewResolver | InternalResourceViewResolver、ThymeleafViewResolver、ContentNegotiatingViewResolver | 责任链模式 |
| 异常解析 | HandlerExceptionResolver | ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver | 责任链模式 |
| 处理器执行链 | HandlerExecutionChain | 组合模式 | |
| 参数解析 | HandlerMethodArgumentResolver | 众多(下篇详述) | 策略模式 |
| 返回值处理 | HandlerMethodReturnValueHandler | 众多(下篇详述) | 策略模式 |
| 消息转换 | HttpMessageConverter | MappingJackson2HttpMessageConverter | 策略模式(待详述) |
延伸阅读
- Spring Framework 官方文档:Web Servlet
- 《Spring 实战 (第5版)》, Craig Walls, 第2章和第7章
- Spring 源码深度解析系列(推荐阅读
DispatcherServlet源码及核心组件)
结语:本文深入剖析了 Spring MVC 请求处理全链路,从
doDispatch的骨架设计,到策略组件的协作,再到异常安全与视图解析的风险点。掌握了这条链路,不仅能使你在面试中游刃有余,更能在日常开发与故障排查中,直击问题本质。下一篇文章将深入参数解析与绑定,敬请期待。