【大白话说Java面试题 第161题】【06_Spring篇】第21题: SpringMVC工作流程

📌 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 等)
策略模式 HandlerMappingViewResolver 等多组件接口 不同实现可插拔替换

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 的处理链路

  1. RequestMappingHandlerAdapter 调用 HandlerMethodReturnValueHandler 处理返回值;
  2. RequestResponseBodyMethodProcessor(处理 @ResponseBody 的处理器)接管;
  3. 通过 HttpMessageConverter(消息转换器)将 Java 对象序列化为 JSON/XML/Protobuf 等;
  4. 直接写入 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. 异常处理机制

DispatcherServletdoDispatch() 的 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);
    }
}

异常处理优先级

  1. @ExceptionHandler 方法(当前 Controller 内);
  2. @ControllerAdvice + @ExceptionHandler(全局);
  3. HandlerExceptionResolver 实现类(如 SimpleMappingExceptionResolver);
  4. 默认 Servlet 容器错误页面。

8. 面试官追问与高分回答模板
  • 追问 1:"请详细描述 SpringMVC 的工作流程?"

    低分回答:"请求先到 DispatcherServlet,然后找 HandlerMapping,再交给 HandlerAdapter 执行,最后 ViewResolver 解析视图。"(只背了表面流程,没有源码级理解)

    高分回答 : > "SpringMVC 的核心是 DispatcherServlet 的模板方法模式。完整流程分为 9 步: > 1. 请求接收DispatcherServlet 接收 HTTP 请求,先通过 checkMultipart() 处理文件上传; > 2. 查找处理器 :调用 getHandler() 遍历所有 HandlerMappingRequestMappingHandlerMapping 根据 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() 处理结果。如果是 @ResponseBodyRequestResponseBodyMethodProcessor 通过 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),只需新增对应的 HandlerMappingHandlerAdapter,无需修改 DispatcherServlet 的核心逻辑,完全符合开闭原则。"

  • 追问 3:"前后端分离项目中,SpringMVC 的 ViewResolver 还有用吗?"

    高分回答 : > "在纯 REST API 项目中,ViewResolverView 渲染步骤确实被跳过,但 SpringMVC 的核心流程并未改变。关键区别在于返回值处理: > - 传统 MVC:Controller 返回 String(视图名)或 ModelAndViewViewResolver 解析为 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 容器中默认是 单例 的,但它是 无状态 的------不会在成员变量中保存请求相关的数据。每个请求的处理数据(如 HttpServletRequestHttpServletResponseModelAndView)都通过方法参数传递,不共享状态。 > 线程安全的关键在于: > 1. doDispatch() 方法内的所有变量(processedRequestmappedHandlermv 等)都是 局部变量 ,存储在每个线程的栈中,线程间隔离; > 2. 核心组件(HandlerMappingHandlerAdapter 等)虽然作为成员变量存在,但它们是 无状态 的,只负责调度逻辑,不保存请求状态; > 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 扫描所有 @ControllerAdvice Bean,缓存其中的 @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() 的处理骨架,将具体工作委派给各个组件:

  1. HandlerMapping 负责"找谁处理"RequestMappingHandlerMapping 在启动时扫描注解构建映射表,运行时根据 URL + HTTP Method + consumes/produces 等多条件精确匹配,返回 HandlerExecutionChain(含拦截器链)。
  2. HandlerAdapter 负责"怎么执行" :通过适配器模式统一适配 @RequestMapping 方法、Controller 接口、HttpRequestHandler 等多种处理器类型,内部通过 HandlerMethodArgumentResolver 解析参数、HandlerMethodReturnValueHandler 处理返回值。
  3. ViewResolver 负责"怎么展示" :传统 MVC 中将逻辑视图名解析为具体 View 对象;前后端分离项目中,@ResponseBodyRequestResponseBodyMethodProcessor 通过 HttpMessageConverter 直接序列化为 JSON,跳过视图渲染。

理解 SpringMVC 必须抓住三个关键点:

  • 九大组件初始化initStrategies() 加载所有组件,未配置则提供默认实现,"约定优于配置";
  • 拦截器执行链preHandle() → Controller → postHandle() → 视图渲染 → afterCompletion(),任一 preHandle() 返回 false 即中断,但 afterCompletion() 始终执行;
  • 前后端分离的链路变化@ResponseBody 不改变核心流程,只是在 processDispatchResult() 中通过 HttpMessageConverter 直接写回响应,跳过了 ViewResolverView.render()

最后记住:Filter 在 DispatcherServlet 之前执行(Servlet 层),Interceptor 在之后执行(SpringMVC 层),两者职责边界清晰,不可互相替代。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
落叶-IT3 小时前
Java Scanner 类精讲:控制台交互
java·开发语言
chh5633 小时前
C++--string
java·开发语言·网络·c++·学习
Listen·Rain3 小时前
向量化详解
java·人工智能·spring·机器学习·ai
想你依然心痛3 小时前
AUTOSAR Classic Platform架构解析:BSW、RTE与ASW——分层、配置、代码生成
java·架构·实时互动
梦想的旅途23 小时前
企业微信外部群智能自动回复:基于RPA接口的关键词匹配方案
java·开发语言·数据库·机器人·自动化·企业微信
娇气的红酒3 小时前
关于.NET VS JavaEE平台争论的沉思录
java·java-ee·.net
折哥的程序人生 · 物流技术专研3 小时前
第3篇:手写一个饮品制作模板(附代码)
java·设计模式·行为型模式·模版方法模式·钩子方法·代码复用·编程实战
SamDeepThinking3 小时前
为什么很多人学了Java,还是没真正理解类和对象?
java·后端·面试
zzh___zzh4 小时前
Spring Boot资源路径与ClassPathResource详解
java
学渣超4 小时前
微服务日志智能诊断系统(八) 全链路追踪——request_id驱动的跨服务诊断
java·后端·agent