跳到主要内容

上下文层次结构

DeepSeek V3 中英对照 Context Hierarchies

在编写依赖已加载的 Spring ApplicationContext 的集成测试时,通常只需针对单个上下文进行测试即可。然而,在某些情况下,针对 ApplicationContext 实例的层次结构进行测试是有益的,甚至是必要的。例如,如果你正在开发一个 Spring MVC Web 应用程序,通常会有一个由 Spring 的 ContextLoaderListener 加载的根 WebApplicationContext,以及一个由 Spring 的 DispatcherServlet 加载的子 WebApplicationContext。这将形成一个父子上下文层次结构,其中共享组件和基础设施配置在根上下文中声明,并由 Web 特定的组件在子上下文中使用。另一个用例可以在 Spring Batch 应用程序中找到,在这种情况下,通常会有一个父上下文,用于提供共享批处理基础设施的配置,以及一个子上下文,用于特定批处理作业的配置。

你可以通过使用 @ContextHierarchy 注解声明上下文配置来编写使用上下文层次结构的集成测试,无论是在单个测试类上还是在测试类层次结构中。如果在测试类层次结构中的多个类上声明了上下文层次结构,你还可以合并或覆盖上下文层次结构中特定命名级别的上下文配置。在合并层次结构中给定级别的配置时,配置资源类型(即 XML 配置文件或组件类)必须保持一致。否则,完全可以接受在上下文层次结构中使用不同资源类型配置的不同级别。

本节中剩余的基于 JUnit Jupiter 的示例展示了集成测试中常见的配置场景,这些场景需要使用上下文层次结构。

具有上下文层次结构的单一测试类

ControllerIntegrationTests 展示了一个典型的 Spring MVC Web 应用程序的集成测试场景,通过声明一个由两个层级组成的上下文层次结构来实现。一个层级用于根 WebApplicationContext(通过使用 TestAppConfig @Configuration 类加载),另一个层级用于分发器 servlet 的 WebApplicationContext(通过使用 WebConfig @Configuration 类加载)。自动注入到测试实例中的 WebApplicationContext 是子上下文的那个(即层次结构中的最低层上下文)。以下清单展示了此配置场景:

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {

@Autowired
WebApplicationContext wac;

// ...
}
java

具有隐式父类上下文的类层次结构

在这个示例中,测试类定义了一个在测试类层次结构中的上下文层次结构。AbstractWebTests 声明了一个在 Spring 驱动的 Web 应用程序中根 WebApplicationContext 的配置。然而,需要注意的是,AbstractWebTests 并没有声明 @ContextHierarchy。因此,AbstractWebTests 的子类可以选择性地参与上下文层次结构,或者遵循 @ContextConfiguration 的标准语义。SoapWebServiceTestsRestWebServiceTests 都继承自 AbstractWebTests,并通过使用 @ContextHierarchy 定义了一个上下文层次结构。结果是加载了三个应用程序上下文(每个 @ContextConfiguration 声明对应一个),并且基于 AbstractWebTests 中的配置加载的应用程序上下文被设置为每个具体子类加载的上下文的父上下文。以下清单展示了这种配置场景:

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
java

具有合并上下文层次结构的类层次结构配置

此示例中的类展示了如何使用命名层次结构级别来合并上下文层次结构中特定级别的配置。BaseTests 在层次结构中定义了两个级别,parentchildExtendedTests 扩展了 BaseTests,并通过确保 @ContextConfiguration 中的 name 属性声明的名称均为 child,指示 Spring TestContext Framework 合并 child 层次级别的上下文配置。结果是加载了三个应用程序上下文:一个用于 /app-config.xml,一个用于 /user-config.xml,另一个用于 {"/user-config.xml", "/order-config.xml"}。与前面的示例一样,从 /app-config.xml 加载的应用程序上下文被设置为从 /user-config.xml{"/user-config.xml", "/order-config.xml"} 加载的上下文的父上下文。以下清单展示了此配置场景:

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
java

具有覆盖上下文层次结构配置的类层次结构

与前面的示例相比,这个示例展示了如何通过在 @ContextConfiguration 中将 inheritLocations 标志设置为 false 来覆盖上下文层次结构中给定命名级别的配置。因此,ExtendedTests 的应用程序上下文仅从 /test-user-config.xml 加载,并将其父上下文设置为从 /app-config.xml 加载的上下文。以下清单展示了此配置场景:

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
@ContextConfiguration(
name = "child",
locations = "/test-user-config.xml",
inheritLocations = false
))
class ExtendedTests extends BaseTests {}
java
备注

在上下文层次结构中污染上下文

如果你在一个测试中使用 @DirtiesContext,并且该测试的上下文被配置为上下文层次结构的一部分,你可以使用 hierarchyMode 标志来控制上下文缓存的清除方式。更多详细信息,请参阅 Spring Testing Annotations 中关于 @DirtiesContext 的讨论以及 @DirtiesContext 的 Javadoc 文档。