SpringMVC 处理请求的流程是怎样的?

#Java#面试八股

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请求 → handlerRequestMappingHandlerMapping
HandlerAdapter统一调用不同形态的 handlerRequestMappingHandlerAdapter
HandlerInterceptor请求前后增强自定义实现 + WebMvcConfigurer
HandlerExceptionResolver异常 → 响应ExceptionHandlerExceptionResolver
HttpMessageConverter对象 ↔ JSON/XMLMappingJackson2HttpMessageConverter
ViewResolver视图名 → ViewInternalResourceViewResolver

三、代码:一个完整的可观测拦截器

@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"的线上问题。

笔记加载中…