作为 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)管理:
- 容器启动时加载 Servlet
- 调用
init()方法初始化 Servlet - 每次请求到来时调用
service()方法处理请求 - 容器关闭时调用
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:
- Tomcat 创建 DispatcherServlet 的实例
- 调用 DispatcherServlet 的
init()方法 init()方法调用initServletBean()方法开始 Spring MVC 的初始化
阶段 2:创建 WebApplicationContext
initServletBean()方法会创建 Spring MVC 的 WebApplicationContext(子容器):
- 如果已经存在父容器(Spring 容器),就将其设置为子容器的父容器
- 加载 Spring MVC 的配置文件或配置类
- 刷新子容器,创建所有 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 个步骤,每个步骤都讲清楚做了什么,由哪个组件负责。
完整请求处理流程
- 用户发送请求:用户在浏览器中输入 URL,发送 HTTP 请求到 Tomcat
- Tomcat 接收请求:Tomcat 接收请求,将其交给 DispatcherServlet 处理
- DispatcherServlet 接收请求 :DispatcherServlet 调用
doService()方法处理请求 - HandlerMapping 查找 Handler:DispatcherServlet 调用 HandlerMapping,根据请求找到对应的 Handler 和拦截器链
- HandlerAdapter 适配 Handler:DispatcherServlet 根据 Handler 的类型,找到对应的 HandlerAdapter
- 执行拦截器的 preHandle 方法 :按顺序执行拦截器链的
preHandle()方法 - HandlerAdapter 调用 Handler:HandlerAdapter 调用 Handler 的方法,处理业务逻辑
- Handler 返回 ModelAndView:Handler 处理完成后,返回 ModelAndView 对象(包含数据和视图名)
- 执行拦截器的 postHandle 方法 :按逆序执行拦截器链的
postHandle()方法 - ViewResolver 解析视图:DispatcherServlet 调用 ViewResolver,根据视图名解析出 View 对象
- View 渲染视图:View 对象将 Model 中的数据渲染到视图中,生成 HTML
- 执行拦截器的 afterCompletion 方法 :按逆序执行拦截器链的
afterCompletion()方法 - 返回响应:DispatcherServlet 将渲染后的 HTML 返回给浏览器

关键步骤详解
步骤 4:HandlerMapping 查找 Handler
HandlerMapping 会遍历所有注册的 Handler,找到与请求匹配度最高的那个。匹配规则包括:
- 请求 URL
- 请求方法(GET、POST 等)
- 请求参数
- 请求头
- 等等
如果找不到匹配的 Handler,就会返回 404 错误。
步骤 7:HandlerAdapter 调用 Handler
这是最核心的一步,HandlerAdapter 会做以下事情:
- 解析请求参数,绑定到 Handler 方法的参数上
- 执行参数校验
- 调用 Handler 方法
- 将方法返回值转换为 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注解:
- 提取注解中的 URL、请求方法、参数、头等信息
- 将这些信息封装成
RequestMappingInfo对象 - 将
RequestMappingInfo和对应的HandlerMethod(封装了 Controller 类和方法)注册到映射表中
这样,在启动完成后,RequestMappingHandlerMapping就有了所有请求到处理方法的映射关系。
阶段 2:运行时匹配
当请求到来时,RequestMappingHandlerMapping会:
- 提取请求的 URL、请求方法、参数、头等信息
- 遍历所有注册的
RequestMappingInfo,找到与请求匹配的那个 - 如果找到多个匹配的,选择匹配度最高的那个
- 返回对应的
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 实现,它的工作原理如下:
- 实现了
InitializingBean接口,在 Bean 初始化完成后调用afterPropertiesSet()方法 afterPropertiesSet()方法调用initHandlerMethods()方法initHandlerMethods()方法扫描所有 Bean,找出带有@Controller或@RequestMapping注解的类- 对每个类,解析类和方法上的
@RequestMapping注解,生成RequestMappingInfo - 将
RequestMappingInfo和HandlerMethod注册到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 的关系
这是面试最基础也是最容易被忽略的问题:
- DispatcherServlet 是 Servlet 的子类 :它继承自
HttpServlet,本质上就是一个 Servlet - Spring MVC 是 Servlet 的封装:它在 Servlet 的基础上,提供了请求分发、参数解析、数据绑定、视图渲染等功能
- Spring MVC 运行在 Servlet 容器中:它需要 Tomcat、Jetty 等 Servlet 容器才能运行
- 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. 最佳实践
- 使用 RESTful API 设计 :使用
@GetMapping、@PostMapping等派生注解,而不是通用的@RequestMapping - 统一异常处理 :使用
@ControllerAdvice和@ExceptionHandler实现全局异常处理 - 使用拦截器:对于需要统一处理的逻辑(如权限校验、日志记录),使用拦截器实现
- 参数校验 :使用 JSR-303 注解(
@NotNull、@NotBlank等)进行参数校验 - 避免在 Controller 中写业务逻辑:Controller 只负责接收请求和返回响应,业务逻辑应该放到 Service 层
- 合理使用视图解析器:根据项目需求选择合适的视图解析器,前后端分离项目不需要视图解析器
八、高频面试题解答
-
问:Spring MVC 的工作流程是什么? 答:用户发送请求到 Tomcat,Tomcat 将请求交给 DispatcherServlet。DispatcherServlet 调用 HandlerMapping 找到对应的 Handler 和拦截器链,然后通过 HandlerAdapter 调用 Handler 处理请求。Handler 返回 ModelAndView,DispatcherServlet 通过 ViewResolver 解析视图,最后将渲染后的视图返回给用户。
-
问:DispatcherServlet 的作用是什么? 答:DispatcherServlet 是 Spring MVC 的前端控制器,它接收所有的 HTTP 请求,统一分发到对应的处理器处理。它负责初始化 Spring MVC 的核心组件,协调各个组件完成请求处理。
-
问:HandlerMapping 和 HandlerAdapter 有什么区别? 答:HandlerMapping 负责根据请求找到对应的处理器,HandlerAdapter 负责适配不同类型的处理器,统一调用方式。简单来说:HandlerMapping 回答 "谁来处理",HandlerAdapter 回答 "怎么处理"。
-
问:为什么需要 HandlerAdapter? 答:因为 Spring MVC 支持多种类型的处理器,它们的调用方式不同。HandlerAdapter 使用适配器模式,将不同类型的处理器适配成统一的接口,让 DispatcherServlet 可以用统一的方式调用所有处理器。
-
问:ViewResolver 的作用是什么? 答:ViewResolver 负责根据视图名和本地化信息,解析出对应的 View 对象。它将逻辑视图名转换为实际的视图对象,然后由 View 对象负责渲染视图。
-
问:Spring MVC 和 Servlet 有什么关系? 答:Spring MVC 是基于 Servlet 的 Web 框架,DispatcherServlet 是 Servlet 的子类。Spring MVC 在 Servlet 的基础上,提供了请求分发、参数解析、数据绑定、视图渲染等功能,大大简化了 Web 开发。
-
问:@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 相关的面试题。