0%

Web上下文初始化

Spring MVC Web 上下文初始化机制详解:从 web.xml 到注解配置

Web 上下文(WebApplicationContext)是 Spring MVC 在 Web 环境中的核心容器,负责管理 Bean 的生命周期并与 Servlet 容器集成。其初始化过程与 ServletContext 紧密绑定,生命周期完全一致。从 “传统 web.xml 配置→ContextLoaderListener 工作原理→Servlet 3.0 注解配置” 三个维度,彻底解析 Spring MVC Web 上下文的初始化机制。

Web 上下文与 ServletContext 的关系

在 Web 应用中,ServletContext 是 Servlet 容器(如 Tomcat)提供的全局上下文对象,代表整个 Web 应用,生命周期从应用启动到停止。而 Spring 的 WebApplicationContext 是 Spring 在 Web 环境中的 IOC 容器,依赖 ServletContext 存在,并通过 ServletContext 存储和获取,两者生命周期完全同步。

  • 存储位置WebApplicationContext 初始化后,会以固定键值(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,即 org.springframework.web.context.WebApplicationContext.ROOT)存入 ServletContext 的属性中,方便后续获取。
  • 核心作用WebApplicationContext 管理 Web 相关的 Bean(如 Controller、Service、Repository 等),并整合 Spring 与 Servlet 容器的交互(如请求处理、会话管理等)。

传统配置:基于 web.xml 与 ContextLoaderListener

在 Servlet 3.0 之前,Web 上下文的初始化依赖 web.xml 配置,核心是通过 ContextLoaderListener 触发初始化流程。

1. web.xml 核心配置

1
2
3
4
5
6
7
8
9
10
11
<!-- 1. 配置 ContextLoaderListener:触发 Web 上下文初始化 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 2. 配置上下文配置文件路径(可选,默认有默认值) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 多个配置文件用逗号分隔,支持 classpath: 或 /WEB-INF/ 前缀 -->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
  • ContextLoaderListener:实现 ServletContextListener 接口,监听 ServletContext 的初始化和销毁事件,是 Web 上下文的 “启动器”。
  • contextConfigLocation:指定 Spring 配置文件的位置,若不配置则使用默认路径(/WEB-INF/applicationContext.xml)。

ContextLoaderListener 工作原理

ContextLoaderListener 继承 ContextLoader 类,通过重写 ServletContextListener 的回调方法,将 Web 上下文的初始化与 ServletContext 生命周期绑定。

核心源码解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
// 当 ServletContext 初始化时触发(Web 应用启动时)
@Override
public void contextInitialized(ServletContextEvent event) {
// 核心:初始化 WebApplicationContext 并与 ServletContext 绑定
initWebApplicationContext(event.getServletContext());
}

// 当 ServletContext 销毁时触发(Web 应用关闭时)
@Override
public void contextDestroyed(ServletContextEvent event) {
// 关闭 WebApplicationContext 并清理资源
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
initWebApplicationContext:初始化核心流程

initWebApplicationContext 是 Web 上下文初始化的核心方法,流程如下:

  1. 检查是否已存在根上下文
    避免重复初始化:若 ServletContext 中已存在 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 键的属性,则抛出异常。

  2. 创建 WebApplicationContext 实例
    通过 createWebApplicationContext(servletContext) 方法创建上下文实例:

    • web.xml 中通过 contextClass 配置了自定义上下文类(如 <context-param name="contextClass" value="com.example.MyWebContext"/>),则使用该类;
    • 否则使用默认实现类 XmlWebApplicationContext(定义在 ContextLoader.properties 中:org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext)。
  3. 配置并刷新上下文
    对创建的 ConfigurableWebApplicationContext 进行配置:

    • 设置父容器(若有,通常用于多上下文分层);
    • 加载配置文件(通过 contextConfigLocation 指定的路径);
    • 调用 refresh() 方法完成 Bean 的加载、实例化和依赖注入(IOC 容器核心流程)。
  4. 绑定上下文到 ServletContext
    将初始化完成的 WebApplicationContext 存入 ServletContext

    1
    2
    3
    4
    servletContext.setAttribute(
    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
    this.context
    );

    后续可通过 WebApplicationContextUtils.getWebApplicationContext(servletContext) 随时获取上下文。

3. 默认配置路径与上下文分层

  • 默认配置路径:若未指定 contextConfigLocationXmlWebApplicationContext 会默认加载 /WEB-INF/applicationContext.xml(定义在 ContextLoader 的常量 DEFAULT_CONFIG_LOCATION 中)。
  • 上下文分层
    ContextLoaderListener 创建的是根上下文(Root WebApplicationContext),通常管理 Service、Repository 等 “业务层” Bean;而 DispatcherServlet 会创建子上下文(WebApplicationContext),管理 Controller、HandlerMapping 等 “Web 层” Bean。子上下文可访问父上下文的 Bean,但父上下文不能访问子上下文的 Bean(实现关注点分离)。

简化配置:Spring 3.x+ 无需 ContextLoaderListener

从 Spring 3.x 开始,若无需上下文分层(即不需要单独的根容器),可仅配置 DispatcherServlet,此时 DispatcherServlet 会创建一个单一的 WebApplicationContext,同时承担根容器和子容器的角色,仅加载 MVC 相关配置文件。

示例(web.xml 简化配置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 仅配置 DispatcherServlet,无需 ContextLoaderListener -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value> <!-- 仅加载 MVC 配置 -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

这种方式适用于简单应用,避免了上下文分层的复杂性;但复杂应用仍建议保留分层(通过 ContextLoaderListener 创建根容器),实现业务逻辑与 Web 逻辑的解耦。

现代配置:Servlet 3.0+ 无 web.xml 方式

Servlet 3.0 规范(JSR 315)引入了 “无 XML 配置” 支持,通过 Java 代码和注解替代 web.xml。Spring 基于此提供了 WebApplicationInitializer 接口,实现类可在 Servlet 容器启动时自动注册组件(如 DispatcherServletContextLoaderListener)。

1. SPI 机制:SpringServletContainerInitializer

Servlet 3.0 允许通过 SPI(Service Provider Interface) 机制自动发现并加载 ServletContainerInitializer 实现类。Spring 在 spring-web 包中通过 SPI 注册了 SpringServletContainerInitializer

  • SPI 配置文件META-INF/services/javax.servlet.ServletContainerInitializer,内容为 org.springframework.web.SpringServletContainerInitializer
  • 触发时机:Servlet 容器(如 Tomcat)启动时,会扫描并实例化所有 ServletContainerInitializer 实现类,调用其 onStartup 方法。

SpringServletContainerInitializer 通过 @HandlesTypes 注解指定需要处理的接口(WebApplicationInitializer):

1
2
3
4
5
6
7
8
9
10
11
12
13
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
// 收集所有 WebApplicationInitializer 实现类
List<WebApplicationInitializer> initializers = new LinkedList<>();
// ... 省略实例化逻辑 ...
// 调用所有实现类的 onStartup 方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}

即:Servlet 容器启动时,SpringServletContainerInitializer 会自动查找并执行所有 WebApplicationInitializer 实现类的 onStartup 方法,完成 Spring MVC 的初始化。

2. 抽象实现:AbstractAnnotationConfigDispatcherServletInitializer

Spring 提供 AbstractAnnotationConfigDispatcherServletInitializer 作为 WebApplicationInitializer 的抽象实现类,简化了无 XML 配置的开发。只需继承该类并实现 3 个抽象方法,即可替代 web.xml 中的所有配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

/**
* 1. 根容器配置类(对应 ContextLoaderListener 加载的配置)
* return null 表示无需根容器(仅使用一个容器)
*/
@Override
protected Class<?>[] getRootConfigClasses() {
// 例如:返回 Spring 业务层配置类(Service、Repository 等)
return new Class[]{RootConfig.class};
}

/**
* 2. Web 容器配置类(对应 DispatcherServlet 加载的配置)
*/
@Override
protected Class<?>[] getServletConfigClasses() {
// 返回 Spring MVC 配置类(Controller、ViewResolver 等)
return new Class[]{WebConfig.class};
}

/**
* 3. DispatcherServlet 的映射路径(同 web.xml 中的 <url-pattern>)
*/
@Override
protected String[] getServletMappings() {
// 拦截所有请求(除了 JSP)
return new String[]{"/"};
}
}

配置类示例:

  • RootConfig.class(根容器配置,业务层):

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    @ComponentScan(basePackages = "com.example.service", excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
    })
    public class RootConfig {
    // 配置数据源、事务管理器等业务层组件
    }
  • WebConfig.class(Web 容器配置,MVC 层):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @EnableWebMvc // 启用 Spring MVC 注解驱动
    @ComponentScan(basePackages = "com.example.controller") // 扫描 Controller
    public class WebConfig implements WebMvcConfigurer {
    // 配置视图解析器、静态资源处理等 MVC 组件
    @Bean
    public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
    }
    }

3. 自动注册的组件

AbstractAnnotationConfigDispatcherServletInitializer 会自动完成以下工作(替代 web.xml):

  1. 创建根容器(Root WebApplicationContext):通过 getRootConfigClasses() 指定的配置类,对应 ContextLoaderListener 的功能;
  2. 创建 Web 容器(Servlet WebApplicationContext):通过 getServletConfigClasses() 指定的配置类,对应 DispatcherServlet 的上下文;
  3. 注册 DispatcherServlet:映射路径由 getServletMappings() 指定,并设置为启动时加载(load-on-startup=1);
  4. 注册 ContextLoaderListener:若 getRootConfigClasses() 返回非空配置类,则自动注册,用于初始化根容器。

两种配置方式对比与最佳实践

配置方式 适用场景 优势 劣势
web.xml + ContextLoaderListener 传统项目、需要兼容旧版本 Servlet 容器 配置直观,易于理解和调试 配置繁琐,XML 与代码分离,维护成本高
注解配置(AbstractAnnotationConfigDispatcherServletInitializer) 现代项目、Servlet 3.0+ 容器 类型安全,配置与代码结合,简化维护 调试时需跟踪代码逻辑,对新手不友好

最佳实践

  1. 新项目:优先使用 Servlet 3.0+ 注解配置,通过 AbstractAnnotationConfigDispatcherServletInitializer 实现,减少 XML 配置;
  2. 上下文分层:复杂项目建议保留根容器(业务层)和 Web 容器(MVC 层)的分层,通过 getRootConfigClasses()getServletConfigClasses() 分别配置,实现关注点分离;
  3. 简化配置:简单项目可返回 getRootConfigClasses() = null,仅使用 Web 容器,减少配置复杂度。

总结

Spring MVC Web 上下文的初始化是 Spring 与 Servlet 容器集成的核心环节,其本质是通过 ContextLoaderListener 或注解配置,创建 WebApplicationContext 并与 ServletContext 绑定,管理 Web 环境中的 Bean 生命周期。

  • 传统方式:通过 web.xml 配置 ContextLoaderListenercontextConfigLocation,显式指定初始化参数;
  • 现代方式:基于 Servlet 3.0+ 的 SPI 机制,通过 WebApplicationInitializer 实现类(如 AbstractAnnotationConfigDispatcherServletInitializer),用代码替代 XML 配置,更灵活且类型安全

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10