为何@ContextHierarchy搭配@EnableConfigurationProperties时不缓存Bean?
嗨,我来帮你拆解这个问题——你遇到的HelloWorld组件被初始化两次的情况,本质是Spring测试上下文的缓存机制在非@SpringBootTest场景下没正常生效,结合你用到的@EnableConfigurationProperties和@ContextHierarchy,具体原因和解决办法可以这样梳理:
为啥会出现重复初始化?
当你不用@SpringBootTest,手动搭配@EnableConfigurationProperties和@ContextHierarchy时,Spring Test的上下文缓存逻辑会“认不出”这两个测试应该复用同一个上下文:
@ContextHierarchy本身是用来构建上下文层级的,但在非Boot场景下,Spring默认的缓存策略会因为两个测试的配置组合细节差异(哪怕看起来一样),或者缺少Boot自动做的缓存优化,而判定需要重新创建上下文。- 另外,
@EnableConfigurationProperties会引入额外的配置元数据处理逻辑,这会让Spring觉得每个测试的上下文配置是独特的,直接跳过缓存,导致每次测试都重新初始化所有Bean,包括你的HelloWorld,所以会两次输出init666。
而@SpringBootTest能正常工作,是因为它内部已经集成了Boot的上下文缓存优化,会自动处理配置合并、缓存键生成这些细节,确保相同配置的测试复用同一个上下文。
几个可行的解决办法
1. 给测试上下文加统一的缓存标识
你可以通过@TestPropertySource给两个测试类添加相同的自定义属性,让Spring把它们识别为同一个上下文进行缓存:
// 在TestOne和TestTwo上都加上这段配置 @ContextHierarchy({ @ContextConfiguration(classes = TestConfiguration.class) }) @TestPropertySource(properties = "test.context.cache.key=shared-hello-world") @EnableConfigurationProperties public class TestOne { // 你的测试代码 }
这样Spring会把带有这个相同属性的测试上下文视为同一组,自动复用,不会重复初始化Bean。
2. 让配置类继承Spring Boot的配置能力
如果你的测试配置类没有用到Boot的自动配置,可以给TestConfiguration加上@SpringBootConfiguration注解,让Spring Test应用Boot的缓存逻辑:
@SpringBootConfiguration @EnableConfigurationProperties public class TestConfiguration { // 你的Bean定义 }
之后测试类直接引用这个配置类(如果层级不是必须的,甚至可以去掉@ContextHierarchy),Boot的缓存机制会自动帮你复用上下文。
3. 抽离公共配置,统一测试上下文
如果两个测试的上下文结构差不多,把公共的配置(比如TestConfiguration、HelloWorld的Bean定义)抽成一个共享配置类,让两个测试类完全复用这个配置:
// 公共测试配置类 @Configuration @EnableConfigurationProperties public class SharedTestConfig { // 把TestConfiguration里的内容移到这里 @Bean public HelloWorld helloWorld() { return new HelloWorld(); } } // TestOne @ContextConfiguration(classes = SharedTestConfig.class) public class TestOne { // 测试代码 } // TestTwo @ContextConfiguration(classes = SharedTestConfig.class) public class TestTwo { // 测试代码 }
这种方式消除了两个测试的配置差异,Spring会自动识别并缓存这个共享上下文,自然就不会重复初始化Bean了。
内容的提问来源于stack exchange,提问作者Artem K.

