在 web.xml 文件中给 Spring MVC 的 Servlet 配置了 load-on-startup,所以程序启动的时候会初始化 Spring MVC,在 HttpServletBean 中将配置的 contextConfigLocation 属性设置到 Servlet 中,然后在 FrameworkServlet 中创建了 WebApplicationContext,DispatcherServlet 根据 contextConfigLocation 配置的 classpath 下的 xml 文件初始化了 Spring MVC 总的组件。

这段描述对应传统 war 包 + web.xml 时代。今天主流是 Spring Boot 内嵌容器 + 自动配置,启动链路已完全不同;但两条链路的终点相同:DispatcherServlet 初始化时完成九大组件的装配。本文双链路对照讲清,并落到”组件初始化”这一共同核心。

传统链路:web.xml + 三层继承

  1. 容器(Tomcat)启动,读 web.xml,按 load-on-startup 顺序实例化 Servlet
  2. HttpServletBean.init:把 init-param(contextConfigLocation 等)注入 servlet 属性(BeanWrapper)
  3. FrameworkServlet.initServletBean:创建/刷新 WebApplicationContext(关联父容器 root context——ContextLoaderListener 先一步用 contextConfigLocation 建好 Spring 根容器)
  4. DispatcherServlet.onRefresh:initStrategies——初始化九大组件(见下)

继承链 HttpServletBean → FrameworkServlet → DispatcherServlet 各自负责:属性注入 → 上下文创建 → MVC 组件装配。

Boot 链路:内嵌容器 + 自动配置

  1. SpringApplication.run:创建 ApplicationContext(AnnotationConfigServletWebServerApplicationContext),加载自动配置
  2. ServletWebServerApplicationContext.onRefresh 中创建内嵌 Tomcat(TomcatWebServer),注册 DispatcherServlet(DispatcherServletRegistrationBean,url-pattern 默认 /)
  3. 容器启动触发 DispatcherServlet.init → 同样走 HttpServletBean → FrameworkServlet → onRefresh 链路;上下文已由 Boot 主容器提供(不再单独建 xml 上下文)
  4. 九大组件来自自动配置默认值 + 用户 Bean 覆盖(WebMvcAutoConfiguration / WebMvcConfigurer)

差异本质:传统链路”Servlet 自己建上下文”,Boot 链路”上下文先存在、Servlet 挂进去”;组件装配时机与内容一致。

共同核心:onRefresh 的九大组件初始化

DispatcherServlet.initStrategies 按序初始化:

  1. MultipartResolver:文件上传解析
  2. LocaleResolver:国际化区域
  3. ThemeResolver:主题(已边缘化)
  4. HandlerMapping:注册 RequestMappingHandlerMapping(扫描 @RequestMapping)等
  5. HandlerAdapter:RequestMappingHandlerAdapter(含参数解析器/转换器集合)
  6. HandlerExceptionResolver:异常解析链(含 @ControllerAdvice 支持)
  7. RequestToViewNameTranslator:隐式视图名
  8. ViewResolver:视图解析(REST 项目闲置)
  9. FlashMapManager:重定向闪传属性

每个组件的获取策略:容器中有用户定义的用用户的,没有则用默认策略(DispatcherServlet.properties)——这是 Spring “约定优于配置 + 可覆盖” 的又一实例。

启动期常见考点与排障

  • RequestMappingHandlerMapping 的注册时机:onRefresh 中初始化组件时,从上下文收集所有 @Controller bean 的方法映射——所以”映射没生效”先查 bean 是否被扫描/条件注解是否满足
  • load-on-startup 的值:决定 Servlet 初始化顺序(DispatcherServlet 应早于业务 Servlet);Boot 中由注册 bean 的 loadOnStartup 属性控制
  • 启动慢定位:内嵌容器启动 vs 上下文 refresh(bean 实例化)vs 组件初始化,分段看日志时间戳
  • 404 排查链:Servlet 映射(/ vs /*)→ HandlerMapping 是否收录该方法(public?@RequestMapping?)→ 拦截器是否拦截

两条链路的选择今天意味着什么

新项目只有 Boot 链路;理解传统链路的价值在于:维护老 war 项目、理解 WebApplicationContext 父子容器结构(root 与 servlet context 的 bean 可见性:servlet 可见 root,反之不可见)、以及读懂任何 Spring Web 源码中 FrameworkServlet 的行为。

启动流程的终点永远是同一句话:DispatcherServlet 在 onRefresh 中把九大组件装配完毕,此后每个请求都按运行流程在这套组件间流转——启动是装配,运行是调度,两篇合起来即 Spring MVC 全貌。