You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中如何在Bean外部及自定义PropertySourceFactory中获取当前激活的配置文件

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:04:32