第三篇: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
执行顺序 通过@Orderweb.xmlfilter-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会自动创建ObjectMapperWebMvcAutoConfiguration检测到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。

相关推荐
空中海10 小时前
02 ArkTS 语言与工程规范
java·前端·spring
亚历克斯神10 小时前
Java 25 模式匹配增强:让代码更简洁优雅
java·spring·微服务
云烟成雨TD12 小时前
Spring AI Alibaba 1.x 系列【49】状态图运行时引擎:CompiledGraph 源码解析
java·人工智能·spring
Tutankaaa13 小时前
从10队到50队:知识竞赛软件的高并发场景如何设计?
java·经验分享·后端·spring
云烟成雨TD13 小时前
Spring AI Alibaba 1.x 系列【48】状态图编译配置类:CompileConfig 源码解析
java·人工智能·spring
身如柳絮随风扬15 小时前
MyBatis 与 Spring 中的设计模式
spring·设计模式·mybatis
l软件定制开发工作室16 小时前
Spring开发系列教程(35)——使用Actuator
java·后端·spring
静心观复17 小时前
从短连接到 gRPC:一文读懂 HTTP 连接模型的演进
网络·网络协议·http
DavidSoCool18 小时前
Spring AI Alibaba ReactAgent 调用Tool 实现多轮对话
java·人工智能·spring·多轮对话·reactagent