Spring MVC 中 Bean 的加载本质上是两个有层级关系的IoC 容器ApplicationContext的启动过程。它们由ContextLoaderListener和DispatcherServlet分别负责创建并与底层的Servlet 容器如 Tomcat紧密集成。我按照从总体架构到源码细节的顺序来梳理。第一阶段根容器的加载ContextLoaderListener第二阶段DispatcherServlet 的加载Web容器️ 总体架构双重容器体系Servlet 容器启动时会创建两个独立的 Spring 容器根容器 (Root WebApplicationContext)由ContextLoaderListener加载通常用于管理 Service、Dao 等全局业务组件。Web 容器 (DispatcherServlet 持有的子容器)由每个DispatcherServlet加载通常用于管理 Controller、ViewResolver 等 Web 组件。两者的关系是Web 容器将根容器设为父容器。子容器可以访问父容器中的 Bean反之则不行这形成了一种清晰的职责分离。接下来通过源码解析这一过程。⚙️ 根容器Bean工厂加载ContextLoaderListener的使命在传统的web.xml配置中根容器的加载与 Servlet 容器的启动事件绑定在一起context-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-config.xml/param-value/context-paramlistenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listener这段配置背后有着精密的源码逻辑。1. 监控 Servlet 生命周期ContextLoaderListenerContextLoaderListener实现了javax.servlet.ServletContextListener接口。当 Tomcat 容器启动时会自动调用其contextInitialized方法。下面是ContextLoaderListener的源码publicclassContextLoaderListenerextendsContextLoaderimplementsServletContextListener{// Tomcat 启动时调用的方法OverridepublicvoidcontextInitialized(ServletContextEventevent){// 核心调用父类的 initWebApplicationContext 方法并传入 ServletContextinitWebApplicationContext(event.getServletContext());}// Tomcat 关闭时调用的方法用于销毁容器OverridepublicvoidcontextDestroyed(ServletContextEventevent){closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}}ContextLoaderListener将核心工作委托给了父类ContextLoader。2.initWebApplicationContext方法创建与初始化BeanContextLoader.initWebApplicationContext是根容器创建和初始化的核心。publicWebApplicationContextinitWebApplicationContext(ServletContextservletContext){// 1. 检查 ServletContext 中是否已存在根容器若存在则抛出异常防止重复初始化if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)!null){thrownewIllegalStateException(Cannot initialize context because there is already a root application context present.);}// 2. 创建 WebApplicationContext 实例if(this.contextnull){// 通过 createWebApplicationContext 方法创建容器如 XmlWebApplicationContextthis.contextcreateWebApplicationContext(servletContext);}// 3. 配置并刷新容器解析配置、创建BeanconfigureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context,servletContext);// 4. 将创建好的根容器存入 ServletContext 中Key 为常量WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEservletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);returnthis.context;}createWebApplicationContext方法会根据配置实例化容器protectedWebApplicationContextcreateWebApplicationContext(ServletContextsc){// 1. 获取上下文类默认为 XmlWebApplicationContextClass?contextClassdetermineContextClass(sc);// 2. 检查类型合法性if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)){thrownewApplicationContextException(Custom context class [contextClass.getName()] is not of type ConfigurableWebApplicationContext);}// 3. 通过反射实例化容器ConfigurableWebApplicationContextwac(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);returnwac;}configureAndRefreshWebApplicationContext方法则负责真正的 Bean 加载protectedvoidconfigureAndRefreshWebApplicationContext(ConfigurableWebApplicationContextwac,ServletContextsc){// 1. 设置 ServletContext 和 ConfigLocationwac.setServletContext(sc);StringconfigLocationsc.getInitParameter(CONFIG_LOCATION_PARAM);if(configLocation!null){wac.setConfigLocation(configLocation);}// 2. 设置环境、父容器此时为null// ... 其他设置// 3. 核心调用刷新容器这行代码会触发 所有Bean 的解析、注册、实例化和依赖注入wac.refresh();}refresh()是 IoC 容器初始化的真正入口它会加载配置文件、扫描组件、创建 Bean 实例、完成依赖注入。此处不展开但其结果是所有在spring-config.xml或通过注解声明的 Service、Dao 等 Bean 被创建和管理。3. 与 Servlet 容器的关联存入ServletContext最关键的一行代码是servletContext.setAttribute(...)。它将根容器存储到ServletContext中这个ServletContext是 Servlet 容器的全局上下文。这意味着Spring 根容器成为 Servlet 容器的一个全局属性任何 Servlet包括DispatcherServlet都可以通过ServletContext访问它。至此根容器被加载并存放于 Servlet 容器的全局空间中静待后续使用。 Web容器处理请求线程加载DispatcherServlet 的使命根容器加载完接下来DispatcherServlet会启动初始化它自己持有的 Web 容器子容器。1. Servlet 的起点init()方法作为一个 ServletDispatcherServlet的入口是init()方法。它的继承链是DispatcherServlet-FrameworkServlet-HttpServletBean-HttpServlet。init()方法最终在HttpServletBean中被实现。publicabstractclassHttpServletBeanextendsHttpServlet{Overridepublicfinalvoidinit()throwsServletException{// ... 从 web.xml 中读取 init-param 配置并通过 BeanWrapper 设置到当前 Servlet 实例中// 模板方法由子类 FrameworkServlet 实现initServletBean();}}2. 容器的创建与关联initWebApplicationContextinitServletBean在FrameworkServlet中的实现最终会调用initWebApplicationContext这是 Web 容器加载的核心。protectedWebApplicationContextinitWebApplicationContext(){// 1. 获取根容器【关键步骤】// 从 ServletContext 中以 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为 Key 获取根容器WebApplicationContextrootContextWebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContextwacnull;// 2. 如果通过构造器传入了容器如在 Servlet 3.0 环境中则直接使用if(this.webApplicationContext!null){wacthis.webApplicationContext;// ... 设置父容器}if(wacnull){// 3. 没有传入则创建一个新的 Web 容器waccreateWebApplicationContext(rootContext);// 创建时会将 rootContext 设为父容器}// 4. 刷新 Web 容器初始化其内部的 Bean如 ControllerconfigureAndRefreshWebApplicationContext(wac);returnwac;}这里的核心是WebApplicationContextUtils.getWebApplicationContext(getServletContext());它正是从 Servlet 容器的全局空间中拿到了之前在ContextLoaderListener中创建并存入的根容器。3.createWebApplicationContext: 创建并建立父子关系创建子容器时会建立父容器关系protectedWebApplicationContextcreateWebApplicationContext(NullableApplicationContextparent){// 1. 获取上下文类默认为XmlWebApplicationContextClass?contextClassgetContextClass();// 2. 通过反射实例化一个新的 ConfigurableWebApplicationContextConfigurableWebApplicationContextwac(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);// 3. 设置环境wac.setEnvironment(getEnvironment());// 4. 设置父容器【关键步骤】wac.setParent(parent);// 5. 配置位置例如 spring-mvc.xml 或 MvcConfigStringconfigLocationgetContextConfigLocation();if(configLocation!null){wac.setConfigLocation(configLocation);}// 6. 完成后续容器初始化与刷新操作配置、监听器、refresh 等configureAndRefreshWebApplicationContext(wac);returnwac;}wac.setParent(parent);这行代码建立父子容器关系。4.configureAndRefreshWebApplicationContext与onRefreshprotectedvoidconfigureAndRefreshWebApplicationContext(ConfigurableWebApplicationContextwac){// 1. 设置ServletContext、ServletConfig...// 2. 刷新子容器所有Web组件的创建、依赖注入发生在此wac.refresh();}refresh()完成后FrameworkServlet会调用模板方法onRefresh()而DispatcherServlet则重写了onRefresh()用于初始化其核心策略组件。OverrideprotectedvoidonRefresh(ApplicationContextcontext){// 初始化Spring MVC的九大核心组件如 HandlerMapping、HandlerAdapter、ViewResolver 等initStrategies(context);}protectedvoidinitStrategies(ApplicationContextcontext){// 每个方法都会尝试从子容器WebApplicationContext中查找相应组件// 如果找不到就使用 DispatcherServlet.properties 中的默认配置initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);} Controller Bean 的注册与 HandlerMapping 建立ControllerBean 注册有两种情形情形一基于 XML 配置若initWebApplicationContext时配置了contextConfigLocation如classpath:spring-mvc.xml容器在refresh()时会解析该 XML处理其中的context:component-scan或bean class...Controller/标签完成Controller的 Bean 定义、注册。情形二基于注解配置更常见通过AnnotationConfigWebApplicationContext等注解驱动的容器在refresh()时也会被ClassPathBeanDefinitionScanner扫描检测到标注有Controller、Service的类为其创建 BeanDefinition并注册到容器中。关键时机afterPropertiesSet()与 HandlerMapping 建立在 Bean 初始化完成refresh()后Spring 会调用实现了InitializingBean接口的 Bean 的afterPropertiesSet()方法。RequestMappingHandlerMapping恰巧如此publicclassRequestMappingHandlerMappingextendsAbstractHandlerMethodMappingRequestMappingInfo{OverridepublicvoidafterPropertiesSet(){// 1. 初始化配置initConfiguration();// 2. 核心扫描容器中所有的 Bean找出标注了 Controller 和 RequestMapping 的方法// 并解析为 RequestMappingInfo最终存储到 MappingRegistry 中super.afterPropertiesSet();}}super.afterPropertiesSet()是真正的处理入口。至此Controller注册完成HandlerMapping也建立了 URL 到处理器的映射一个请求到来时能正确找到对应方法。 总结容器与 Servlet 的关联整理一下从 Servlet 容器启动到 Spring MVC 就绪的关键步骤和对应源码容器启动Tomcat 等启动加载web.xml或通过 SPI 发现配置。根容器加载ContextLoaderListener的contextInitialized被 Tomcat 调用最终调用ContextLoader.initWebApplicationContext创建WebApplicationContext并存入ServletContext。Web容器加载作为 Servlet 的DispatcherServlet其init()被 Tomcat 调用最终进入FrameworkServlet.initWebApplicationContext。父子关联initWebApplicationContext中从ServletContext拿到根容器并通过wac.setParent(parent)建立父子关联。Bean 注册子容器refresh()→ 扫描 →Controller等 Bean 被注册。afterPropertiesSet()→HandlerMapping建立 URL 映射。策略初始化onRefresh()→initStrategies()加载 Spring MVC 的九大组件。请求接管一切就绪DispatcherServlet开始处理 HTTP 请求。在 Servlet 3.0 环境中整个启动过程可以完全摆脱web.xml通过实现ServletContainerInitializer或继承AbstractAnnotationConfigDispatcherServletInitializer来完成。连贯阅读顺序总结模拟实际 debug 路径要一步步跟踪源码建议按以下顺序打断点或阅读Tomcat 启动→ 触发ContextLoaderListener.contextInitialized(ServletContextEvent)进入ContextLoader.initWebApplicationContext(ServletContext)观察createWebApplicationContext如何实例化XmlWebApplicationContext进入configureAndRefreshWebApplicationContext→wac.refresh()在refresh()中观察obtainFreshBeanFactory()→loadBeanDefinitions()读取 XML/注解配置回到initWebApplicationContext看servletContext.setAttribute(...)存入根容器DispatcherServlet 初始化Tomcat 调用HttpServletBean.init()→FrameworkServlet.initServletBean()→initWebApplicationContext()在initWebApplicationContext中查看WebApplicationContextUtils.getWebApplicationContext(servletContext)取出根容器进入createWebApplicationContext(rootContext)注意setParent(parent)再次调用refresh()刷新子容器子容器refresh()完成后FrameworkServlet调用onRefresh()进入DispatcherServlet.initStrategies()查看initHandlerMappings等最后请求进来时DispatcherServlet.doDispatch()会使用这些已初始化的组件。提示如果想看Controller是如何被HandlerMapping识别的可以在RequestMappingHandlerMapping.afterPropertiesSet()里打断点它会在子容器refresh()的过程中被调用因为实现了InitializingBean。高阶无 web.xml 的启动路径Spring Boot / Servlet 3.0如果没有web.xml入口是META-INF/services/javax.servlet.ServletContainerInitializer文件中配置的SpringServletContainerInitializer。它会调用WebApplicationInitializer的实现类如AbstractAnnotationConfigDispatcherServletInitializer其内部最终仍然会创建ContextLoaderListener并注册DispatcherServlet逻辑与上述一致只是不再需要手动编写 XML。可以这样跟踪在SpringServletContainerInitializer.onStartup(SetClass?, ServletContext)打断点进入AbstractDispatcherServletInitializer.registerDispatcherServlet(ServletContext)等但核心的父子容器创建和关联逻辑与web.xml方式完全一样。通过以上路径就能从源码层面完整理解 Spring MVC 是如何加载 Bean以及如何与 Servlet 容器如 Tomcat建立联系的了。Spring MVC 组件在 Spring MVC 中DispatcherServlet的initStrategies方法会初始化九大核心组件为后续的请求处理做好准备。每个组件都有明确的职责和特定的运用时期请求处理流程中的某个阶段。// DispatcherServlet.javaprotectedvoidinitStrategies(ApplicationContextcontext){initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}运用时期组件总览按请求处理顺序步骤组件运用阶段1MultipartResolver识别文件上传请求包装请求对象2FlashMapManager读取上次重定向保存的 Flash 属性3HandlerMapping将 URL 映射到处理器执行链4HandlerAdapter适配并执行处理器Controller5HandlerExceptionResolver处理器执行时若抛异常在此处理6RequestToViewNameTranslator当未返回视图名时生成默认视图名7ViewResolver将逻辑视图名解析为 View 对象8LocaleResolver视图渲染时确定国际化区域9ThemeResolver视图渲染时确定主题可选10FlashMapManager将 Flash 属性保存以用于重定向后注意LocaleResolver和ThemeResolver也可能在 Controller 方法中提前被调用但最主要还是在视图渲染阶段。