SpringMVC 处理请求的流程是怎样的?
结论先行:所有请求都先进 DispatcherServlet,由它的 doDispatch 统一调度:找 handler(HandlerMapping)→ 找适配器(HandlerAdapter)→ 执行(参数解析、调用方法、返回值处理)→ 渲染或直接写响应(ViewResolver / HttpMessageConverter),异常交给 HandlerExceptionResolver,全程被 HandlerInterceptor 环绕。前端控制器模式的好处是:所有横切能力(拦截器、异常处理、参数绑定)都集中在调度器里,Controller 只管业务签名。
一、时序
HTTP 请求
│
▼
FilterChain(字符编码、鉴权、TraceId)
│
▼
DispatcherServlet#doDispatch
├─ 1. getHandler(req) → HandlerExecutionChain(handler + 拦截器链)
│ └─ RequestMappingHandlerMapping:按 @RequestMapping 匹配
├─ 2. getHandlerAdapter(handler)
│ └─ RequestMappingHandlerAdapter
├─ 3. applyPreHandle ← 拦截器 preHandle(false 则中断)
├─ 4. handle() → 参数解析 → 反射调用 Controller → 返回值处理
│ ├─ 参数:@RequestParam/@PathVariable/@RequestBody(HttpMessageConverter)
│ └─ 返回:@ResponseBody 直接写响应 / ModelAndView 走视图
├─ 5. processDispatchResult
│ ├─ 有异常 → HandlerExceptionResolver(@ExceptionHandler、@ControllerAdvice)
│ ├─ ModelAndView → ViewResolver 解析视图名 → render
│ └─ 无视图 → 响应已由 MessageConverter 写出
└─ 6. triggerAfterCompletion ← 拦截器 afterCompletion
二、核心组件
| 组件 | 职责 | 默认实现 |
|---|---|---|
HandlerMapping | 请求 → handler | RequestMappingHandlerMapping |
HandlerAdapter | 统一调用不同形态的 handler | RequestMappingHandlerAdapter |
HandlerInterceptor | 请求前后增强 | 自定义实现 + WebMvcConfigurer |
HandlerExceptionResolver | 异常 → 响应 | ExceptionHandlerExceptionResolver |
HttpMessageConverter | 对象 ↔ JSON/XML | MappingJackson2HttpMessageConverter |
ViewResolver | 视图名 → View | InternalResourceViewResolver |
三、代码:一个完整的可观测拦截器
@Component
public class TraceInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(TraceInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
String traceId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("traceId", traceId);
req.setAttribute("start", System.nanoTime());
return true; // false 则不再执行后续 handler
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp,
Object handler, ModelAndView mav) {
// Controller 正常返回、视图渲染前调用;@ResponseBody 场景也在此前
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp,
Object handler, Exception ex) {
long cost = (System.nanoTime() - (Long) req.getAttribute("start")) / 1_000_000;
log.info("{} {} cost={}ms ex={}", req.getMethod(), req.getRequestURI(), cost,
ex == null ? "-" : ex.getMessage());
MDC.clear(); // 线程池复用,必须清理
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TraceInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/health");
}
}
四、参数与返回值是怎么"变"出来的
- 入参:
HandlerMethodArgumentResolver链。@RequestBody→RequestResponseBodyMethodProcessor→HttpMessageConverter.read;@RequestParam→RequestParamMethodArgumentResolver→request.getParameter;@PathVariable→ 从 URI 模板变量取。 - 返回值:
HandlerMethodReturnValueHandler链。@ResponseBody→ 走HttpMessageConverter.write直接写流;返回String且无@ResponseBody→ 当作视图名,交给ViewResolver。 - 异常:
@ExceptionHandler优先匹配最具体的异常类型,@ControllerAdvice做全局兜底。
五、常见追问
- 问:过滤器(Filter)和拦截器(Interceptor)区别? Filter 属于 Servlet 规范,在
DispatcherServlet之前,拿不到 handler 信息;Interceptor 属于 Spring MVC,能拿到 handler、能访问ModelAndView,且可中断执行。 - 问:
@RequestBody只能读一次流吗? 是。HttpServletRequest#getInputStream不可重复读,若在 Filter 里读过需包装ContentCachingRequestWrapper。 - 问:静态资源怎么处理?
ResourceHttpRequestHandler由SimpleUrlHandlerMapping映射,或配置spring.mvc.static-path-pattern,不会走到业务 Controller。 - 问:异步请求(
DeferredResult/Callable)流程有变化吗? 有。handle()返回后立即释放容器线程,asyncDispatch二次进入doDispatch完成响应,拦截器的afterCompletion在异步完成后才触发。 - 问:404 和 405 分别在哪一步产生? 404 在
getHandler找不到 handler;405 在HandlerAdapter判断请求方法不匹配。
小结
把 doDispatch 的六步背成一条线:找 handler → 找适配器 → 前置拦截 → 执行 → 处理结果/异常 → 完成回调。再补"参数由 ArgumentResolver 解析、返回值由 ReturnValueHandler 处理"这层,就能解释绝大多数"参数收不到、返回值变成 404"的线上问题。