前言
在前两篇文章中,我们拆解了Spring的IoC容器和AOP机制------它们解决了对象管理和横切逻辑的问题。但作为一个Web框架,Spring最直接的面孔是SpringMVC:它负责接收HTTP请求,找到对应的Controller方法,执行并返回响应。
面试中,SpringMVC是必考题:
"一个HTTP请求进入Spring后,经历了哪些步骤?"
"DispatcherServlet是什么?它怎么知道该调用哪个Controller?"
"拦截器和过滤器有什么区别?"
"@RequestBody和@ResponseBody是怎么实现JSON自动转换的?"
这些问题考察的不是"会不会写Controller",而是"理不理解请求处理的完整链路"。本文从一个HTTP请求的视角出发,逐层拆解SpringMVC的核心组件和协作流程。
本文核心问题:
- DispatcherServlet是什么?它在前端控制器模式中扮演什么角色?
- 一个HTTP请求从到达Tomcat到返回响应的完整链路是怎样的?
- HandlerMapping、HandlerAdapter、Handler分别做什么?
- ViewResolver和消息转换器(HttpMessageConverter)各自解决什么问题?
- 拦截器和过滤器有什么区别?各自在什么时机执行?
- @RequestBody和@ResponseBody是怎么实现JSON自动转换的?
- SpringBoot是如何自动配置SpringMVC的?
读完本文,你将对SpringMVC的请求处理拥有从入口到响应的完整理解。
一、SpringMVC的核心架构------前端控制器模式
疑问:SpringMVC最核心的组件是什么?整个框架是怎么协调工作的?
回答:SpringMVC采用前端控制器模式------DispatcherServlet作为统一入口接收所有请求,然后将请求分发给具体的Controller处理。
1.1 没有前端控制器时
每个Servlet处理一种请求:
/user/add → UserAddServlet
/user/delete → UserDeleteServlet
/order/create → OrderCreateServlet
问题:每增加一个接口就要新增一个Servlet类,需要做很多重复的请求参数解析和响应封装。
1.2 有了前端控制器
DispatcherServlet(唯一的Servlet)
│
├── /user/add → UserController.add()
├── /user/delete → UserController.delete()
└── /order/create → OrderController.create()
一个DispatcherServlet接收所有请求,分发给对应的Controller方法。
参数解析、返回值处理、异常处理全部统一管理。
1.3 核心组件与职责
DispatcherServlet
│
├── HandlerMapping(处理器映射器)
│ 作用:根据请求URL找到对应的Handler(Controller方法)
│ 实现:RequestMappingHandlerMapping 解析@GetMapping、@PostMapping等
│
├── HandlerAdapter(处理器适配器)
│ 作用:执行具体的Handler,处理参数绑定和返回值处理
│ 实现:RequestMappingHandlerAdapter 处理@Controller注解的类
│
├── HandlerInterceptor(拦截器)
│ 作用:在Handler执行前后进行拦截处理
│
├── HttpMessageConverter(消息转换器)
│ 作用:将请求体转换为Java对象(@RequestBody),将Java对象转换为响应体(@ResponseBody)
│ 实现:MappingJackson2HttpMessageConverter 处理JSON
│
└── ViewResolver(视图解析器)
作用:将逻辑视图名解析为实际的视图(JSP、Thymeleaf等)
注意:前后端分离后,这个组件逐渐被消息转换器替代
二、一个HTTP请求的完整链路
疑问:从浏览器发送请求到返回响应,SpringMVC到底经历了什么?
回答:整个流程可以拆解为九个核心步骤,每个步骤都有专门的组件负责。
2.1 九个步骤
HTTP请求 → Tomcat → Filter链 → DispatcherServlet → 响应
DispatcherServlet内部处理步骤:
步骤1:doService() 接收请求
↓
步骤2:doDispatch() 开始分发
↓
步骤3:getHandler()
└── HandlerMapping 根据请求URL找到对应的Handler
└── 返回 HandlerExecutionChain(Handler + 拦截器列表)
↓
步骤4:getHandlerAdapter()
└── 找到支持该Handler的HandlerAdapter
↓
步骤5:applyPreHandle()
└── 执行所有拦截器的preHandle()方法
└── 如果某个拦截器返回false → 请求终止
↓
步骤6:handle()
└── HandlerAdapter调用Handler(Controller方法)
├── 参数解析:@RequestParam、@PathVariable、@RequestBody等
└── 返回值处理:@ResponseBody、视图名等
↓
步骤7:applyPostHandle()
└── 执行所有拦截器的postHandle()方法
↓
步骤8:processDispatchResult()
└── 处理返回值:视图渲染 / 消息转换器写JSON
↓
步骤9:afterCompletion()
└── 执行所有拦截器的afterCompletion()方法(视图渲染完成后)
2.2 各步骤对应的源码位置
| 步骤 | 核心方法 | 作用 |
|---|---|---|
| 获取Handler | getHandler(request) |
遍历所有HandlerMapping,找到第一个能处理当前请求的 |
| 获取HandlerAdapter | getHandlerAdapter(handler) |
遍历所有HandlerAdapter,找到支持当前Handler的 |
| 执行拦截器前置 | applyPreHandle(request, response) |
按顺序执行preHandle,有一个返回false即中断 |
| 执行Handler | adapter.handle(request, response, handler) |
参数解析→调用方法→处理返回值 |
| 执行拦截器后置 | applyPostHandle(request, response, mv) |
逆序执行postHandle |
| 处理结果 | processDispatchResult(request, response, mappedHandler, mv, dispatchException) |
视图渲染或JSON序列化 |
三、HandlerMapping------谁来决定调用哪个Controller?
疑问:一个请求URL过来,SpringMVC怎么知道该调用哪个Controller的哪个方法?
回答:HandlerMapping负责建立"请求URL→Controller方法"的映射关系。最常用的是RequestMappingHandlerMapping,它解析@GetMapping、@PostMapping、@RequestMapping等注解,建立路径到方法的映射表。
3.1 RequestMappingHandlerMapping的工作原理
Spring容器启动时:
1. 扫描所有@Controller注解的类
2. 遍历每个类中@GetMapping、@PostMapping等注解的方法
3. 将路径+请求方法 → Method 的映射存入内部Map
请求到来时:
1. 根据请求的URL和HTTP方法查找Map
2. 找到对应的HandlerMethod
3. 将HandlerMethod和拦截器列表包装成HandlerExecutionChain返回
3.2 路径匹配规则
java
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/{id}") // GET /api/user/123
public User getById(@PathVariable Long id) { ... }
@PostMapping // POST /api/user
public User create(@RequestBody UserReq req) { ... }
@DeleteMapping("/{id}") // DELETE /api/user/123
public void delete(@PathVariable Long id) { ... }
}
HandlerMapping内部维护的映射表大致是:
GET /api/user/{id} → UserController.getById()
POST /api/user → UserController.create()
DELETE /api/user/{id} → UserController.delete()
四、HandlerAdapter------参数是怎么解析的?
疑问:HandlerMapping找到了要调用的方法,但方法的参数(@RequestParam、@RequestBody、@PathVariable)是怎么自动填进去的?
回答:HandlerAdapter不仅要调用Controller方法,还要负责解析方法参数、处理返回值。不同类型的参数由不同的ArgumentResolver处理。
4.1 HandlerAdapter的职责
HandlerAdapter.handle()做了三件事:
1. 参数解析:将HTTP请求中的参数转换成Controller方法的入参
2. 调用方法:反射调用Controller方法
3. 返回值处理:将方法返回值转换成HTTP响应
4.2 参数解析器(ArgumentResolver)家族
| 参数注解 | 解析器 | 数据来源 |
|---|---|---|
@RequestParam |
RequestParamMethodArgumentResolver | URL查询参数或表单参数 |
@PathVariable |
PathVariableMethodArgumentResolver | URL路径中的变量 |
@RequestBody |
RequestResponseBodyMethodProcessor | 请求体(通过消息转换器解析) |
@RequestHeader |
RequestHeaderMethodArgumentResolver | HTTP请求头 |
@ModelAttribute |
ModelAttributeMethodProcessor | 表单参数绑定到对象 |
| 无注解的普通参数 | 根据类型自动匹配解析器 | --- |
4.3 返回值处理器(ReturnValueHandler)
| 返回值注解 | 处理器 | 处理方式 |
|---|---|---|
@ResponseBody |
RequestResponseBodyMethodProcessor | 通过消息转换器序列化为JSON写入响应体 |
@ResponseStatus |
--- | 设置HTTP状态码 |
| 无注解返回String | ViewNameMethodReturnValueHandler | 解析为视图名 |
| 无注解返回ModelAndView | --- | 直接使用其中的视图和模型 |
五、HttpMessageConverter------JSON是怎么自动转换的?
疑问:@RequestBody怎么能把JSON字符串自动变成Java对象?@ResponseBody怎么把Java对象自动变成JSON?
回答:HttpMessageConverter是SpringMVC中的消息转换器------它负责HTTP请求体和响应体与Java对象之间的双向转换。MappingJackson2HttpMessageConverter专门处理JSON。
5.1 工作原理
请求阶段(@RequestBody):
请求体JSON字符串 → 找到合适的HttpMessageConverter(根据Content-Type)
→ read() 方法反序列化 → 生成Java对象 → 作为Controller方法参数
响应阶段(@ResponseBody):
Controller方法返回Java对象 → 找到合适的HttpMessageConverter(根据Accept头)
→ write() 方法序列化 → 生成JSON字符串 → 写入响应体
5.2 SpringBoot默认注册的转换器
| 转换器 | Content-Type | 作用 |
|---|---|---|
| MappingJackson2HttpMessageConverter | application/json | JSON↔Java对象 |
| StringHttpMessageConverter | text/plain | 字符串读写 |
| ByteArrayHttpMessageConverter | application/octet-stream | 字节数组读写 |
5.3 自定义转换器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 自定义日期格式
converter.setObjectMapper(new ObjectMapper()
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
converters.add(converter);
}
}
六、拦截器 vs 过滤器
疑问:拦截器和过滤器有什么区别?什么时候用拦截器,什么时候用过滤器?
回答:过滤器是Servlet规范定义的,运行在Servlet容器层面,可以拦截所有请求(包括静态资源)。拦截器是SpringMVC定义的,运行在DispatcherServlet内部,只能拦截进入SpringMVC的请求。
6.1 核心区别
| 维度 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 规范 | Servlet规范 | SpringMVC特有 |
| 执行时机 | 在Servlet之前后 | 在Handler(Controller方法)之前后 |
| 作用范围 | 所有请求(包括静态资源) | 只拦截进入DispatcherServlet的请求 |
| IoC支持 | 不能直接注入Spring Bean | 可以直接注入Spring Bean |
| 执行顺序 | 通过@Order或web.xml的filter-mapping顺序控制 |
通过addInterceptor的注册顺序控制 |
6.2 执行顺序
请求 → Filter1 → Filter2 → DispatcherServlet → Interceptor1.preHandle
→ Interceptor2.preHandle
→ Handler(Controller方法)
→ Interceptor2.postHandle
→ Interceptor1.postHandle
→ 视图渲染/JSON序列化
→ Interceptor2.afterCompletion
→ Interceptor1.afterCompletion
→ Filter2 → Filter1 → 响应
记忆口诀:Filter包在最外层,进出都经过。Interceptor包在Handler外层,前置顺序执行、后置逆序执行。afterCompletion在视图渲染完成后执行,适合做资源清理。
6.3 什么时候用什么?
| 场景 | 选择 | 原因 |
|---|---|---|
| 字符编码设置 | Filter | 需要在SpringMVC之前处理 |
| Spring Security安全校验 | Filter | 独立于SpringMVC,保护所有资源 |
| 用户登录校验 | Interceptor | 可以注入UserService等Bean |
| 操作日志记录 | Interceptor | 可以获取到Controller方法和参数 |
| 跨域处理(CORS) | Filter | 需要在请求最早期处理 |
七、SpringBoot对SpringMVC的自动配置
疑问:SpringBoot项目中没有写任何SpringMVC配置,它是怎么工作的?
回答:SpringBoot通过WebMvcAutoConfiguration自动配置了SpringMVC所需的核心组件,包括DispatcherServlet、HandlerMapping、HandlerAdapter、HttpMessageConverter等。
7.1 自动配置做了什么
WebMvcAutoConfiguration 自动配置:
1. 注册 DispatcherServlet
2. 配置 RequestMappingHandlerMapping(路径映射)
3. 配置 RequestMappingHandlerAdapter(参数解析和返回值处理)
4. 配置 HttpMessageConverter(JSON转换,自动引入Jackson)
5. 配置 ViewResolver(视图解析,默认是Thymeleaf或InternalResourceViewResolver)
6. 配置静态资源处理(/static、/public、/resources等目录)
7.2 为什么引入Jackson依赖就能处理JSON?
SpringBoot的JacksonAutoConfiguration会自动创建ObjectMapper。WebMvcAutoConfiguration检测到ObjectMapper存在后,自动注册MappingJackson2HttpMessageConverter。这就是为什么只引入Jackson依赖,@RequestBody和@ResponseBody就能自动处理JSON。
八、面试中这样回答
面试官:"SpringMVC的请求处理流程是怎样的?"
回答框架:
"所有请求统一由DispatcherServlet接收。第一步通过HandlerMapping找到处理请求的Handler------通常是Controller中对应的方法。第二步通过HandlerAdapter执行这个Handler------在调用之前会解析请求参数(@RequestParam、@RequestBody等),调用之后会处理返回值(视图名或@ResponseBody的JSON序列化)。第三步处理响应------如果是页面请求走ViewResolver渲染视图,前后端分离场景则通过HttpMessageConverter将返回值序列化为JSON写入响应体。整个过程前后还有拦截器链和过滤器链的包裹------Filter先于Interceptor执行。"
面试官:"拦截器和过滤器有什么区别?"
回答:
"过滤器是Servlet规范定义的,运行在DispatcherServlet之外,可以拦截所有请求包括静态资源。拦截器是SpringMVC特有的,运行在DispatcherServlet内部,只能拦截进入SpringMVC的请求。过滤器不能直接注入Spring Bean,拦截器可以。执行顺序上过滤器在最外层包裹整个Servlet处理过程,拦截器在DispatcherServlet内部包裹Handler的执行。"
总结
- DispatcherServlet是SpringMVC的核心------它采用前端控制器模式,统一接收所有请求并按标准步骤分发处理
- 请求处理的三个关键组件:HandlerMapping负责路由定位,HandlerAdapter负责参数解析和反射调用,HttpMessageConverter负责JSON的双向序列化
- @RequestBody通过MappingJackson2HttpMessageConverter反序列化JSON为Java对象,@ResponseBody通过同一个转换器完成对象的JSON序列化。这一对组件让前后端分离的自然语言交互成为可能
- Filter在Servlet容器层,拦截所有请求 ;Interceptor在SpringMVC内部,只能拦截进入DispatcherServlet的请求。两者的执行时机和作用范围不同
- SpringBoot的WebMvcAutoConfiguration自动配置了所有核心组件------你不需要写任何XML配置,引入Jackson依赖即自动获得JSON处理能力
下一篇预告 :Spring原理(四)------SpringBoot自动配置:约定大于配置的底层原理。拆解@SpringBootApplication背后的@EnableAutoConfiguration是如何通过spring.factories加载配置类的,以及如何自定义一个starter。