【大白话说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 |
// DispatcherServlet.initStrategies() 源码protectedvoidinitStrategies(ApplicationContextcontext){initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);// 加载所有 HandlerMapping BeaninitHandlerAdapters(context);// 加载所有 HandlerAdapter BeaninitHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}关键设计:initHandlerMappings(context)会获取 IoC 容器中所有HandlerMapping类型的 Bean,按order属性排序后存入List<HandlerMapping>。请求处理时按顺序遍历,第一个返回非 null 的HandlerExecutionChain即被采用。这种设计允许自定义HandlerMapping覆盖默认行为。[citation:0]
2. doDispatch() 源码级完整流程
DispatcherServlet.doDispatch()是请求处理的核心入口,采用模板方法模式定义处理骨架:
protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{HttpServletRequestprocessedRequest=request;HandlerExecutionChainmappedHandler=null;booleanmultipartRequestParsed=false;try{ModelAndViewmv=null;ExceptiondispatchException=null;try{// 1. 文件上传预处理:将普通 Request 包装为 MultipartHttpServletRequestprocessedRequest=checkMultipart(request);multipartRequestParsed=(processedRequest!=request);// 2. 获取处理器执行链(含 Handler + Interceptor 链)mappedHandler=getHandler(processedRequest);if(mappedHandler==null){noHandlerFound(processedRequest,response);// 404 处理return;}// 3. 获取处理器适配器(适配器模式)HandlerAdapterha=getHandlerAdapter(mappedHandler.getHandler());// 4. 执行拦截器 preHandle(),任一返回 false 则中断if(!mappedHandler.applyPreHandle(processedRequest,response)){return;}// 5. 实际调用 Handler(Controller 方法),返回 ModelAndViewmv=ha.handle(processedRequest,response,mappedHandler.getHandler());// 6. 应用默认视图名(如果 Handler 返回 null 视图名)applyDefaultViewName(processedRequest,mv);// 7. 执行拦截器 postHandle()mappedHandler.applyPostHandle(processedRequest,response,mv);}catch(Exceptionex){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/@RestControllerBean,解析@RequestMapping注解构建映射元数据:
// RequestMappingHandlerMapping 核心映射逻辑protectedHandlerMethodgetHandlerInternal(HttpServletRequestrequest)throwsException{StringlookupPath=initLookupPath(request);// 获取请求路径HandlerMethodhandlerMethod=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 的适配器:
protectedHandlerAdaptergetHandlerAdapter(Objecthandler)throwsServletException{for(HandlerAdapterha:this.handlerAdapters){if(ha.supports(handler)){// 适配器判断是否支持该 Handler 类型returnha;}}thrownewServletException("No adapter for handler ["+handler+"]");}Spring 内置的 HandlerAdapter 实现:
| 实现类 | 支持的 Handler 类型 | 典型场景 |
|---|---|---|
| RequestMappingHandlerAdapter | @RequestMapping注解的 HandlerMethod | 现代注解驱动开发(最常用) |
| HttpRequestHandlerAdapter | HttpRequestHandler接口实现 | 静态资源处理、文件下载 |
| SimpleControllerHandlerAdapter | Controller接口实现 | 传统 Spring MVC 开发 |
| SimpleServletHandlerAdapter | Servlet接口实现 | 集成遗留 Servlet |
RequestMappingHandlerAdapter 的核心执行链路:
// 简化的执行流程publicModelAndViewhandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){HandlerMethodhandlerMethod=(HandlerMethod)handler;// 1. 参数解析:通过 HandlerMethodArgumentResolver 组合解析方法参数Object[]args=resolveArguments(handlerMethod,request,response);// 2. 方法调用:反射执行 Controller 方法ObjectreturnValue=invokeHandlerMethod(handlerMethod,args);// 3. 返回值处理:通过 HandlerMethodReturnValueHandler 处理返回值returnprocessReturnValue(returnValue,handlerMethod);}[citation:3]
5. 前后端分离场景:@ResponseBody 如何跳过 ViewResolver
在现代前后端分离项目中,Controller 直接返回 JSON 数据,不再需要视图渲染。SpringMVC 通过@ResponseBody(或@RestController)实现这一机制:
// 传统 MVC:返回视图名,需要 ViewResolver@ControllerpublicclassUserController{@GetMapping("/user/page")publicStringgetUserPage(Modelmodel){// 返回视图名model.addAttribute("user",userService.getById(1L));return"userDetail";// → ViewResolver → userDetail.html}}// 前后端分离:直接返回 JSON,跳过 ViewResolver@RestController// = @Controller + @ResponseBodypublicclassUserApiController{@GetMapping("/api/user")publicUsergetUser(){// 返回对象returnuserService.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()处理:
privatevoidprocessDispatchResult(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,ModelAndViewmv,Exceptionexception)throwsException{if(exception!=null){// 1. 交给 HandlerExceptionResolver 处理异常Objecthandler=(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 层),两者职责边界清晰,不可互相替代。
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯
