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

切换到application.yaml后,Spring特定Profile的@Configuration类失效问题

问题分析:Spring为何忽略TestAppConfig配置类,仅使用DefaultAppConfig

问题背景

我正在将配置从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>

问题原因分析

  1. Profile激活时机滞后
    当通过AnnotationConfigApplicationContext(DefaultAppConfig.class)初始化上下文时,Spring会先加载处理DefaultAppConfig,但spring.profiles.active=test的配置是通过自定义的PropertySourcesPlaceholderConfigurer加载的——这个Bean的初始化在上下文创建的后期阶段,导致Spring扫描组件、识别@Profile注解时,还未读取到激活的Profile,因此TestAppConfig因不满足@Profile("test")条件被跳过。

  2. 自定义PropertySource的局限性
    手动实现的PropertySourcesPlaceholderConfigurer仅加载了application.yaml,未处理Profile对应的application-test.yaml,且覆盖了Spring默认的属性加载机制,导致Profile相关配置文件无法自动加载,app.env=test自然也不会生效。

  3. @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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:25:56