Java面试——Spring MVC的执行流程详解

DispatcherServlet 是总调度员,负责拦截请求并协调 HandlerMapping、HandlerAdapter、ViewResolver 等组件;其启动依赖 Servlet 容器注册,初始化时加载默认组件,请求匹配、参数解析、视图渲染等均按固定顺序执行。

Spring MVC 的执行流程不是靠背出来的,而是得看清楚每个组件在什么时机、以什么方式参与进来。核心结论就一条:DispatcherServlet 是总调度员,它不干活,但所有活都得它点头才能开始。

DispatcherServlet 怎么启动并接管请求?

它本质是个 Servlet,靠 web.xml 或 Spring Boot 的自动配置注册进 Servlet 容器(如 Tomcat)。一旦注册成功,所有匹配 //*.do 等 pattern 的 HTTP 请求,都会被它拦截。

关键点:

  • DispatcherServlet 初始化时会主动从 ApplicationContext 中查找并加载默认组件(如 HandlerMappingHandlerAdapterViewResolver),也可以通过配置显式指定
  • 它本身不解析 URL 路径,而是把请求丢给注册的 HandlerMapping 实例去匹配
  • 如果没配任何 HandlerMapping,它会用默认的 RequestMappingHandlerMapping —— 这就是为什么加了 @Controller + @RequestMapping 就能响应请求

HandlerMapping 找不到 handler 会怎样?

常见现象是返回 404,但原因可能不止“路径写错了”。真实排查顺序应该是:

  • 确认请求 URL 是否匹配 DispatcherServleturl-pattern(比如 Spring Boot 默认是 /**,而老项目常配成 /app/*,这时访问 /api/xxx 就根本进不来)
  • 确认目标类是否被 Spring 扫描到:@Controller

    类必须在 @ComponentScan 范围内,且不能是普通 new 出来的对象
  • 确认方法上的注解是否有效:@GetMapping("/user") 等价于 @RequestMapping(value = "/user", method = RequestMethod.GET);若写了 method = POST 却用 GET 访问,也会 404(实际是 405,但前端常误判为 404)
  • 注意路径拼接:若类上写了 @RequestMapping("/api"),方法上写了 @GetMapping("v1/user"),完整路径是 /api/v1/user,不是 /api//v1/user(多斜杠一般会被容器 normalize,但容易干扰判断)

HandlerAdapter 执行 Controller 方法时传参怎么来的?

不是反射一调就完事。参数值来源五花八门,HandlerAdapter(实际是 RequestMappingHandlerAdapter)会根据参数类型和注解,委托给不同的 HandlerMethodArgumentResolver

  • @RequestParam → 从 query string 或 form data 取值(注意:默认 required = true,没传就抛 MissingServletRequestParameterException
  • @PathVariable → 从 URI 模板中提取(如 /user/{id} 中的 id
  • @RequestBody → 用 HttpMessageConverter(如 Jackson2ObjectMapper)反序列化请求体,要求 Content-Type 是 application/json 等可识别类型
  • 无注解的 POJO 参数 → 自动做数据绑定(property match),支持嵌套对象、集合,但字段名必须严格匹配(大小写敏感)

最容易踩的坑是:前端传了 {"userName":"abc"},后端用 User user 接收,但 User 里字段叫 username(小写),结果绑定失败为 null —— 因为默认不忽略大小写,也不自动下划线转驼峰。

ModelAndView 和 ViewResolver 怎么配合渲染页面?

Controller 方法返回 String(如 "user/list")或 ModelAndView,只是逻辑视图名,不是真实路径。真正定位 HTML 或 JSP 文件的是 ViewResolver

  • InternalResourceViewResolver 最常用,会拼前缀后缀:prefix="/WEB-INF/views/" + "user/list" + suffix=".jsp"/WEB-INF/views/user/list.jsp
  • ThymeleafViewResolver 不拼后缀,但要求模板文件名必须严格等于视图名(user/list.html
  • 返回 ResponseEntity> 或加了 @ResponseBody,则跳过 ViewResolver,直接走 HttpMessageConverter 写响应体

一个典型故障:明明有 /templates/user/list.html,却报 TemplateInputException: Error resolving template "user/list" —— 很可能是 spring.thymeleaf.prefix 配成了 classpath:/templates/,但实际路径是 src/main/resources/templates/(Maven 标准结构没问题),问题出在 IDE 没刷新 resources 或打包时漏了该目录。

整个流程里最易被忽略的,是 HandlerInterceptor 的执行时机:它夹在 HandlerMapping 找到 handler 之后、HandlerAdapter 执行之前,且 preHandle 返回 false 会直接中断流程,连 controller 都不会进 —— 日志里既看不到 controller 入口,也看不到异常堆栈,只看到请求“静默消失”。