Spring中如何在Bean外部及自定义PropertySourceFactory中获取当前激活的配置文件
这个问题我之前做自定义配置加载库的时候也碰到过,确实挺挠头的——毕竟咱们自定义的PropertySourceFactory是在Spring容器初始化的早期就运行了,这时候常规的Bean依赖注入还没完全就绪,想直接拿到Environment实例简直像“隔着玻璃抓东西”。不过别担心,咱们可以用个“全局持有者”的思路来解决,同时兼顾生产和测试环境(包括你说的@ActiveProfiles设置的情况)。
1. 先搞个全局的Environment持有者
首先创建一个简单的单例类,用来提前保存Spring准备好的Environment,这样咱们的Factory就能随时拿到它:
public class EnvironmentHolder { // 用静态变量存Environment,保证全局可访问 private static ConfigurableEnvironment environment; // 私有构造方法,防止被实例化 private EnvironmentHolder() {} // 供Spring调用,设置Environment public static void setEnvironment(ConfigurableEnvironment env) { environment = env; } // 对外提供获取Environment的方法 public static ConfigurableEnvironment getEnvironment() { return environment; } // 快捷获取激活的Profiles public static String[] getActiveProfiles() { return environment != null ? environment.getActiveProfiles() : new String[0]; } }
2. 用ApplicationContextInitializer提前捕获Environment
Spring有个ApplicationContextInitializer接口,它会在Environment准备完毕、但ApplicationContext还没完全创建的时候执行——这个时机刚好!咱们可以实现这个接口,把准备好的Environment存到刚才的Holder里:
public class EnvironmentCaptureInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { // 从上下文里拿到已经配置好的Environment,包含所有激活的Profiles ConfigurableEnvironment environment = applicationContext.getEnvironment(); EnvironmentHolder.setEnvironment(environment); } }
3. 注册这个Initializer,让Spring能找到它
接下来要让Spring知道咱们这个Initializer的存在,有两种方式:
方式一:用spring.factories全局注册(推荐,适合库开发)
在你的库的resources/META-INF目录下创建spring.factories文件,添加如下内容:
org.springframework.context.ApplicationContextInitializer=com.yourpackage.EnvironmentCaptureInitializer
这样不管是生产环境启动还是测试环境,Spring都会自动加载这个Initializer。
方式二:在SpringApplication启动时手动添加(适合应用端)
如果是你自己的Spring Boot应用,也可以在启动类里手动添加Initializer:
@SpringBootApplication public class YourApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(YourApplication.class); app.addInitializers(new EnvironmentCaptureInitializer()); app.run(args); } }
4. 在你的YamlPropertySourceFactory里直接用!
现在回到你的YamlPropertySourceFactory,就可以直接调用EnvironmentHolder.getActiveProfiles()拿到所有激活的Profiles了,包括@ActiveProfiles设置的那些:
public class YamlPropertySourceFactory implements PropertySourceFactory { @Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { // 拿到激活的Profiles,比如["management", "test"] String[] activeProfiles = EnvironmentHolder.getActiveProfiles(); // 接下来就可以根据Profiles加载对应的配置文件了,比如common-application-test.yaml List<Resource> yamlResources = new ArrayList<>(); // 先加默认的common-application.yaml yamlResources.add(resource.getResource()); // 遍历激活的Profiles,添加对应的配置文件 for (String profile : activeProfiles) { String profileYaml = resource.getResource().getFilename().replace(".yaml", "-" + profile + ".yaml"); Resource profileResource = new ClassPathResource(profileYaml); if (profileResource.exists()) { yamlResources.add(profileResource); } } // 然后把这些资源合并成一个PropertySource,用Spring的YamlPropertySourceLoader来解析 YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); CompositePropertySource compositeSource = new CompositePropertySource("common-yaml-properties"); for (Resource yamlResource : yamlResources) { List<PropertySource<?>> sources = loader.load(yamlResource.getFilename(), yamlResource); sources.forEach(compositeSource::addPropertySource); } return compositeSource; } }
5. 测试环境的小细节
因为你提到了@SpringBootTest加@ActiveProfiles的场景,这里要注意:Spring Test在每次启动测试上下文时都会执行ApplicationContextInitializer,所以EnvironmentHolder里的Environment会被正确更新,不会和其他测试用例冲突。如果担心多上下文的污染,可以再加个ApplicationListener监听ContextClosedEvent,在上下文关闭时清空Holder:
public class EnvironmentCleanupListener implements ApplicationListener<ContextClosedEvent> { @Override public void onApplicationEvent(ContextClosedEvent event) { EnvironmentHolder.setEnvironment(null); } }
然后同样在spring.factories里注册这个Listener:
org.springframework.context.ApplicationListener=com.yourpackage.EnvironmentCleanupListener
最后再给你捋捋为啥之前的方法不行:
System.getenv和System.getProperty只能拿到系统层面设置的Profiles,@ActiveProfiles是Spring Test内部维护的,不会同步到系统环境变量或属性里,所以拿不到。- 直接依赖注入
Environment到Factory里行不通,因为Factory是在Bean创建之前就被调用了,这时候Spring还没完成Environment的注入流程。
内容来源于stack exchange

