一个请求进来到底发生了什么?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} 不限制类型。1 和 abc 都是合法字符串,都匹配。类型转换发生在参数解析器(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 乱码、参数解析失败这类问题,你就能一眼定位到是哪个环节出了问题。