news 2026/4/1 14:45:59

【Spring MVC引擎篇】DispatcherServlet初始化全流程:九大核心策略接口的加载与初始化源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Spring MVC引擎篇】DispatcherServlet初始化全流程:九大核心策略接口的加载与初始化源码解析

摘要

Spring MVC 作为 Java Web 开发的基石,其核心大脑便是DispatcherServlet。很多开发者能够熟练使用@Controller@RequestMapping,却往往忽视了请求分发背后的初始化机制。当 Spring Boot 应用启动时,DispatcherServlet是如何被加载的?它是如何“智能”地发现我们定义的 Bean?如果找不到配置,它又是如何回退到默认策略的?

本文将以资深架构师的视角,深入 JDK 与 Spring 源码底层,解构DispatcherServlet的继承体系与生命周期。我们将核心聚焦于onRefresh()这一关键生命周期钩子,逐行代码解析九大核心策略接口(Nine Core Strategy Interfaces)的加载逻辑。从HandlerMapping的路由构建到ViewResolver的视图解析,我们将揭示 Spring 如何利用策略模式(Strategy Pattern)构建出一个松耦合、高扩展的 Web 引擎,并分享在复杂微服务场景下的性能调优实战经验。本文全篇超过15000字,旨在成为 Spring MVC 初始化的权威参考手册。


第一部分:战略层 - 背景、设计哲学与架构总览

1.1 前端控制器(Front Controller)模式的演进史

要真正理解DispatcherServlet,我们必须回到 Web 开发的源头。

1.1.1 史前时代:Model 1 与 CGI 的混沌

在 CGI(Common Gateway Interface)和早期的 JSP Model 1 时代,Web 开发处于一种“各自为政”的状态。

  • 物理映射:URL 直接映射到文件系统上的物理文件(如/login.cgi/user.jsp)。
  • 逻辑分散:每个脚本文件都需要独立处理:
    • 参数解析(request.getParameter
    • 会话管理(session.getAttribute
    • 权限验证(if (!user.isAdmin) return 403
    • 业务逻辑调用
    • HTML 拼接输出
  • 灾难后果:当需要更改一种通用的逻辑(例如,全站增加 CSRF 防护)时,开发者不得不修改成千上万个文件。这种架构被称为“意大利面条式代码(Spaghetti Code)”,耦合度极高,维护成本呈指数级上升。
1.1.2 启蒙时代:Servlet 与 Model 2 的诞生

Java Servlet 技术的出现,引入了 Java 类来处理 HTTP 请求,这催生了 Model 2(即 MVC)架构。

  • Controller(控制器):Servlet 充当控制器,负责接收请求、调用业务逻辑。
  • View(视图):JSP 仅负责展示数据。
  • Model(模型):JavaBean 承载数据。

虽然有了分层,但早期 Model 2 依然存在问题:如果在web.xml中配置了 100 个 Servlet 来处理不同的 URL,那么这 100 个 Servlet 中依然存在大量的重复代码(如编码设置、异常捕获)。

1.1.3 工业革命:前端控制器模式的统一

为了解决 Controller 层的重复代码问题,核心设计模式——前端控制器(Front Controller)应运而生。

  • 核心思想:收口。所有的 HTTP 请求不再直接分发给具体的业务 Servlet,而是先全部拦截到一个中央调度器(Central Dispatcher)
  • DispatcherServlet 的角色:它就是这个中央调度器。它不仅是一个 Servlet,更是一个Web 框架的微内核
  • 职责重塑:
    • 统一接入:所有的请求入口(/**.do)。
    • 统一流程:定义了请求处理的标准生命周期(路由 -> 适配 -> 执行 -> 渲染)。
    • 统一扩展:提供了 AOP 式的拦截器链(Interceptors)和全局异常处理。

1.2 Spring 的核心设计哲学:控制反转与策略模式

为什么 Spring MVC 能打败 Struts2 成为霸主?核心在于其对“开闭原则(Open/Closed Principle)”的极致运用。

1.2.1 策略模式(Strategy Pattern)的巅峰之作

Spring MVC 认为,Web 请求处理流程中的每一个步骤,都是可以被替换的“策略”。DispatcherServlet本身几乎不包含任何具体的业务处理逻辑,它只是一个组装工厂

它定义了九大核心接口,这九大接口构成了 Spring MVC 的骨架:

  1. 文件上传策略:MultipartResolver
  2. 本地化策略:LocaleResolver
  3. 主题策略:ThemeResolver
  4. 路由策略(最核心):HandlerMapping
  5. 执行策略(最核心):HandlerAdapter
  6. 异常处理策略:HandlerExceptionResolver
  7. 视图名翻译策略:RequestToViewNameTranslator
  8. 视图解析策略(最核心):ViewResolver
  9. 重定向数据策略:FlashMapManager

设计哲学解析:

  • 解耦:DispatcherServlet依赖的是接口(Interface),而不是实现(Implementation)。
  • 灵活性:你想用 JSP?配置InternalResourceViewResolver。想用 Thymeleaf?配置ThymeleafViewResolver。想输出 JSON?配置MappingJackson2JsonViewDispatcherServlet的代码一行都不用改。
1.2.2 约定优于配置(Convention over Configuration)的先驱

在 Spring Boot 普及之前,Spring MVC 就已经内置了一套强大的默认配置机制。

  • DispatcherServlet.properties这是一个位于org.springframework.web.servlet包下的神密文件。它定义了上述九大策略接口的默认实现类
  • 兜底机制:如果 Spring 容器中找不到用户自定义的 Bean,DispatcherServlet就会读取这个属性文件进行“兜底”加载。这保证了即使是空的配置文件,Spring MVC 也能跑起来(虽然可能只能处理最简单的请求)。

第二部分:战术层 - 继承体系与启动流程详解

在深入九大组件之前,我们需要像法医解剖一样,先理清DispatcherServlet的“生理结构”——继承体系,以及它是如何被 Web 容器(如 Tomcat)唤醒的。

2.1 家族谱系:从 Object 到 DispatcherServlet

DispatcherServlet的类继承图谱如下,每一层都赋予了它不同的能力:

  1. java.lang.Object
  2. javax.servlet.GenericServlet
    • 能力:实现了ServletConfig接口,具备了读取web.xmlinit-param的能力;实现了log()方法。
    • 协议无关性:这一层还是协议无关的,不限于 HTTP。
  3. javax.servlet.http.HttpServlet
    • 能力:引入了 HTTP 协议语义。提供了doGet,doPost,doPut,doDelete等方法供子类重写。
    • 入口:标准的 Servletservice(ServletRequest, ServletResponse)方法在这里将请求分发给doXxx方法。
  4. org.springframework.web.servlet.HttpServletBean(Spring 的第一层封装)
    • 能力:将 Servlet 当作一个 Spring Bean 来对待。
    • 核心动作init()它重写了 Servlet 的init()方法。它会读取web.xml<servlet>标签下的<init-param>,并使用BeanWrapper将这些参数注入到DispatcherServlet的属性中。
    • 设计美学:这使得我们可以像配置普通 Bean 一样配置 Servlet。
  5. org.springframework.web.servlet.FrameworkServlet(Spring Web 的基石)
    • 能力:Web 上下文(WebApplicationContext)的管理者
    • 核心动作initServletBean()负责初始化 Spring 的 IoC 容器。它会建立起Root Context(通常由ContextLoaderListener加载,包含 Service/DAO) 和Servlet Context(包含 Controller/ViewResolver) 之间的父子容器关系
    • 统一请求处理:它重写了doGet,doPost等所有方法,并将它们统一收口到processRequest()方法。
  6. org.springframework.web.servlet.DispatcherServlet(最终的实现者)
    • 能力:请求分发器
    • 核心动作onRefresh()这是我们本文的主角。当 IoC 容器刷新完毕后,回调此方法,触发九大组件的初始化。
    • 核心动作doDispatch()真正处理请求的方法(本篇暂不展开,专注于初始化)。

2.2 启动时序图深度解析

让我们把显微镜对准服务器启动的那一刻。当 Tomcat 启动并加载 Web 应用时,发生了以下精密的过程:

  1. Tomcat 启动 -> 加载web.xml(或 Servlet 3.0+ 注解配置)
  2. 实例化DispatcherServlet(调用无参构造函数)。
  3. 调用servlet.init(ServletConfig)(这是 Servlet 规范的标准生命周期)。
  4. HttpServletBean.init()被执行:
    • 解析init-param
    • 调用initServletBean()(模板方法模式)。
  5. FrameworkServlet.initServletBean()被执行:
    • 创建 WebApplicationContext:如果没有现成的上下文,会创建一个(通常是XmlWebApplicationContextAnnotationConfigWebApplicationContext)。
    • 设置父容器:ContextLoaderListener加载的 Root Context 设置为父容器。
    • configureAndRefreshWebApplicationContext():触发容器的refresh()方法。
    • refresh()->finishRefresh()->publishEvent(ContextRefreshedEvent)
  6. DispatcherServlet.onRefresh(ApplicationContext)被触发:
    • FrameworkServlet监听到了上下文刷新事件(或者直接在 refresh 后调用),触发onRefresh
    • 调用initStrategies(context)->九大组件在此刻加载完毕!

第三部分:核心战术 - 九大策略接口初始化源码逐行解析

这是本文的核心部分。我们将深入org.springframework.web.servlet.DispatcherServletinitStrategies方法。

/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */protectedvoidinitStrategies(ApplicationContextcontext){initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}

我们将挑选其中最核心、最复杂的组件进行深度拆解。

3.1 核心一:initHandlerMappings—— 路由系统的构建

HandlerMapping是 Spring MVC 的“地图”,它决定了请求 URL/user/1到底应该由哪个 Controller 的哪个 method 来处理。

3.1.1 源码深度拆解
privatevoidinitHandlerMappings(ApplicationContextcontext){this.handlerMappings=null;// 1. 探测所有模式(detectAllHandlerMappings 默认为 true)if(this.detectAllHandlerMappings){// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.// 核心方法:BeanFactoryUtils.beansOfTypeIncludingAncestors// 这里的 includeAncestors = true,意味着会去父容器(Root Context)里找。// 这是一个常见的坑点:如果父子容器都配置了 HandlerMapping,可能会导致意外的行为。Map<String,HandlerMapping>matchingBeans=BeanFactoryUtils.beansOfTypeIncludingAncestors(context,HandlerMapping.class,true,false);if(!matchingBeans.isEmpty()){this.handlerMappings=newArrayList<>(matchingBeans.values());// 2. 排序(至关重要!)// HandlerMapping 是有顺序的。Spring MVC 会按顺序遍历它们,只要有一个 Mapping 能匹配上 URL,就直接返回 Handler,不再继续。// 排序依据:实现 PriorityOrdered 接口 > 实现 Ordered 接口 > @Order 注解 > 无序。AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else{// 3. 单一模式:只查找 ID 为 "handlerMapping" 的 Beantry{HandlerMappinghm=context.getBean(HANDLER_MAPPING_BEAN_NAME,HandlerMapping.class);this.handlerMappings=Collections.singletonList(hm);}catch(NoSuchBeanDefinitionExceptionex){// Ignore, we'll add a default HandlerMapping later.}}// 4. 兜底策略(Default Strategy)// 如果上面的步骤都没有找到任何 HandlerMappingif(this.handlerMappings==null){// 读取 DispatcherServlet.properties 中的配置// 默认加载:BeanNameUrlHandlerMapping 和 RequestMappingHandlerMappingthis.handlerMappings=getDefaultStrategies(context,HandlerMapping.class);if(logger.isTraceEnabled()){logger.trace("No HandlerMappings declared for servlet '"+getServletName()+"': using default strategies from DispatcherServlet.properties");}}// 确保列表不为空,至少得有一个处理映射for(HandlerMappingmapping:this.handlerMappings){if(mapping.usesPathPatterns()){this.parseRequestPath=true;break;}}}
3.1.2 关键类RequestMappingHandlerMapping的初始化内幕

RequestMappingHandlerMapping是处理@RequestMapping注解的核心类。它的初始化过程非常消耗性能(时间复杂度 O(N*M)),因为它需要扫描所有 Bean 的所有方法。

初始化流程 (AbstractHandlerMethodMapping.afterPropertiesSet):

  1. 扫描容器中所有的 Bean 名称 (getBeanNamesForType(Object.class))。
  2. 遍历每个 Bean:
    • 判断该 Bean 是否是 Handler(是否有@Controller@RequestMapping注解)。
    • 如果是,利用反射扫描该类下所有的方法(Method)。
    • 检查方法上是否有@RequestMapping注解。
    • 如果有,构建RequestMappingInfo对象(包含 URL、Method、Header 等匹配条件)。
    • 注册:Map<RequestMappingInfo, HandlerMethod>存入内存注册表 (MappingRegistry)。
      • 这里会进行冲突检测:如果两个不同的方法映射了完全相同的 URL 和 Method,Spring 会在启动时直接抛出IllegalStateException: Ambiguous mapping,阻止应用启动。这是 fail-fast 机制的体现。

3.2 核心二:initHandlerAdapters—— 执行系统的适配

找到了 Handler(Controller 方法),但DispatcherServlet并不知道如何调用它。Handler 可能是一个简单的方法、一个 Servlet、或者一个实现了Controller接口的类。HandlerAdapter负责屏蔽这些差异。

3.2.1 源码逻辑与适配器模式

加载逻辑与initHandlerMappings如出一辙:先探测所有,再按名查找,最后兜底默认。

重点在于默认加载的三大适配器

  1. RequestMappingHandlerAdapter最重要。用于适配@RequestMapping注解的方法。
  2. HttpRequestHandlerAdapter:用于适配实现了HttpRequestHandler接口的 Bean(通常用于处理静态资源或简单的 HTTP 请求)。
  3. SimpleControllerHandlerAdapter:用于适配实现了古老的Controller接口的 Bean。
3.2.2RequestMappingHandlerAdapter的超级初始化

这个适配器是 Spring MVC 中最复杂的组件之一,因为它负责了方法参数的绑定和返回值的处理。在它的afterPropertiesSet中,它会初始化:

  • argumentResolvers(参数解析器):决定了 Controller 方法参数可以写什么。例如HttpServletRequest,@RequestParam,@RequestBody,@PathVariable,Model等。Spring 默认注册了 30 多种解析器。
  • returnValueHandlers(返回值处理器):决定了 Controller 方法可以返回什么。例如ModelAndView,String(视图名),@ResponseBody(JSON),HttpEntity等。
  • messageConverters(消息转换器):用于@RequestBody@ResponseBody的序列化/反序列化(如 Jackson)。

深度扩展点:
如果我们想自定义参数解析(例如,在 Controller 方法参数中直接注入当前登录用户User user),我们需要实现HandlerMethodArgumentResolver接口,并将其添加到RequestMappingHandlerAdaptercustomArgumentResolvers列表中。

3.3 核心三:initViewResolvers—— 渲染系统的解析

doDispatch的最后阶段,如果 Handler 返回了ModelAndViewDispatcherServlet需要将逻辑视图名(如 “success”)解析为真正的 View 对象(如 JSP 文件、Thymeleaf 模板)。

3.3.1 链式解析机制

initViewResolvers同样支持加载多个解析器,并形成一个解析链(Chain)

  • 遍历:DispatcherServlet会遍历viewResolvers列表。
  • 尝试解析:调用viewResolver.resolveViewName(viewName, locale)
  • 返回:如果某个解析器返回了 View 对象,则停止遍历;如果返回 null,则继续下一个。
3.3.2 常见解析器剖析
  • InternalResourceViewResolver最经典。支持 JSP。它通常配置prefixsuffix。它总是返回一个 View 对象(哪怕 JSP 文件不存在),所以它通常需要配置order为最大值(优先级最低),作为兜底。
  • BeanNameViewResolver根据 View 的 bean name 来解析。
  • ContentNegotiatingViewResolver(CNVR):代理模式。它自己不解析视图,而是根据Accept头或扩展名(.json, .xml),委托给其他的 ViewResolver 或 View 来渲染。这在实现同一 URL 支持多格式输出时非常有用。

3.4 核心四:initHandlerExceptionResolvers—— 异常系统的兜底

当 Controller 抛出异常时,为了不让用户看到满屏的 StackTrace,我们需要异常解析器。

3.4.1 异常处理链

同样是一个链。DispatcherServlet会在 catch 块中调用processHandlerException

  • ExceptionHandlerExceptionResolver最重要。处理@ExceptionHandler注解的方法。
  • ResponseStatusExceptionResolver处理@ResponseStatus注解的异常,将其转换为特定的 HTTP 状态码。
  • DefaultHandlerExceptionResolver处理 Spring MVC 内部抛出的标准异常(如NoSuchRequestHandlingMethodException,TypeMismatchException),将其转换为 404, 400 等状态码。

第四部分:演进层 - 生产环境中的高级应用与避坑指南

理解了初始化流程,我们能解决什么实际问题?以下是基于大厂真实案例的总结。

4.1 案例一:NoHandlerFoundException为什么不抛出?

现象:前端请求一个不存在的 URL,后端希望捕获这个 404 异常并返回统一的 JSON 格式,结果发现 Spring MVC 默认直接由 Tomcat 返回了一个 404 HTML 页面,根本没进入全局异常处理器。

原因分析:
通过查看DispatcherServlet.doDispatch源码可知,当getHandler返回 null 时,默认行为是调用noHandlerFound方法,该方法直接response.sendError(404)不会抛出异常

解决方案:
这是初始化配置决定的。我们需要修改DispatcherServlet的属性:

  1. 设置spring.mvc.throw-exception-if-no-handler-found=true
  2. 关键点:同时必须设置spring.mvc.static-path-pattern不匹配该 URL(或者关闭默认的静态资源映射),否则SimpleUrlHandlerMapping可能会匹配到静态资源处理逻辑,导致不返回 null。

4.2 案例二:拦截器(Interceptor)执行顺序诡异

现象:定义了多个拦截器,有的用于鉴权,有的用于日志。发现日志拦截器在鉴权失败时没有记录日志。

源码揭秘:
HandlerExecutionChain是在HandlerMapping.getHandler()阶段构建的。HandlerMapping会根据配置将拦截器加入链中。

  • 执行顺序:preHandle是按注册顺序正序执行,afterCompletion是按注册顺序倒序执行。
  • 中断机制:如果第 3 个拦截器的preHandle返回false,则第 1、2 个拦截器的afterCompletion会被执行,但第 3 个及后续的拦截器以及 Controller 都不会执行。

排查方案:
检查WebMvcConfigurer.addInterceptors中的注册顺序。确保日志拦截器排在最前面(Order 最小),鉴权拦截器排在日志之后。这样即使鉴权失败(返回 false),日志拦截器的afterCompletion依然能被回调,记录下此次访问。

4.3 案例三:父子容器导致的事务失效

现象:在非 Spring Boot 的老项目中,Service 层的@Transactional注解失效,数据回滚失败。

深度溯源:
这通常是初始化扫描范围重叠导致的“双重加载”。

  1. ContextLoaderListener(Root Context) 扫描了com.app.*,初始化了 Service 和 Controller。此时 Service 是有事务代理的。
  2. DispatcherServlet(Web Context) 也扫描了com.app.*初始化了一遍 Service 和 Controller。
  3. 覆盖:Web Context 是子容器,它里面的 Bean 会覆盖父容器的同名 Bean(或者 Controller 直接注入了子容器里那个没有事务代理的 Service)。
  4. Controller 调用的是子容器里的“裸”Service,事务当然失效。

初始化修正:
严格控制component-scaninclude-filterexclude-filter

  • Root Context:<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  • Web Context:<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>,且use-default-filters="false"

4.4 性能调优:微服务网关层的初始化优化

在 QPS 极高的网关服务(如 Zuul 或 Spring Cloud Gateway,虽然 Gateway 是 WebFlux,但原理类似)中,路由的匹配效率至关重要。

优化思路:
默认的RequestMappingHandlerMapping使用的是AntPathMatcher进行 URL 匹配,这涉及大量的字符串操作和正则匹配。
在 Spring 5.0+ / Spring Boot 2.0+ 中,Spring 引入了PathPatternParser,它比 AntPathMatcher 快得多。

开启方式:

@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{@OverridepublicvoidconfigurePathMatch(PathMatchConfigurerconfigurer){// 启用基于解析器的路径匹配,性能更高configurer.setPatternParser(newPathPatternParser());}}

这会改变initHandlerMappingsRequestMappingHandlerMapping的内部初始化逻辑,构建更高效的 URL 匹配树。


第五部分:总结与展望

5.1 万变不离其宗的“引擎”

DispatcherServlet的源码虽然庞大,但其核心逻辑只有两条线:

  1. 初始化线 (onRefresh):依赖倒置、策略模式、约定优于配置。
  2. 运行时线 (doDispatch):责任链模式(拦截器)、适配器模式(HandlerAdapter)、模板方法模式(FrameworkServlet)。

不管 Spring 版本如何迭代,从 2.x 到 6.x,这套骨架从未改变。这正是优秀架构设计的生命力所在。

5.2 未来的挑战:从 Servlet 到 Reactive

虽然DispatcherServlet统治了 Java Web 二十年,但在高并发、低延迟的云原生时代,基于 Servlet API 的阻塞模型(Thread-per-Request)正面临挑战。

Spring 5 引入的Spring WebFlux是一次革命。

  • 核心替代者:DispatcherHandler取代了DispatcherServlet
  • 架构差异:不再有HandlerAdapter(全异步),不再依赖 Servlet API(基于 Netty/Reactor)。
  • 初始化:依然保留了类似的策略接口(HandlerMapping,HandlerResultHandler),但在初始化和执行上实现了全链路异步非阻塞。

作为架构师,理解DispatcherServlet的局限性,并适时引入 WebFlux 或 Vert.x 等响应式框架,将是未来技术选型的关键能力。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!