一个请求进来到底发生了什么?Spring MVC 请求处理全链路源码拆解

一个请求进来到底发生了什么?Spring MVC 请求处理全链路源码拆解

从浏览器点下按钮到 Controller 方法执行,中间经历了多少环节?为什么 @RequestBody 能自动把 JSON 变成对象?为什么返回值自动变成 JSON?为什么 404 长那样?这一篇把 DispatcherServlet 从请求进入到响应返回的完整链路拆给你看。

一、Spring MVC 的核心:DispatcherServlet

所有请求的"总入口"是 DispatcherServlet。它就是一个特殊的 Servlet,拦截所有匹配的 URL,然后统一调度。

javascript 复制代码
用户请求
   │
   ▼
┌──────────────────────────────────────────────────────────────┐
│                     DispatcherServlet                        │
│                                                              │
│  1. HandlerMapping   ──► 找到处理该请求的 Controller 方法     │
│  2. HandlerAdapter   ──► 适配执行 Controller 方法             │
│  3. 参数解析器        ──► 把请求参数变成方法入参                │
│  4. Controller 方法   ──► 执行业务逻辑                         │
│  5. 返回值处理器       ──► 把返回值变成响应数据                  │
│  6. 消息转换器         ──► 把数据序列化成 JSON/XML              │
│  7. HandlerException  ──► 异常统一处理                         │
│  8. ViewResolver     ──► (返回视图时)解析视图                 │
└──────────────────────────────────────────────────────────────┘
   │
   ▼
用户响应

这张图里的 8 个环节,就是 Spring MVC 的全部家当。下面逐个拆。

二、DispatcherServlet 的初始化:9 大组件

DispatcherServlet 在启动时通过 initStrategies() 初始化 9 大组件:

组件 作用 默认实现
MultipartResolver 文件上传解析 StandardServletMultipartResolver
LocaleResolver 国际化 AcceptHeaderLocaleResolver
ThemeResolver 主题 FixedThemeResolver
HandlerMapping 请求→方法映射 RequestMappingHandlerMapping
HandlerAdapter 方法执行适配 RequestMappingHandlerAdapter
HandlerExceptionResolver 异常处理 ExceptionHandlerExceptionResolver
RequestToViewNameTranslator 视图名推断 DefaultRequestToViewNameTranslator
ViewResolver 视图解析 ContentNegotiatingViewResolver
FlashMapManager 重定向传参 SessionFlashMapManager
scss 复制代码
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);    // 1. 文件上传
    initLocaleResolver(context);       // 2. 国际化
    initThemeResolver(context);        // 3. 主题
    initHandlerMappings(context);      // 4. 映射器
    initHandlerAdapters(context);      // 5. 适配器
    initHandlerExceptionResolvers(context); // 6. 异常
    initRequestToViewNameTranslator(context); // 7. 视图名
    initViewResolvers(context);        // 8. 视图
    initFlashMapManager(context);      // 9. Flash
}

启动时找不到自定义实现,就用默认的;找到了,就替换成你的。

三、请求处理主链路:doDispatch()

所有请求都汇聚到 DispatcherServlet.doDispatch()。这是整个 Spring MVC 的心脏:

java 复制代码
protected void doDispatch(HttpServletRequest request,
                          HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    try {
        // 1. 文件上传请求:先转成 MultipartHttpServletRequest
        processedRequest = checkMultipart(request);
        mappedHandler = getHandler(processedRequest);   // 2. 找 Handler(含拦截器链)
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);  // 404
            return;
        }
        // 3. 拿到适配器
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 4. 执行拦截器 preHandle
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        // 5. 真正执行 Controller 方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 6. 执行拦截器 postHandle
        mappedHandler.applyPostHandle(processedRequest, response, mv);
    } catch (Exception ex) {
        dispatchException = ex;   // 统一异常出口
    }
    // 7. 渲染视图 / 写响应体 + 拦截器 afterCompletion
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

核心就一句话:找 Handler(映射)→ 适配执行 → 处理结果。下面拆每一步。

四、HandlerMapping:怎么找到那个方法?

RequestMappingHandlerMapping 在启动时,扫描所有 @Controller,把每个 @RequestMapping 方法解析成一个映射(RequestMappingInfo),存进注册表:

ini 复制代码
@RequestMapping("/order/{id}") + GET
        │
        ▼
RequestMappingInfo{
    patterns = {"/order/{id}"},
    methods  = {GET},
    params   = {},
    headers  = {},
    consumes = {},
    produces = {}
}
        │
        ▼
映射注册表: Map<RequestMappingInfo, HandlerMethod>

请求进来时,通过 getHandlerInternal() 匹配:

  • 按 URL 模板匹配(支持 {id} 路径变量)
  • 再按请求方法(GET/POST)过滤
  • 支持正则、通配符、最佳匹配排序

匹配到以后,返回一个 HandlerExecutionChain------包含 HandlerMethod + 拦截器链

java 复制代码
// 路径变量如何提取?
HandlerMethod handlerMethod = ...;
UrlPathHelper helper = new UrlPathHelper();
Map<String, String> uriVariables = helper.decodeMatrixVariables(...);
// 最后通过 MethodArgumentResolver 注入到 @PathVariable 参数

五、HandlerAdapter:参数是怎么变成方法入参的?

RequestMappingHandlerAdapter 拿着 34 种 HandlerMethodArgumentResolver,挨个判断"这个参数该由谁来解析":

参数类型 解析器 解析逻辑
@PathVariable PathVariableMethodArgumentResolver 从 URI 模板变量取
@RequestParam RequestParamMethodArgumentResolver 从 queryString / form 取
@RequestBody RequestResponseBodyMethodProcessor 读 body → 消息转换器反序列化
@RequestHeader RequestHeaderMethodArgumentResolver 从请求头取
@ModelAttribute ModelAttributeMethodProcessor 绑定表单到对象
@CookieValue ServletCookieValueMethodArgumentResolver 从 Cookie 取
HttpServletRequest ServletRequestMethodArgumentResolver 直接注入原始对象
@RequestPart RequestPartMethodArgumentResolver 文件/多部分参数

@RequestBody 的完整流程:

java 复制代码
// 1. 从 body 读出字节流
byte[] body = readBody(request);
// 2. 根据 Content-Type 找消息转换器
HttpMessageConverter<?> converter = getMessageConverter(request);
// 3. 从字节流反序列化为对象
Object arg = converter.read(type, request);

Spring Boot 默认注册了 MappingJackson2HttpMessageConverter(JSON),所以 @RequestBody OrderDTO 能直接把 JSON 变成对象。这就是你"啥都没配置就能用"的原因。

六、返回值处理器:返回值怎么变成响应?

执行完 Controller 方法后,HandlerMethodReturnValueHandler 决定怎么处理返回值:

返回值 处理器 行为
@ResponseBody 对象 RequestResponseBodyMethodProcessor 消息转换器序列化为 JSON
String(视图名) ViewNameMethodReturnValueHandler 找视图渲染
ModelAndView ModelAndViewMethodReturnValueHandler 直接渲染
ResponseEntity HttpEntityMethodProcessor 带状态码输出
void 响应由 response 对象直接写
java 复制代码
// @ResponseBody 的核心逻辑
public void handleReturnValue(Object returnValue, ...) {
    // 1. 找到能写这个类型的消息转换器
    HttpMessageConverter<?> converter = selectConverter(returnValue);
    // 2. 把对象序列化后写进 response body
    converter.write(returnValue, contentType, response);
}

因为@RestController=@Controller + @ResponseBody`,所以每个方法返回值都会走这条 JSON 输出通道。

七、消息转换器:序列化的最后一公里

HttpMessageConverter 接口的两个核心方法:

java 复制代码
public interface HttpMessageConverter<T> {
    // 能不能读这个类型?
    boolean canRead(Class<?> clazz, MediaType mediaType);
    // 能不能写这个类型?
    boolean canWrite(Class<?> clazz, MediaType mediaType);
    // 反序列化:字节流 → 对象
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage);
    // 序列化:对象 → 字节流
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage);
}

默认注册的一批转换器(按顺序匹配):

转换器 处理内容
ByteArrayHttpMessageConverter 字节数组
StringHttpMessageConverter 文本
FormHttpMessageConverter 表单
MappingJackson2HttpMessageConverter JSON
MappingJackson2XmlHttpMessageConverter XML
ResourceHttpMessageConverter 资源下载

顺序很重要------Spring 会按 getSupportedMediaTypes() 匹配,Accept 头是 JSON 时选 Jackson,是 XML 时选 XML 转换器。

八、异常处理:ExceptionHandlerExceptionResolver

Controller 抛异常后,DispatcherServlet 兜底调用 HandlerExceptionResolver

java 复制代码
Controller 抛异常
   │
   ▼
HandlerExceptionResolver 链
   ├─ ExceptionHandlerExceptionResolver(@ExceptionHandler/@RestControllerAdvice)
   ├─ ResponseStatusExceptionResolver(@ResponseStatus)
   └─ DefaultHandlerExceptionResolver(内置状态码)

@RestControllerAdvice 的全局异常处理器就是第一个 resolver 在工作:

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResult> handleBiz(BusinessException e) {
        return ResponseEntity.badRequest()
            .body(new ErrorResult(e.getCode(), e.getMessage()));
    }
}

匹配逻辑:ExceptionHandlerExceptionResolver 收集所有 @ExceptionHandler 方法,按异常类型最接近优先(子类 > 父类)。

九、一次完整的请求:从进入到响应

把上面的环节拼起来,一个 POST /order 的完整旅程:

kotlin 复制代码
① 请求进入容器(Tomcat)
② 路由到 DispatcherServlet.doDispatch()
③ checkMultipart:不是文件上传,跳过
④ getHandler:匹配 @RequestMapping("/order") + POST
   └─ HandlerExecutionChain = HandlerMethod + 拦截器链
⑤ 拦截器 preHandle → 认证/日志/限流
⑥ RequestMappingHandlerAdapter 解析参数:
   └─ @RequestBody OrderDTO
       └─ MappingJackson2HttpMessageConverter.read()
⑦ 执行 OrderController.create()
   └─ 业务逻辑,返回 OrderVO
⑧ ResponseBody 处理器:
   └─ MappingJackson2HttpMessageConverter.write(JSON)
⑨ 拦截器 postHandle → afterCompletion
⑩ 响应返回浏览器

看到没?你在 Controller 里写的那几行业务代码,只是整个链路里的一小段。 剩下的全部由框架代劳。

十、几个高频面试追问

9.1 为什么 @Transactional 和 @Async 经常失效?在 MVC 里为什么会调用"自身方法"失效?

同一个类内部方法调用不走代理,@Transactional/@Async 都是 AOP 代理拦截的。所以:

java 复制代码
// 错误:内部调用,不走代理
public void outer() {
    this.inner();  // @Transactional 失效
}

// 正确:注入自身代理
public void outer() {
    self.inner();  // 通过代理调用,事务生效
}

9.2 为什么 URL 匹配 /order/{id} 能同时匹配 /order/1/order/abc

路径变量是字符串匹配,{id} 不限制类型。1abc 都是合法字符串,都匹配。类型转换发生在参数解析器(PathVariable)里,转换失败会抛 MethodArgumentTypeMismatchException(400)。

9.3 拦截器(Interceptor)和过滤器(Filter)有什么区别?

Filter Interceptor
规范 Servlet 规范 Spring 框架
时机 进入 DispatcherServlet 之前 DispatcherServlet 之后
能拿到 request/response HandlerMethod(能改目标方法)
调用链 多个 Filter 链式 preHandle → postHandle → afterCompletion

9.4 为什么返回对象是 JSON 而不是视图?

@RestController@ResponseBody 让返回值处理器选择了 JSON 输出通道。而 @Controller 返回 String 会被当成视图名,走 ViewResolver 渲染。

十一、总结

Spring MVC 的请求处理本质是一个流水线:DispatcherServlet 做调度,HandlerMapping 做路由,HandlerAdapter 做参数与执行,消息转换器做序列化,异常解析器兜底。

你只需要关注"我的业务方法怎么写",剩下的框架全帮你编排好了。 理解了这条链路,遇到 404、400、JSON 乱码、参数解析失败这类问题,你就能一眼定位到是哪个环节出了问题。

相关推荐
爱敲键盘的猴子1 小时前
Spring MVC 详解(一):全注解开发与请求响应处理
java·spring·mvc
counting money2 小时前
SpringBoot 项目创建(IDEA不全,还需要修改)
java·spring boot·spring·maven
一水3 小时前
AI 时代审查思维:审查第一篇
java·jvm·数据库·spring
她说可以呀4 小时前
Spring AI 常用 Advisor
人工智能·python·spring
cfm_29144 小时前
Spring AI Advisor 全解
java·大数据·人工智能·spring
lang201509285 小时前
从 J2EE 到云原生:Spring Framework 的设计哲学与演进之路
spring·云原生·java-ee
风流 少年15 小时前
Spring AI 2.0:Advisor
android·人工智能·spring
砍材农夫15 小时前
spring-ai|Spring‑AI 2.0.0 新特性 + 入门教程
spring boot·spring·spring cloud
lv__pf21 小时前
spring之整合mybatis【TL spring 12】
mysql·spring·mybatis