📌 PDF :大白话说Java面试题 --- 06_Spring篇
第21题:SpringMVC 的工作流程(DispatcherServlet 的工作流程)
📚 回答:
- 核心考点 : SpringMVC 的工作流程远不止"DispatcherServlet → HandlerMapping → HandlerAdapter → ViewResolver"的背诵式回答。大厂面试中,面试官期望你深入理解 DispatcherServlet 的九大组件初始化策略 (
initStrategies)、doDispatch()方法的完整源码链路 (含文件上传预处理、拦截器链执行、异常处理)、HandlerMapping 的映射匹配机制 (URL + HTTP Method + consumes/produces 多条件匹配)、HandlerAdapter 的适配器模式设计 (参数解析器 + 返回值处理器)、以及 前后端分离场景下@ResponseBody如何跳过 ViewResolver 直接走HttpMessageConverter。面试官真正想判断的是:你是否能从源码级理解 SpringMVC 如何将一个 HTTP 请求转化为 Controller 方法的执行,以及各组件如何通过接口抽象实现高度可扩展性。
1. 九大核心组件与初始化策略
SpringMVC 的 DispatcherServlet 在初始化时通过 initStrategies() 方法加载九大核心组件,采用"约定优于配置"原则,未显式配置时提供默认实现:
| 组件 | 接口/类 | 职责 | 默认实现 |
|---|---|---|---|
| MultipartResolver | MultipartResolver |
文件上传解析 | StandardServletMultipartResolver(Servlet 3.0+) |
| LocaleResolver | LocaleResolver |
国际化区域解析 | AcceptHeaderLocaleResolver |
| ThemeResolver | ThemeResolver |
主题解析 | FixedThemeResolver |
| HandlerMapping | HandlerMapping |
请求 → 处理器映射 | RequestMappingHandlerMapping |
| HandlerAdapter | HandlerAdapter |
适配执行处理器 | RequestMappingHandlerAdapter |
| HandlerExceptionResolver | HandlerExceptionResolver |
异常统一处理 | ExceptionHandlerExceptionResolver |
| RequestToViewNameTranslator | RequestToViewNameTranslator |
请求转视图名 | DefaultRequestToViewNameTranslator |
| ViewResolver | ViewResolver |
视图名 → 视图对象 | InternalResourceViewResolver |
| FlashMapManager | FlashMapManager |
重定向参数传递 | SessionFlashMapManager |
java
// DispatcherServlet.initStrategies() 源码
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context); // 加载所有 HandlerMapping Bean
initHandlerAdapters(context); // 加载所有 HandlerAdapter Bean
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
关键设计 :initHandlerMappings(context) 会获取 IoC 容器中所有 HandlerMapping 类型的 Bean,按 order 属性排序后存入 List<HandlerMapping>。请求处理时按顺序遍历,第一个返回非 null 的 HandlerExecutionChain 即被采用。这种设计允许自定义 HandlerMapping 覆盖默认行为。citation:0
2. doDispatch() 源码级完整流程
DispatcherServlet.doDispatch() 是请求处理的核心入口,采用 模板方法模式 定义处理骨架:
java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1. 文件上传预处理:将普通 Request 包装为 MultipartHttpServletRequest
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 2. 获取处理器执行链(含 Handler + Interceptor 链)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response); // 404 处理
return;
}
// 3. 获取处理器适配器(适配器模式)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. 执行拦截器 preHandle(),任一返回 false 则中断
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 5. 实际调用 Handler(Controller 方法),返回 ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 6. 应用默认视图名(如果 Handler 返回 null 视图名)
applyDefaultViewName(processedRequest, mv);
// 7. 执行拦截器 postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 8. 处理视图渲染 / 异常处理 / JSON 序列化
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} finally {
// 9. 清理 Multipart 资源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
关键设计亮点:
| 设计模式 | 体现位置 | 作用 |
|---|---|---|
| 模板方法模式 | doDispatch() 定义骨架,子类可覆盖 getHandler() 等 protected 方法 |
统一流程,允许定制 |
| 责任链模式 | HandlerExecutionChain 组合多个 HandlerInterceptor |
可配置拦截器链,任意拦截器返回 false 即中断 |
| 适配器模式 | HandlerAdapter.supports() + HandlerAdapter.handle() |
统一适配多种处理器类型(注解方法、接口实现、Servlet 等) |
| 策略模式 | HandlerMapping、ViewResolver 等多组件接口 |
不同实现可插拔替换 |
citation:1
3. HandlerMapping 的映射匹配机制
RequestMappingHandlerMapping 是 Spring 5+ 的默认实现,在应用启动时扫描所有 @Controller/@RestController Bean,解析 @RequestMapping 注解构建映射元数据:
java
// RequestMappingHandlerMapping 核心映射逻辑
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request); // 获取请求路径
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); // 查找匹配
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
映射匹配条件(多维度精确匹配):
| 匹配维度 | 注解属性 | 示例 |
|---|---|---|
| 请求路径 | value / path |
@GetMapping("/users/{id}") |
| HTTP 方法 | method |
@RequestMapping(method = RequestMethod.POST) |
| 请求参数 | params |
@GetMapping(params = "version=2") |
| 请求头 | headers |
@GetMapping(headers = "X-Api-Version=2") |
| 消费内容类型 | consumes |
@PostMapping(consumes = "application/json") |
| 生产内容类型 | produces |
@GetMapping(produces = "application/json") |
匹配优先级 :精确路径 > 带通配符路径 > 正则路径;同时满足的条件越多优先级越高。若多个 HandlerMethod 优先级相同,会抛出 IllegalStateException( ambiguous mapping)。citation:2
4. HandlerAdapter 的适配器模式设计
DispatcherServlet 通过 getHandlerAdapter() 遍历所有 HandlerAdapter,调用 supports() 找到支持当前 Handler 的适配器:
java
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (ha.supports(handler)) { // 适配器判断是否支持该 Handler 类型
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler + "]");
}
Spring 内置的 HandlerAdapter 实现:
| 实现类 | 支持的 Handler 类型 | 典型场景 |
|---|---|---|
| RequestMappingHandlerAdapter | @RequestMapping 注解的 HandlerMethod |
现代注解驱动开发(最常用) |
| HttpRequestHandlerAdapter | HttpRequestHandler 接口实现 |
静态资源处理、文件下载 |
| SimpleControllerHandlerAdapter | Controller 接口实现 |
传统 Spring MVC 开发 |
| SimpleServletHandlerAdapter | Servlet 接口实现 |
集成遗留 Servlet |
RequestMappingHandlerAdapter 的核心执行链路:
java
// 简化的执行流程
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 1. 参数解析:通过 HandlerMethodArgumentResolver 组合解析方法参数
Object[] args = resolveArguments(handlerMethod, request, response);
// 2. 方法调用:反射执行 Controller 方法
Object returnValue = invokeHandlerMethod(handlerMethod, args);
// 3. 返回值处理:通过 HandlerMethodReturnValueHandler 处理返回值
return processReturnValue(returnValue, handlerMethod);
}
citation:3
5. 前后端分离场景:@ResponseBody 如何跳过 ViewResolver
在现代前后端分离项目中,Controller 直接返回 JSON 数据,不再需要视图渲染。SpringMVC 通过 @ResponseBody(或 @RestController)实现这一机制:
java
// 传统 MVC:返回视图名,需要 ViewResolver
@Controller
public class UserController {
@GetMapping("/user/page")
public String getUserPage(Model model) { // 返回视图名
model.addAttribute("user", userService.getById(1L));
return "userDetail"; // → ViewResolver → userDetail.html
}
}
// 前后端分离:直接返回 JSON,跳过 ViewResolver
@RestController // = @Controller + @ResponseBody
public class UserApiController {
@GetMapping("/api/user")
public User getUser() { // 返回对象
return userService.getById(1L);
// → RequestResponseBodyMethodProcessor 处理
// → HttpMessageConverter(如 MappingJackson2HttpMessageConverter)
// → 序列化为 JSON 直接写回 response
}
}
@ResponseBody 的处理链路:
RequestMappingHandlerAdapter调用HandlerMethodReturnValueHandler处理返回值;RequestResponseBodyMethodProcessor(处理@ResponseBody的处理器)接管;- 通过
HttpMessageConverter(消息转换器)将 Java 对象序列化为 JSON/XML/Protobuf 等; - 直接写入
HttpServletResponse,跳过 ViewResolver 和 View 渲染。
常用 HttpMessageConverter:
| 转换器 | 支持媒体类型 | 功能 |
|---|---|---|
MappingJackson2HttpMessageConverter |
application/json |
JSON 序列化/反序列化 |
StringHttpMessageConverter |
text/plain |
字符串读写 |
ByteArrayHttpMessageConverter |
application/octet-stream |
字节数组读写 |
ProtobufHttpMessageConverter |
application/x-protobuf |
Protobuf 序列化 |
citation:4
6. 拦截器 vs 过滤器:执行时机与职责边界
| 对比维度 | 过滤器(Filter) | 拦截器(HandlerInterceptor) |
|---|---|---|
| 所属规范 | Servlet 规范 | SpringMVC 规范 |
| 执行时机 | DispatcherServlet 之前(Servlet 容器层面) |
DispatcherServlet 之后,Handler 执行前后(SpringMVC 层面) |
| 依赖容器 | 依赖 Servlet 容器(Tomcat 等) | 依赖 Spring IoC 容器 |
| 访问范围 | 可拦截所有请求(包括静态资源) | 只能拦截被 HandlerMapping 映射的请求 |
| 执行顺序 | 按 web.xml/@Order 顺序 |
按 HandlerExecutionChain 中的顺序 |
| 典型用途 | 字符编码、登录校验、权限过滤 | 日志记录、性能监控、事务边界 |
执行顺序图解:
HTTP Request
↓
Filter1.doFilter() → Filter2.doFilter() 【Servlet 层】
↓
DispatcherServlet.doDispatch()
↓
HandlerInterceptor.preHandle() 【SpringMVC 层 - 前置】
↓
Handler (Controller) 执行业务逻辑
↓
HandlerInterceptor.postHandle() 【SpringMVC 层 - 后置】
↓
View 渲染 / JSON 序列化
↓
HandlerInterceptor.afterCompletion() 【SpringMVC 层 - 最终】
↓
Filter2.doFilter() 返回 → Filter1.doFilter() 返回
↓
HTTP Response
关键区别 :Filter 在 DispatcherServlet 之前执行,可以拦截未映射到 SpringMVC 的请求(如静态资源);Interceptor 在 DispatcherServlet 之后执行,可以访问 Spring 的上下文和 Handler 信息,但无法拦截未映射的请求。citation:5
7. 异常处理机制
DispatcherServlet 在 doDispatch() 的 try-catch 中捕获异常,统一转交 processDispatchResult() 处理:
java
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv,
Exception exception) throws Exception {
if (exception != null) {
// 1. 交给 HandlerExceptionResolver 处理异常
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
}
if (mv != null && !mv.wasCleared()) {
render(mv, request, response); // 2. 渲染视图(或 JSON 错误响应)
}
// 3. 执行拦截器 afterCompletion(无论成功或异常都会执行)
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
异常处理优先级:
@ExceptionHandler方法(当前 Controller 内);@ControllerAdvice+@ExceptionHandler(全局);HandlerExceptionResolver实现类(如SimpleMappingExceptionResolver);- 默认 Servlet 容器错误页面。
8. 面试官追问与高分回答模板
-
追问 1:"请详细描述 SpringMVC 的工作流程?"
低分回答:"请求先到 DispatcherServlet,然后找 HandlerMapping,再交给 HandlerAdapter 执行,最后 ViewResolver 解析视图。"(只背了表面流程,没有源码级理解)
高分回答 : > "SpringMVC 的核心是
DispatcherServlet的模板方法模式。完整流程分为 9 步: > 1. 请求接收 :DispatcherServlet接收 HTTP 请求,先通过checkMultipart()处理文件上传; > 2. 查找处理器 :调用getHandler()遍历所有HandlerMapping,RequestMappingHandlerMapping根据 URL + HTTP Method + consumes/produces 等多条件匹配,返回HandlerExecutionChain(含 Handler + Interceptor 链); > 3. 获取适配器 :getHandlerAdapter()遍历所有HandlerAdapter,通过supports()找到支持当前 Handler 的适配器; > 4. 拦截器前置 :执行applyPreHandle(),所有拦截器的preHandle()顺序执行,任一返回 false 则中断; > 5. 执行业务 :HandlerAdapter.handle()通过反射调用 Controller 方法,内部通过HandlerMethodArgumentResolver解析参数,HandlerMethodReturnValueHandler处理返回值; > 6. 拦截器后置 :执行applyPostHandle(),可以对ModelAndView做修改; > 7. 视图解析/JSON 序列化 :processDispatchResult()处理结果。如果是@ResponseBody,RequestResponseBodyMethodProcessor通过HttpMessageConverter直接序列化为 JSON 写回响应,跳过 ViewResolver;如果是传统 MVC,则通过ViewResolver解析视图名; > 8. 视图渲染 :View.render()将 Model 数据填充到模板; > 9. 最终回调 :triggerAfterCompletion()执行拦截器afterCompletion(),无论成功或异常都会执行,用于资源清理。 > 前后端分离项目中,步骤 7 的视图解析和步骤 8 的视图渲染会被跳过,直接走 JSON 序列化。" -
追问 2:"HandlerMapping 和 HandlerAdapter 为什么要分开设计?"
高分回答 : > "这是经典的 适配器模式 应用,目的是 解耦"查找处理器"和"执行处理器" : > -
HandlerMapping负责 '找谁处理' :根据请求 URL、Method、参数等条件找到对应的 Handler(可能是@RequestMapping方法、Controller接口实现、HttpRequestHandler等)。 > -HandlerAdapter负责 '怎么执行' :不同类型的 Handler 执行方式不同------注解方法需要反射调用,接口实现需要直接调用handleRequest(),Servlet 需要调用service()。HandlerAdapter通过supports()判断是否支持某 Handler,通过handle()统一执行。 > 这种设计的优势是 高度可扩展 :新增一种处理器类型(如 WebFlux 的 RouterFunction),只需新增对应的HandlerMapping和HandlerAdapter,无需修改DispatcherServlet的核心逻辑,完全符合开闭原则。" -
追问 3:"前后端分离项目中,SpringMVC 的 ViewResolver 还有用吗?"
高分回答 : > "在纯 REST API 项目中,
ViewResolver和View渲染步骤确实被跳过,但 SpringMVC 的核心流程并未改变。关键区别在于返回值处理: > - 传统 MVC:Controller 返回String(视图名)或ModelAndView,ViewResolver解析为View对象,再渲染为 HTML。 > - 前后端分离:Controller 返回对象(如User),@ResponseBody(或@RestController)让RequestResponseBodyMethodProcessor接管,通过HttpMessageConverter(如MappingJackson2HttpMessageConverter)将对象序列化为 JSON,直接写入HttpServletResponse。 >processDispatchResult()中判断mv != null时,如果 Handler 返回的是@ResponseBody类型,mv实际为 null(或已被标记为已处理),因此不会进入render()方法。 > 但ViewResolver在需要返回错误页面(如 404、500 的 HTML 错误页)或混合模式(部分接口返回 JSON、部分返回页面)时仍然有用。" -
追问 4:"SpringMVC 的拦截器和过滤器有什么区别?执行顺序是怎样的?"
高分回答 : > "两者在 规范归属、执行时机、访问范围 三个维度有本质区别: > - 过滤器(Filter) :属于 Servlet 规范,在
DispatcherServlet之前执行,由 Servlet 容器(Tomcat)管理,可以拦截所有请求(包括静态资源)。典型用途:字符编码设置(CharacterEncodingFilter)、登录校验、跨域处理。 > - 拦截器(Interceptor) :属于 SpringMVC 规范,在DispatcherServlet之后执行,由 Spring IoC 容器管理,只能拦截被HandlerMapping映射的请求(即 Controller 请求)。典型用途:日志记录、性能监控、权限校验(可访问 Spring 上下文)。 > 执行顺序 :Filter1 → Filter2 → DispatcherServlet → Interceptor.preHandle() → Controller → Interceptor.postHandle() → View 渲染 → Interceptor.afterCompletion() → Filter2 返回 → Filter1 返回。 > 拦截器的afterCompletion()无论请求成功还是异常都会执行,是资源清理和日志记录的最佳位置。" -
追问 5:"DispatcherServlet 是线程安全的吗?为什么?"
高分回答 : > "是线程安全的 。
DispatcherServlet在 Spring IoC 容器中默认是 单例 的,但它是 无状态 的------不会在成员变量中保存请求相关的数据。每个请求的处理数据(如HttpServletRequest、HttpServletResponse、ModelAndView)都通过方法参数传递,不共享状态。 > 线程安全的关键在于: > 1.doDispatch()方法内的所有变量(processedRequest、mappedHandler、mv等)都是 局部变量 ,存储在每个线程的栈中,线程间隔离; > 2. 核心组件(HandlerMapping、HandlerAdapter等)虽然作为成员变量存在,但它们是 无状态 的,只负责调度逻辑,不保存请求状态; > 3. 真正保存请求状态的是ThreadLocal(如RequestContextHolder),每个线程独立。 > 这种设计与 Servlet 规范一致:Servlet 容器(Tomcat)为每个请求分配一个线程,Servlet 实例本身不绑定到特定线程。" -
追问 6:"SpringMVC 如何处理异常?@ControllerAdvice 是怎么生效的?"
高分回答 : > "SpringMVC 的异常处理在
processDispatchResult()中统一完成: > 1.doDispatch()的 try-catch 捕获业务执行中的异常,存入dispatchException; > 2.processDispatchResult()判断exception != null时,调用processHandlerException(); > 3.processHandlerException()遍历所有HandlerExceptionResolver,按优先级尝试处理: > - 首先检查当前 Controller 类内的@ExceptionHandler方法; > - 然后检查@ControllerAdvice类中的@ExceptionHandler方法; > - 最后检查配置的SimpleMappingExceptionResolver等。 >@ControllerAdvice的生效机制:应用启动时,ExceptionHandlerExceptionResolver扫描所有@ControllerAdviceBean,缓存其中的@ExceptionHandler方法。处理异常时,根据异常类型匹配最合适的@ExceptionHandler方法,通过反射调用。 > 如果所有HandlerExceptionResolver都无法处理,异常会抛给 Servlet 容器,返回默认错误页面。"
9. 方案选型速查表
| 业务场景 | 推荐方案 | 核心理由 |
|---|---|---|
| 传统服务端渲染(JSP/Thymeleaf) | 完整 9 步流程 | 需要 ViewResolver 解析视图并渲染 |
| 前后端分离 REST API | @RestController + HttpMessageConverter |
跳过视图渲染,直接 JSON 序列化 |
| 文件上传接口 | MultipartResolver + @RequestParam(MultipartFile) |
自动包装 MultipartHttpServletRequest |
| 全局异常处理 | @ControllerAdvice + @ExceptionHandler |
统一异常响应,避免每个 Controller 重复处理 |
| 接口鉴权/日志 | HandlerInterceptor |
可访问 Spring 上下文,按 URL 精确拦截 |
| 跨域/编码/全局过滤 | Filter(如 CharacterEncodingFilter) |
在 DispatcherServlet 之前执行,覆盖所有请求 |
| 自定义参数解析 | 实现 HandlerMethodArgumentResolver |
如解析自定义注解 @CurrentUser |
| 自定义返回值包装 | 实现 HandlerMethodReturnValueHandler |
如统一包装为 Result<T> 响应体 |
💡 面试官想要的满分总结:
SpringMVC 的核心设计哲学是 "统一入口、组件解耦、高度可扩展" 。
DispatcherServlet作为前端控制器,不直接处理请求,而是通过 模板方法模式 定义doDispatch()的处理骨架,将具体工作委派给各个组件:
- HandlerMapping 负责"找谁处理" :
RequestMappingHandlerMapping在启动时扫描注解构建映射表,运行时根据 URL + HTTP Method + consumes/produces 等多条件精确匹配,返回HandlerExecutionChain(含拦截器链)。- HandlerAdapter 负责"怎么执行" :通过适配器模式统一适配
@RequestMapping方法、Controller接口、HttpRequestHandler等多种处理器类型,内部通过HandlerMethodArgumentResolver解析参数、HandlerMethodReturnValueHandler处理返回值。- ViewResolver 负责"怎么展示" :传统 MVC 中将逻辑视图名解析为具体
View对象;前后端分离项目中,@ResponseBody让RequestResponseBodyMethodProcessor通过HttpMessageConverter直接序列化为 JSON,跳过视图渲染。理解 SpringMVC 必须抓住三个关键点:
- 九大组件初始化 :
initStrategies()加载所有组件,未配置则提供默认实现,"约定优于配置";- 拦截器执行链 :
preHandle()→ Controller →postHandle()→ 视图渲染 →afterCompletion(),任一preHandle()返回 false 即中断,但afterCompletion()始终执行;- 前后端分离的链路变化 :
@ResponseBody不改变核心流程,只是在processDispatchResult()中通过HttpMessageConverter直接写回响应,跳过了ViewResolver和View.render()。最后记住:Filter 在 DispatcherServlet 之前执行(Servlet 层),Interceptor 在之后执行(SpringMVC 层),两者职责边界清晰,不可互相替代。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯