Spring MVC 深度解密:从 DispatcherServlet 到请求处理全流程

作为 Java 后端开发者,我们每天都在写@RestController@RequestMapping,接收 HTTP 请求,返回 JSON 数据。但很多人用了很多年 Spring MVC,却始终没有搞懂它的底层原理:

  • 为什么只需要写一个 Controller 就能处理请求?
  • 请求从浏览器发出到返回响应,中间经历了什么?
  • DispatcherServlet 到底做了什么?
  • HandlerMapping 和 HandlerAdapter 有什么区别?

这些问题不仅是日常开发中排查问题的关键,更是面试中 100% 会被深挖的核心考点。很多人能写出接口,却不知道 404 错误的根本原因;能配置视图解析器,却不知道它的工作机制。

这篇文章,我们就从Servlet 基础→DispatcherServlet 初始化→9 大核心组件→请求处理全流程→注解原理五个维度,彻底搞懂 Spring MVC。不仅会讲清楚理论,更会结合源码和实战,让你看完既能轻松应对面试,又能解决实际项目中的问题。

一、先搞懂:Spring MVC 与 Servlet 的关系

在开始之前,我们必须先理清一个最基础也是最容易被忽略的问题:Spring MVC 本质上是一个基于 Servlet 的 Web 框架

1. 什么是 Servlet?

Servlet 是 Java EE 规范中定义的 Web 组件,用于处理 HTTP 请求。它的生命周期由 Servlet 容器(如 Tomcat)管理:

  1. 容器启动时加载 Servlet
  2. 调用init()方法初始化 Servlet
  3. 每次请求到来时调用service()方法处理请求
  4. 容器关闭时调用destroy()方法销毁 Servlet

传统的 Servlet 开发非常繁琐:每个请求都需要编写一个 Servlet 类,在web.xml中配置映射,手动处理参数解析、响应生成等重复工作。

2. Spring MVC 的本质

Spring MVC 是对 Servlet 的封装和扩展,它的核心是DispatcherServlet。DispatcherServlet 是一个特殊的 Servlet,它接收所有的 HTTP 请求,然后将请求分发给对应的 Controller 处理。

简单来说:Spring MVC 是一个前端控制器模式的实现,DispatcherServlet 是前端控制器,所有请求都经过它统一分发

3. 传统 Servlet vs Spring MVC

特性 传统 Servlet Spring MVC
开发效率 低,每个请求一个 Servlet 高,一个 Controller 可以处理多个请求
配置 繁琐,需要在 web.xml 中配置每个 Servlet 简单,注解驱动,几乎零配置
功能 基础,需要手动处理参数解析、视图渲染等 丰富,自动处理参数解析、数据绑定、视图渲染等
扩展性 好,提供了大量的扩展点

4. Spring 容器与 Spring MVC 容器的关系

这是面试常考的问题,很多人搞不清这两个容器的区别:

  • Spring 父容器 :加载业务层 Bean(Service、Repository 等),由ContextLoaderListener初始化
  • Spring MVC 子容器:加载 Web 层 Bean(Controller、HandlerMapping 等),由 DispatcherServlet 初始化

关系:子容器可以访问父容器中的 Bean,但父容器不能访问子容器中的 Bean。这就是为什么 Service 不能注入 Controller,但 Controller 可以注入 Service 的原因。

二、DispatcherServlet 的初始化过程

DispatcherServlet 是 Spring MVC 的核心,它的初始化过程决定了 Spring MVC 的整个运行环境。整个初始化过程可以分为四个阶段:

阶段 1:Servlet 容器加载 DispatcherServlet

当 Tomcat 启动时,会读取web.xml或 Spring Boot 的自动配置,加载 DispatcherServlet:

  1. Tomcat 创建 DispatcherServlet 的实例
  2. 调用 DispatcherServlet 的init()方法
  3. init()方法调用initServletBean()方法开始 Spring MVC 的初始化

阶段 2:创建 WebApplicationContext

initServletBean()方法会创建 Spring MVC 的 WebApplicationContext(子容器):

  1. 如果已经存在父容器(Spring 容器),就将其设置为子容器的父容器
  2. 加载 Spring MVC 的配置文件或配置类
  3. 刷新子容器,创建所有 Web 层的 Bean

阶段 3:初始化 9 大核心组件

这是最重要的阶段,DispatcherServlet 会调用initStrategies()方法,初始化 9 大核心组件:

java 复制代码
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);        // 初始化文件上传解析器
    initLocaleResolver(context);           // 初始化本地化解析器
    initThemeResolver(context);            // 初始化主题解析器
    initHandlerMappings(context);          // 初始化处理器映射器
    initHandlerAdapters(context);          // 初始化处理器适配器
    initHandlerExceptionResolvers(context); // 初始化异常解析器
    initRequestToViewNameTranslator(context); // 初始化请求到视图名的转换器
    initViewResolvers(context);            // 初始化视图解析器
    initFlashMapManager(context);          // 初始化FlashMap管理器
}

Spring 会先从容器中查找用户自定义的组件,如果找不到,就使用默认的组件。默认的组件定义在DispatcherServlet.properties文件中。

阶段 4:初始化完成

所有组件初始化完成后,DispatcherServlet 就准备好接收请求了。此时 Spring MVC 的整个运行环境已经搭建完成。

三、Spring MVC 的 9 大核心组件

Spring MVC 的 9 大核心组件各司其职,共同完成请求的处理工作。其中HandlerMapping、HandlerAdapter、ViewResolver是面试最常考的三个,必须重点掌握。

组件 作用 默认实现
HandlerMapping 根据请求找到对应的处理器(Handler) RequestMappingHandlerMapping
HandlerAdapter 适配不同类型的处理器,统一调用方式 RequestMappingHandlerAdapter
ViewResolver 根据视图名解析出对应的 View 对象 InternalResourceViewResolver
HandlerExceptionResolver 处理请求过程中抛出的异常 ExceptionHandlerExceptionResolver
RequestToViewNameTranslator 将请求转换为视图名 DefaultRequestToViewNameTranslator
LocaleResolver 解析客户端的本地化信息 AcceptHeaderLocaleResolver
ThemeResolver 解析主题信息 FixedThemeResolver
MultipartResolver 解析文件上传请求 StandardServletMultipartResolver
FlashMapManager 管理重定向时的参数传递 SessionFlashMapManager

1. HandlerMapping(处理器映射器)

作用:根据请求的 URL、请求方法等信息,找到对应的处理器(Handler)和拦截器链。

简单来说:HandlerMapping 负责告诉 DispatcherServlet,这个请求应该由哪个 Controller 的哪个方法来处理

常用实现

  • RequestMappingHandlerMapping:处理@RequestMapping注解的方法,是 Spring 3.1 以后的默认实现
  • BeanNameUrlHandlerMapping:将 URL 映射到 Bean 的名称,比较古老

2. HandlerAdapter(处理器适配器)

作用:适配不同类型的处理器,统一调用方式。

这是最容易被误解的组件,很多人会问:为什么不直接调用 Handler,还要多一个 HandlerAdapter?

答案是:因为 Handler 的类型不统一。Spring MVC 支持多种类型的处理器:

  • 实现了Controller接口的处理器
  • 实现了HttpRequestHandler接口的处理器
  • 带有@RequestMapping注解的方法
  • 甚至可以是 Servlet

这些处理器的调用方式完全不同,如果没有适配器,DispatcherServlet 需要写大量的if-else来判断处理器类型,违反了开闭原则。

HandlerAdapter 使用了适配器模式 ,将不同类型的处理器适配成统一的接口,DispatcherServlet 只需要调用HandlerAdapter.handle()方法即可。

常用实现

  • RequestMappingHandlerAdapter:处理@RequestMapping注解的方法,是最常用的实现
  • HttpRequestHandlerAdapter:适配HttpRequestHandler类型的处理器
  • SimpleControllerHandlerAdapter:适配Controller接口类型的处理器

3. ViewResolver(视图解析器)

作用:根据视图名和本地化信息,解析出对应的 View 对象。

简单来说:ViewResolver 负责告诉 DispatcherServlet,应该返回哪个视图给用户

常用实现

  • InternalResourceViewResolver:解析 JSP 视图,是最常用的实现
  • ThymeleafViewResolver:解析 Thymeleaf 视图
  • FreeMarkerViewResolver:解析 FreeMarker 视图

四、请求处理的完整流程(面试必背)

这是 Spring MVC 最核心的内容,也是面试 100% 会问的问题。我把整个流程拆解成 13 个步骤,每个步骤都讲清楚做了什么,由哪个组件负责。

完整请求处理流程

  1. 用户发送请求:用户在浏览器中输入 URL,发送 HTTP 请求到 Tomcat
  2. Tomcat 接收请求:Tomcat 接收请求,将其交给 DispatcherServlet 处理
  3. DispatcherServlet 接收请求 :DispatcherServlet 调用doService()方法处理请求
  4. HandlerMapping 查找 Handler:DispatcherServlet 调用 HandlerMapping,根据请求找到对应的 Handler 和拦截器链
  5. HandlerAdapter 适配 Handler:DispatcherServlet 根据 Handler 的类型,找到对应的 HandlerAdapter
  6. 执行拦截器的 preHandle 方法 :按顺序执行拦截器链的preHandle()方法
  7. HandlerAdapter 调用 Handler:HandlerAdapter 调用 Handler 的方法,处理业务逻辑
  8. Handler 返回 ModelAndView:Handler 处理完成后,返回 ModelAndView 对象(包含数据和视图名)
  9. 执行拦截器的 postHandle 方法 :按逆序执行拦截器链的postHandle()方法
  10. ViewResolver 解析视图:DispatcherServlet 调用 ViewResolver,根据视图名解析出 View 对象
  11. View 渲染视图:View 对象将 Model 中的数据渲染到视图中,生成 HTML
  12. 执行拦截器的 afterCompletion 方法 :按逆序执行拦截器链的afterCompletion()方法
  13. 返回响应:DispatcherServlet 将渲染后的 HTML 返回给浏览器

关键步骤详解

步骤 4:HandlerMapping 查找 Handler

HandlerMapping 会遍历所有注册的 Handler,找到与请求匹配度最高的那个。匹配规则包括:

  • 请求 URL
  • 请求方法(GET、POST 等)
  • 请求参数
  • 请求头
  • 等等

如果找不到匹配的 Handler,就会返回 404 错误。

步骤 7:HandlerAdapter 调用 Handler

这是最核心的一步,HandlerAdapter 会做以下事情:

  1. 解析请求参数,绑定到 Handler 方法的参数上
  2. 执行参数校验
  3. 调用 Handler 方法
  4. 将方法返回值转换为 ModelAndView 对象

对于@RequestMapping注解的方法,RequestMappingHandlerAdapter会使用反射调用对应的方法。

步骤 10:ViewResolver 解析视图

ViewResolver 会根据视图名和本地化信息,找到对应的 View 对象。例如,InternalResourceViewResolver会将视图名解析为 JSP 文件的路径:

java 复制代码
// 配置InternalResourceViewResolver
@Bean
public InternalResourceViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
}

当视图名为 "index" 时,会解析为/WEB-INF/views/index.jsp

五、@RequestMapping 注解的原理

@RequestMapping是我们每天都在使用的注解,但很少有人知道它的底层原理。它的工作原理可以分为两个阶段:启动时注册运行时匹配

阶段 1:启动时注册

当 Spring 容器启动时,RequestMappingHandlerMapping会扫描所有带有@Controller@RestController注解的类,然后解析类和方法上的@RequestMapping注解:

  1. 提取注解中的 URL、请求方法、参数、头等信息
  2. 将这些信息封装成RequestMappingInfo对象
  3. RequestMappingInfo和对应的HandlerMethod(封装了 Controller 类和方法)注册到映射表中

这样,在启动完成后,RequestMappingHandlerMapping就有了所有请求到处理方法的映射关系。

阶段 2:运行时匹配

当请求到来时,RequestMappingHandlerMapping会:

  1. 提取请求的 URL、请求方法、参数、头等信息
  2. 遍历所有注册的RequestMappingInfo,找到与请求匹配的那个
  3. 如果找到多个匹配的,选择匹配度最高的那个
  4. 返回对应的HandlerMethod和拦截器链

@RequestMapping 的匹配规则

@RequestMapping支持非常灵活的匹配规则:

  • 路径变量@GetMapping("/users/{id}")
  • 通配符@GetMapping("/users/*")匹配/users/1,但不匹配/users/1/2
  • Ant 风格路径@GetMapping("/users/**")匹配所有/users/开头的路径
  • 请求方法限制@PostMapping("/users")只接受 POST 请求
  • 参数限制@GetMapping(value = "/users", params = "type=admin")只接受带有type=admin参数的请求
  • 请求头限制@GetMapping(value = "/users", headers = "X-Requested-With=XMLHttpRequest")只接受 AJAX 请求

六、面试重点深入解析

1. HandlerMapping 的实现原理

RequestMappingHandlerMapping是目前最常用的 HandlerMapping 实现,它的工作原理如下:

  1. 实现了InitializingBean接口,在 Bean 初始化完成后调用afterPropertiesSet()方法
  2. afterPropertiesSet()方法调用initHandlerMethods()方法
  3. initHandlerMethods()方法扫描所有 Bean,找出带有@Controller@RequestMapping注解的类
  4. 对每个类,解析类和方法上的@RequestMapping注解,生成RequestMappingInfo
  5. RequestMappingInfoHandlerMethod注册到mappingRegistry

当请求到来时,RequestMappingHandlerMapping会从mappingRegistry中查找匹配的RequestMappingInfo,然后返回对应的HandlerMethod

2. HandlerAdapter 为什么是必须的?

很多人不理解为什么需要 HandlerAdapter,直接调用 Handler 不行吗?

答案是:不行,因为 Handler 的类型不统一。Spring MVC 设计之初就考虑到了扩展性,支持多种类型的处理器。如果没有 HandlerAdapter,DispatcherServlet 需要知道每种处理器的调用方式,这会导致代码耦合度极高,无法扩展。

HandlerAdapter 使用了适配器模式,将不同类型的处理器适配成统一的HandlerAdapter接口:

java 复制代码
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    long getLastModified(HttpServletRequest request, Object handler);
}

DispatcherServlet 只需要调用supports()方法判断是否支持该处理器,然后调用handle()方法即可,不需要关心处理器的具体类型。

3. ViewResolver 的工作机制

ViewResolver 的工作机制非常简单,它只有一个方法:

java 复制代码
public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

当 DispatcherServlet 需要解析视图时,会遍历所有注册的 ViewResolver,调用resolveViewName()方法,直到找到一个能解析该视图名的 ViewResolver。

Spring MVC 支持多个 ViewResolver 同时存在,可以通过order属性指定它们的优先级。优先级高的 ViewResolver 先被调用,如果返回 null,就继续调用下一个。

4. Spring MVC 与 Servlet 的关系

这是面试最基础也是最容易被忽略的问题:

  1. DispatcherServlet 是 Servlet 的子类 :它继承自HttpServlet,本质上就是一个 Servlet
  2. Spring MVC 是 Servlet 的封装:它在 Servlet 的基础上,提供了请求分发、参数解析、数据绑定、视图渲染等功能
  3. Spring MVC 运行在 Servlet 容器中:它需要 Tomcat、Jetty 等 Servlet 容器才能运行
  4. Spring MVC 的请求处理基于 Servlet 的 service () 方法 :所有请求最终都会调用 DispatcherServlet 的service()方法

七、常见坑点与最佳实践

1. 常见坑点

坑 1:404 错误的常见原因
  • Controller 类没有加@Controller@RestController注解
  • @RequestMapping的 URL 写错了
  • 请求方法不匹配(比如用 GET 请求访问 POST 接口)
  • 参数不匹配
  • 静态资源被 DispatcherServlet 拦截了
坑 2:中文乱码问题
  • 请求参数乱码:配置CharacterEncodingFilter
  • 响应乱码:在@RequestMapping中指定produces = "application/json;charset=UTF-8"
坑 3:静态资源访问问题

Spring MVC 默认会拦截所有请求,导致静态资源(HTML、CSS、JS 等)无法访问。解决方案:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 将/static/**映射到classpath:/static/目录下
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
}
坑 4:文件上传问题
  • 没有配置MultipartResolver
  • 上传文件大小超过了限制
  • 表单的enctype没有设置为multipart/form-data

2. 最佳实践

  1. 使用 RESTful API 设计 :使用@GetMapping@PostMapping等派生注解,而不是通用的@RequestMapping
  2. 统一异常处理 :使用@ControllerAdvice@ExceptionHandler实现全局异常处理
  3. 使用拦截器:对于需要统一处理的逻辑(如权限校验、日志记录),使用拦截器实现
  4. 参数校验 :使用 JSR-303 注解(@NotNull@NotBlank等)进行参数校验
  5. 避免在 Controller 中写业务逻辑:Controller 只负责接收请求和返回响应,业务逻辑应该放到 Service 层
  6. 合理使用视图解析器:根据项目需求选择合适的视图解析器,前后端分离项目不需要视图解析器

八、高频面试题解答

  1. 问:Spring MVC 的工作流程是什么? 答:用户发送请求到 Tomcat,Tomcat 将请求交给 DispatcherServlet。DispatcherServlet 调用 HandlerMapping 找到对应的 Handler 和拦截器链,然后通过 HandlerAdapter 调用 Handler 处理请求。Handler 返回 ModelAndView,DispatcherServlet 通过 ViewResolver 解析视图,最后将渲染后的视图返回给用户。

  2. 问:DispatcherServlet 的作用是什么? 答:DispatcherServlet 是 Spring MVC 的前端控制器,它接收所有的 HTTP 请求,统一分发到对应的处理器处理。它负责初始化 Spring MVC 的核心组件,协调各个组件完成请求处理。

  3. 问:HandlerMapping 和 HandlerAdapter 有什么区别? 答:HandlerMapping 负责根据请求找到对应的处理器,HandlerAdapter 负责适配不同类型的处理器,统一调用方式。简单来说:HandlerMapping 回答 "谁来处理",HandlerAdapter 回答 "怎么处理"。

  4. 问:为什么需要 HandlerAdapter? 答:因为 Spring MVC 支持多种类型的处理器,它们的调用方式不同。HandlerAdapter 使用适配器模式,将不同类型的处理器适配成统一的接口,让 DispatcherServlet 可以用统一的方式调用所有处理器。

  5. 问:ViewResolver 的作用是什么? 答:ViewResolver 负责根据视图名和本地化信息,解析出对应的 View 对象。它将逻辑视图名转换为实际的视图对象,然后由 View 对象负责渲染视图。

  6. 问:Spring MVC 和 Servlet 有什么关系? 答:Spring MVC 是基于 Servlet 的 Web 框架,DispatcherServlet 是 Servlet 的子类。Spring MVC 在 Servlet 的基础上,提供了请求分发、参数解析、数据绑定、视图渲染等功能,大大简化了 Web 开发。

  7. 问:@RequestMapping 注解的原理是什么? 答:启动时,RequestMappingHandlerMapping会扫描所有带有@Controller注解的类,解析@RequestMapping注解,将请求映射信息注册到映射表中。运行时,根据请求信息从映射表中找到对应的处理方法。

九、总结

Spring MVC 是 Java Web 开发的事实标准,它的设计非常优雅,充分体现了开闭原则、单一职责原则等设计模式的思想。

回顾一下全文的核心内容:

  • Spring MVC 本质上是一个基于 Servlet 的 Web 框架,核心是 DispatcherServlet
  • DispatcherServlet 的初始化过程分为四个阶段,最重要的是初始化 9 大核心组件
  • 9 大核心组件各司其职,其中 HandlerMapping、HandlerAdapter、ViewResolver 是最重要的三个
  • 请求处理流程分为 13 个步骤,从请求接收到响应返回,每个步骤都有对应的组件负责
  • @RequestMapping注解的原理分为启动时注册和运行时匹配两个阶段

理解了 Spring MVC 的底层原理,你就能快速定位和解决开发中遇到的各种问题,比如 404 错误、参数绑定失败、视图解析失败等。同时,这些内容也是面试中的高频考点,掌握了它们,你就能轻松应对所有 Spring MVC 相关的面试题。

相关推荐
武子康1 分钟前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
花椒技术44 分钟前
企业内部 Agent 落地复盘:Gateway、Skill 和二次确认如何串起受控业务执行
后端·agent·ai编程
REDcker2 小时前
Linux OverlayFS详解
java·linux·运维
Royzst2 小时前
xml知识点
java·服务器·前端
我是一颗柠檬3 小时前
【MySQL全面教学】MySQL事务与ACID Day9(2026年)
数据库·后端·mysql
枕星而眠3 小时前
数据结构八大排序详解(一):四大简单排序
c语言·数据结构·c++·后端
IT_陈寒3 小时前
React useEffect闭包陷阱差点把我整失业了
前端·人工智能·后端
鱼鳞_3 小时前
苍穹外卖-Day08(缓存套餐)
java·redis·缓存
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
苍何3 小时前
爆肝两周,我把 Codex 最全实战指南开源了
后端