ContextLoader 是 Spring Test 中用于加载 ApplicationContext
的核心接口,通过自定义 ContextLoader
,开发者可以完全控制 Spring 上下文的创建过程。它在以下场景中尤为重要:
ContextLoader
接口定义了两个关键方法:
public interface ContextLoader {
// 加载 ApplicationContext
ApplicationContext loadContext(String... locations) throws Exception;
// 支持基于配置类的加载(Spring 4.1+)
default ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig.getLocations());
}
}
Spring 提供了多种默认实现,常见的有:
AnnotationConfigContextLoader
:加载基于注解的配置类(@Configuration
)。GenericXmlContextLoader
:加载 XML 配置文件。GenericGroovyXmlContextLoader
:加载 Groovy 配置文件。示例:默认加载器的隐式使用
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class) // 使用 AnnotationConfigContextLoader
public class DefaultLoaderTest {
@Autowired
private MyService service;
}
动态创建一个配置类,包含随机生成的数据库连接属性(模拟动态环境配置)。
public class DynamicContextLoader implements ContextLoader {
@Override
public ApplicationContext loadContext(String... locations) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 动态注册配置类
context.register(DynamicConfig.class);
context.refresh();
return context;
}
// 动态配置类(内部类)
@Configuration
static class DynamicConfig {
@Bean
public DataSource dataSource() {
// 生成随机端口模拟动态数据库
String url = "jdbc:h2:mem:testdb_" + UUID.randomUUID().toString().replace("-", "");
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName(url)
.build();
}
}
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = DynamicContextLoader.class) // 使用自定义 Loader
public class CustomLoaderTest {
@Autowired
private DataSource dataSource;
@Test
void testDynamicDataSource() throws SQLException {
// 验证数据源连接是否正常
assertTrue(dataSource.getConnection().isValid(1));
// 输出动态生成的数据库 URL
System.out.println("Database URL: " + dataSource.getConnection().getMetaData().getURL());
}
}
输出示例:
Database URL: jdbc:h2:mem:testdb_6b599d4c8d7a4d7a9e1f
启动 PostgreSQL 容器,并将动态生成的数据库 URL 注入 Spring 上下文。
步骤 1:定义容器与 ContextLoader
public class TestcontainerContextLoader implements ContextLoader {
private static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13");
static {
postgres.start(); // 启动容器(静态块确保只执行一次)
}
@Override
public ApplicationContext loadContext(String... locations) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 动态注册配置类
context.register(TestcontainerConfig.class);
context.refresh();
return context;
}
@Configuration
static class TestcontainerConfig {
@Bean
public DataSource dataSource() {
// 使用容器提供的 JDBC URL
return DataSourceBuilder.create()
.url(postgres.getJdbcUrl())
.username(postgres.getUsername())
.password(postgres.getPassword())
.build();
}
}
}
步骤 2:测试类配置
@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = TestcontainerContextLoader.class)
public class TestcontainerIntegrationTest {
@Autowired
private DataSource dataSource;
@Test
void testDatabaseConnection() throws SQLException {
try (Connection conn = dataSource.getConnection()) {
assertTrue(conn.isValid(1));
System.out.println("Connected to: " + conn.getMetaData().getURL());
}
}
}
输出示例:
Connected to: jdbc:postgresql://localhost:5432/test
在自定义 Loader 中同时支持测试类声明的配置(如 @ContextConfiguration(classes = ...)
)。
public class HybridContextLoader implements ContextLoader {
@Override
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 加载测试类指定的配置类
context.register(mergedConfig.getClasses());
// 动态添加额外配置
context.register(DynamicBeans.class);
context.refresh();
return context;
}
@Configuration
static class DynamicBeans {
@Bean
public String dynamicMessage() {
return "Dynamic Hello!";
}
}
}
测试类:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
loader = HybridContextLoader.class,
classes = AppConfig.class // 主配置类
)
public class HybridLoaderTest {
@Autowired
private String dynamicMessage;
@Test
void testHybridContext() {
assertEquals("Dynamic Hello!", dynamicMessage);
}
}
方法 | 优点 | 缺点 |
---|---|---|
默认 ContextLoader | 简单易用,无需额外编码 | 灵活性有限,无法动态调整配置 |
自定义 ContextLoader | 完全控制上下文加载逻辑 | 需要编写和维护额外代码 |
@DynamicPropertySource | 轻量级动态属性注入 | 仅适用于属性级别,无法修改 Bean 定义 |
通过自定义 ContextLoader
,开发者可以:
最佳实践:
@DynamicPropertySource
处理简单属性注入。ContextLoader
。