开发时用Testcontainers遇DynamicPropertyRegistry注入失败,是否支持动态属性?
开发阶段通过gradle bootTestRun运行应用时,无法直接在配置类的Bean方法中注入DynamicPropertyRegistry,因为这个类是Spring Test模块提供的专属组件,仅在JUnit等测试上下文环境中可用,而bootTestRun启动的是完整的Spring Boot开发应用,不属于测试上下文范畴,因此容器中找不到该类型的Bean,导致注入失败。
错误原因分析
你使用了@TestConfiguration注解标记配置类,同时通过SpringApplication.from().with()将其加载到开发应用上下文中。但@TestConfiguration是专为测试场景设计的配置类注解,DynamicPropertyRegistry也仅在测试上下文(比如JUnit测试的@SpringBootTest环境)中由Spring Test自动提供,开发运行环境并不包含这个Bean。
解决方案
针对开发阶段使用Testcontainers并动态注入属性的需求,可以采用以下几种替代方案:
方案1:使用EnvironmentPostProcessor提前注入属性
通过实现Spring Boot的EnvironmentPostProcessor接口,在应用环境准备阶段启动容器并添加动态属性:
public class TestcontainersEnvProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // 可通过配置开关控制是否启用Testcontainers if (!Boolean.parseBoolean(environment.getProperty("testcontainers.enabled", "true"))) { return; } // 启动Keycloak容器 KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:22.0") .withRealmImportFile("keycloak-realm-config.json"); keycloakContainer.start(); // 向环境中添加动态属性 MutablePropertySources propertySources = environment.getPropertySources(); propertySources.addFirst(new MapPropertySource("testcontainers-props", Map.of( "keycloak.server.external-url", keycloakContainer.getAuthServerUrl(), "keycloak.server.internal-url", keycloakContainer.getAuthServerUrl(), "keycloak.dev-portal-realm", "master" ))); } }
然后在src/main/resources/META-INF/spring.factories中注册该处理器:
org.springframework.boot.env.EnvironmentPostProcessor=com.yourpackage.TestcontainersEnvProcessor
方案2:在启动类中直接启动容器并设置属性
简化流程,直接在应用启动类中启动Testcontainers并设置系统属性:
public class DMATestApplication { public static void main(String[] args) { // 启动Keycloak容器 KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:22.0") .withRealmImportFile("keycloak-realm-config.json"); keycloakContainer.start(); // 设置系统属性供应用读取 System.setProperty("keycloak.server.external-url", keycloakContainer.getAuthServerUrl()); System.setProperty("keycloak.server.internal-url", keycloakContainer.getAuthServerUrl()); System.setProperty("keycloak.dev-portal-realm", "master"); // 启动主应用 SpringApplication.run(DeveloperManagerApplication.class, args); } }
方案3:适配Spring Boot 3.1+的Testcontainers开发模式
如果KeycloakContainer支持@ServiceConnection注解(部分官方维护的容器已支持),可以直接在普通@Configuration类中使用:
@Configuration(proxyBeanMethods = false) public class ContainersConfiguration { @Bean @ServiceConnection public KeycloakContainer keycloakContainer() { return new KeycloakContainer("quay.io/keycloak/keycloak:22.0") .withRealmImportFile("keycloak-realm-config.json"); } }
这种方式下Spring Boot会自动处理容器的启动和属性绑定,无需手动注册动态属性。
关键注意点
- 区分测试上下文和开发运行上下文:
DynamicPropertyRegistry、@TestConfiguration都是测试场景专属组件,不要在开发运行环境中使用。 - 开发阶段使用Testcontainers时,建议通过环境处理器、启动类手动控制,或使用Spring Boot官方提供的开发模式支持。
内容的提问来源于stack exchange,提问作者Jack Beaken

