第三篇:SpringMVC——一个HTTP请求在Spring中经历了什么?

前言

在前两篇文章中,我们拆解了Spring的IoC容器和AOP机制------它们解决了对象管理和横切逻辑的问题。但作为一个Web框架,Spring最直接的面孔是SpringMVC:它负责接收HTTP请求,找到对应的Controller方法,执行并返回响应。

面试中,SpringMVC是必考题:

"一个HTTP请求进入Spring后,经历了哪些步骤?"

"DispatcherServlet是什么?它怎么知道该调用哪个Controller?"

"拦截器和过滤器有什么区别?"

"@RequestBody和@ResponseBody是怎么实现JSON自动转换的?"

这些问题考察的不是"会不会写Controller",而是"理不理解请求处理的完整链路"。本文从一个HTTP请求的视角出发,逐层拆解SpringMVC的核心组件和协作流程。

本文核心问题:

  1. DispatcherServlet是什么?它在前端控制器模式中扮演什么角色?
  2. 一个HTTP请求从到达Tomcat到返回响应的完整链路是怎样的?
  3. HandlerMapping、HandlerAdapter、Handler分别做什么?
  4. ViewResolver和消息转换器(HttpMessageConverter)各自解决什么问题?
  5. 拦截器和过滤器有什么区别?各自在什么时机执行?
  6. @RequestBody和@ResponseBody是怎么实现JSON自动转换的?
  7. 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。

相关推荐
shuoshuohaohao7 小时前
MyBatis 小知识点
java·spring·mybatis
要吃这碗饭10 小时前
深入理解 TCP 协议:从三次握手到可靠传输
网络·网络协议·tcp/ip
苏supper11 小时前
Spring Boot多环境配置与打包运行
spring boot·后端·spring
Zelman13 小时前
HTTPS 协议
网络协议·https
JVM高并发16 小时前
使用 Cloudflare Tunnel(本地管理隧道)搭配 DigitalPlat 免费域名实现内网穿透
网络协议·全栈
吴声子夜歌16 小时前
Nginx应用与运维——Nginx HTTP模块详解(动态赋值功能模块)
运维·nginx·http
Zelman16 小时前
网络协议性能调优手册
网络协议·性能优化
Zelman16 小时前
TCP-IP 协议栈设计哲学
网络协议
阿钱真强道17 小时前
28 嵌入式操作系统 | 网关加 TCP 命令通道:粘包/半包怎么切
网络·网络协议·tcp/ip·粘包
hasty18 小时前
一次 WebSocket 握手为何能终止进程?Undici CVE-2026-19534 的异常边界
网络·websocket·网络协议