切换到application.yaml后,Spring特定Profile的@Configuration类失效问题
问题背景
我正在将配置从application.properties迁移到application.yaml,同时使用Profile机制和对应Profile的@Configuration类。
- 预期行为:执行后输出
TestEnvPrinter.printEnv: env = test - 实际行为:执行后输出
DefaultEnvPrinter.printEnv(): env = default, profile = test
想明确为什么Spring只加载了DefaultAppConfig,却忽略了TestAppConfig这个带@Profile("test")的配置类?
相关代码文件
application.yaml
spring: profiles: active: test app: env: default
application-test.yaml
app: env: test
DefaultAppConfig.java
@ComponentScan("ru.albert_rayanov") @Configuration @PropertySource("classpath:application.yaml") public class DefaultAppConfig { @Bean public static PropertySourcesPlaceholderConfigurer properties() { PropertySourcesPlaceholderConfigurer result = new PropertySourcesPlaceholderConfigurer(); YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean(); yamlFactory.setResources(new ClassPathResource("application.yaml")); result.setProperties(Objects.requireNonNull(yamlFactory.getObject())); return result; } }
TestAppConfig.java
@Configuration @Profile("test") @PropertySources(value = {@PropertySource("classpath:application-test.yaml")}) public class TestAppConfig { @Bean public EnvPrinter envPrinter() { return new TestEnvPrinter(); } }
DefaultEnvPrinter.java
@Component @Primary public class DefaultEnvPrinter implements EnvPrinter { @Value("${app.env}") private String env; @Value("${spring.profiles.active}") private String profile; @Override public void printEnv() { System.out.println("DefaultEnvPrinter.printEnv(): env = " + env + ", profile = " + profile); } }
TestEnvPrinter.java
@Component public class TestEnvPrinter implements EnvPrinter { @Value("${app.env}") private String env; @Override public void printEnv() { System.out.println("TestEnvPrinter.printEnv: env = " + env); } }
ProfileWorker.java
@Component public class ProfileWorker { private final EnvPrinter envPrinter; public ProfileWorker(EnvPrinter envPrinter) { this.envPrinter = envPrinter; } public void doWork() { envPrinter.printEnv(); } }
Main.java
public class Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(DefaultAppConfig.class); context.getBean(ProfileWorker.class).doWork(); } }
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>ru.albert_rayanov</groupId> <artifactId>spring_1_7_profiles</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.8</version> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.33</version> </dependency> </dependencies> </project>
问题原因分析
Profile激活时机滞后
当通过AnnotationConfigApplicationContext(DefaultAppConfig.class)初始化上下文时,Spring会先加载处理DefaultAppConfig,但spring.profiles.active=test的配置是通过自定义的PropertySourcesPlaceholderConfigurer加载的——这个Bean的初始化在上下文创建的后期阶段,导致Spring扫描组件、识别@Profile注解时,还未读取到激活的Profile,因此TestAppConfig因不满足@Profile("test")条件被跳过。自定义PropertySource的局限性
手动实现的PropertySourcesPlaceholderConfigurer仅加载了application.yaml,未处理Profile对应的application-test.yaml,且覆盖了Spring默认的属性加载机制,导致Profile相关配置文件无法自动加载,app.env=test自然也不会生效。@Primary注解的优先级干扰
即使TestEnvPrinter被扫描到,DefaultEnvPrinter上的@Primary注解会让它成为默认Bean;而TestAppConfig未被加载,其定义的envPrinter()Bean也无法覆盖默认实现。
解决方案
方案一:提前激活Profile
在初始化ApplicationContext前先设置激活的Profile,让Spring扫描组件时能识别@Profile("test")的配置类:
public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 先设置激活的Profile context.getEnvironment().setActiveProfiles("test"); // 再注册配置类并刷新上下文 context.register(DefaultAppConfig.class); context.refresh(); context.getBean(ProfileWorker.class).doWork(); } }
方案二:移除自定义PropertySourcesPlaceholderConfigurer
Spring 4.3+支持自动处理YAML配置,只需引入snakeyaml依赖,通过自定义PropertySourceFactory让@PropertySource支持YAML,同时恢复Spring默认的属性加载机制:
@ComponentScan("ru.albert_rayanov") @Configuration @PropertySource(value = "classpath:application.yaml", factory = YamlPropertySourceFactory.class) public class DefaultAppConfig { } // 自定义YAML属性源工厂 class YamlPropertySourceFactory implements PropertySourceFactory { @Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(resource.getResource()); Properties properties = factory.getObject(); return new PropertiesPropertySource(name != null ? name : resource.getResource().getFilename(), properties); } }
同时,TestAppConfig的@PropertySource也需要使用这个工厂类加载YAML文件。
方案三:调整Bean优先级
去掉DefaultEnvPrinter上的@Primary注解,确保Test环境下TestAppConfig加载后,其定义的envPrinter() Bean能成为优先选择;或者直接移除TestEnvPrinter的@Component注解,完全由TestAppConfig的@Bean来创建实例。
内容的提问来源于stack exchange,提问作者Northsoft

