Spring Boot中基于@ConfigurationProperties实现租户属性默认值继承问题
问题解决:Spring Boot租户配置继承默认值时属性覆盖问题
问题核心
你希望让tenant1、tenant2继承default租户的配置,但自定义EnvironmentPostProcessor生成的合并属性,在测试环境中被ConfigDataEnvironmentPostProcessor(由ConfigDataApplicationContextInitializer触发)覆盖,导致租户自有配置被默认值替换,断言assertEquals(5, tenant.getProperty1())失败。
解决方案
1. 调整自定义处理器的执行顺序与属性源优先级
ConfigDataEnvironmentPostProcessor的默认执行顺序为0,需让自定义处理器在它之后执行,确保能拿到完整的配置文件内容;同时将合并后的属性源添加到属性源列表最前面,保证合并后的配置优先级最高。
修正后的DefaultTenantPostProcessor代码:
import org.springframework.boot.SpringApplication; import org.springframework.boot.env.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import java.util.HashMap; import java.util.Map; import java.util.Set; // 设置顺序在ConfigData处理器之后 @Order(ConfigDataEnvironmentPostProcessor.ORDER + 1) public class DefaultTenantPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Map<String, Object> defaultProperties = getTenantProperties("app.tenants.default", environment); Set<String> tenantNames = getNonDefaultTenantNames(environment); Map<String, Object> mergedProperties = mergeTenantConfigs(tenantNames, defaultProperties, environment); // 将合并后的属性源添加到最前面,确保优先级最高 environment.getPropertySources().addFirst(new MapPropertySource("mergedTenantConfigs", mergedProperties)); } // 获取指定租户的所有配置属性 private Map<String, Object> getTenantProperties(String tenantPrefix, ConfigurableEnvironment environment) { Map<String, Object> properties = new HashMap<>(); for (org.springframework.core.env.PropertySource<?> source : environment.getPropertySources()) { if (source instanceof org.springframework.core.env.MapPropertySource mapSource) { mapSource.getSource().forEach((key, value) -> { if (key.startsWith(tenantPrefix + ".")) { properties.put(key, value); } }); } } return properties; } // 获取所有非default的租户名称 private Set<String> getNonDefaultTenantNames(ConfigurableEnvironment environment) { Set<String> tenantNames = new java.util.HashSet<>(); for (org.springframework.core.env.PropertySource<?> source : environment.getPropertySources()) { if (source instanceof org.springframework.core.env.MapPropertySource mapSource) { mapSource.getSource().keySet().forEach(key -> { if (key.startsWith("app.tenants.") && !key.startsWith("app.tenants.default.")) { String tenantName = key.split("\\.")[2]; tenantNames.add(tenantName); } }); } } return tenantNames; } // 合并逻辑:保留租户自有配置,仅补全缺失的默认配置 private Map<String, Object> mergeTenantConfigs(Set<String> tenantNames, Map<String, Object> defaultProps, ConfigurableEnvironment environment) { Map<String, Object> merged = new HashMap<>(); for (String tenant : tenantNames) { String tenantPrefix = "app.tenants." + tenant; Map<String, Object> tenantProps = getTenantProperties(tenantPrefix, environment); // 先添加租户自有配置,确保不会被默认值覆盖 merged.putAll(tenantProps); // 补全租户缺失的默认配置 defaultProps.forEach((defaultKey, defaultValue) -> { String targetKey = defaultKey.replace("app.tenants.default", tenantPrefix); if (!merged.containsKey(targetKey)) { merged.put(targetKey, defaultValue); } }); } return merged; } }
2. 正确注册处理器
在src/main/resources/META-INF/spring.factories中添加处理器注册:
org.springframework.boot.env.EnvironmentPostProcessor=com.yourpackage.DefaultTenantPostProcessor
3. 修正测试用例拼写错误
将测试类名TenantsProeprtiesTest改为TenantsPropertiesTest,确保Autowired能正确注入:
@SpringBootTest public class TenantsPropertiesTest { @Autowired TenantsProperties tenants; @Test void test_default_properties() { Tenant tenant1 = tenants.getTenants().get("tenant1"); assertEquals(2, tenant1.getProperty2()); assertEquals(5, tenant1.getProperty1()); assertTrue(tenant1.getFeatures().get("feature2")); assertTrue(tenant1.getFeatures().get("feature1")); } }
关键注意事项
- 执行顺序:必须让自定义处理器在
ConfigDataEnvironmentPostProcessor之后执行,否则无法获取完整的配置文件内容。 - 属性源优先级:合并后的属性源要添加到最前面,保证Spring读取配置时优先使用合并后的结果,避免租户自有配置被默认值覆盖。
- 合并逻辑:先添加租户自有配置,再补全缺失的默认配置,确保租户自定义的属性不会被覆盖。
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

