Skip to content

上下文层级 (Context Hierarchy)

DispatcherServlet 需要一个 WebApplicationContext(普通 ApplicationContext 的扩展)来进行自身配置。WebApplicationContext 拥有指向 ServletContext 及其关联的 Servlet 的链接。它还与 ServletContext 绑定,应用可以使用 RequestContextUtils 上的静态方法来查找 WebApplicationContext(如果需要访问它)。

对于许多应用程序来说,拥有单个 WebApplicationContext 就足够且简单了。但也可能存在上下文层级,其中一个根(Root)WebApplicationContext 在多个 DispatcherServlet(或其他 Servlet)实例之间共享,而每个实例都有自己的子(Child)WebApplicationContext 配置。

层级关系

WebApplicationContext 通常包含基础设施 Bean,例如数据仓库和业务服务,这些 Bean 需要在多个 Servlet 实例之间共享。这些 Bean 实际上被继承,并可以在 Servlet 特有的子 WebApplicationContext 中被重写(即重新声明),子上下文通常包含该 Servlet 本地特有的 Bean。

下图展示了这种关系:

Spring MVC 上下文层级

Java 配置示例

以下示例配置了一个 WebApplicationContext 层级:

java
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		// 根上下文配置(Service, Repository 等)
		return new Class<?>[] { RootConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		// Servlet 特有上下文配置(Controller, ViewResolver 等)
		return new Class<?>[] { App1Config.class };
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/app1/*" };
	}
}
kotlin
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

	override fun getRootConfigClasses(): Array<Class<*>> {
		// 根上下文配置
		return arrayOf(RootConfig::class.java)
	}

	override fun getServletConfigClasses(): Array<Class<*>> {
		// Servlet 特有上下文配置
		return arrayOf(App1Config::class.java)
	}

	override fun getServletMappings(): Array<String> {
		return arrayOf("/app1/*")
	}
}

提示

如果不需要应用程序上下文层级,配置类可以全部通过 getRootConfigClasses() 返回,而 getServletConfigClasses() 返回 null

XML 配置示例 (web.xml)

以下是对应的 web.xml 配置:

xml
<web-app>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/root-context.xml</param-value>
	</context-param>

	<servlet>
		<servlet-name>app1</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/app1-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>app1</servlet-name>
		<url-pattern>/app1/*</url-pattern>
	</servlet-mapping>

</web-app>

提示

如果不需要层级结构,可以仅配置“根”上下文,并将 Servlet 的 contextConfigLocation 参数留空。


补充教学

1. 为什么需要“双上下文”结构?

这种经典的层级结构(Root + Servlet Context)解决了一个核心问题:解耦与共享

  • Root Context:通过 ContextLoaderListener 启动。它存储的是非 Web 相关的 Bean(如 AccountService, DataSource)。如果你的应用有多个 Servlet(比如一个处理 API,一个处理管理后台),它们都可以看到 Root 里的 Bean。
  • Servlet Context:由 DispatcherServlet 启动。它存储的是 Web 相关的 Bean(如 UserController, ViewResolver)。它能看到 Root Context,但 Root Context 看不到它。

2. Spring Boot 里的情况

Spring Boot 中,默认情况下,这种层级结构被简化为了单个上下文

  • 原因:大多数现代微服务只有一个入口(一个 DispatcherServlet),不需要复杂的父子共享。
  • 优势:单个上下文启动更快,Bean 的查找也更直观。
  • 注意:你依然可以在 Spring Boot 中手动创建父子容器,但这通常只有在非常复杂的大型遗留系统迁移中才有用。

3. 理解继承与隔离

  • 可见性:子容器可以访问父容器中的 Bean,但父容器不能直接访问子容器中的 Bean。
  • 重写:如果子容器定义了一个与父容器同名的 Bean,子容器里的方法在注入时会优先拿到自己的那个 Bean(即“覆盖”了父容器的定义)。这在单元测试或多环境配置中偶尔会派上用场。

Based on Spring Framework.