Spring Boot 配置文件加载顺序

阿里P6面试间,候选人小孙在面试中被问到:

"application.yml 和 application-dev.yml 的加载顺序是什么?哪个优先级更高?"

小孙说:"dev 会覆盖主配置吧?因为它专门给开发环境用的。"面试官追问:"那如果两个文件都定义了 server.port,以哪个为准?"

小孙说:"dev 的吧?"面试官拿起笔:"那我换个问法:jar 包外的 application-dev.yml 和 jar 包内的 application.yml,哪个优先级高?"

小孙停顿了三秒,说:"应该是 jar 包外的?"

面试官说:"回答反了。"

【面试官心理】

这道题我用来测试候选人对 Spring Boot 配置体系的最基本理解。99% 的候选人不能完整说出加载顺序,能答对 jar 包内外优先级的只有 5%。这道题太细节了,大多数人只在 IDE 里改过配置,从没深入研究过官方文档。

一、配置文件加载顺序(完整版) 🔴

1.1 Spring Boot 2.x 的加载顺序

Spring Boot 的配置文件加载遵循后面的覆盖前面的原则。以下是完整的加载顺序,从低到高:

1. classpath:/application.yml                    (jar 内,优先级最低)
2. classpath:/application-{profile}.yml
3. file:./config/application.yml                 (jar 外,优先级更高)
4. file:./config/application-{profile}.yml
5. classpath:/application.properties
6. classpath:/application-{profile}.properties
7. file:./config/application.properties
8. file:./config/application-{profile}.properties
9. 命令行参数 --spring.config.additional-location
10. 命令行参数 --spring.config.location
⚠️

重点:jar 包的配置文件优先级比 jar 包!这是很多人答反的点。不是 dev.yml 覆盖主配置,而是加载位置决定了优先级——jar 包外的配置比 jar 包内的高。

1.2 面试必考的口诀

记住这个优先级口诀:命令行 > jar外config目录 > jar外根目录 > jar内

命令行参数(最高)

jar 包外 ./config/ 下的文件

jar 包外根目录 ./ 下的文件

jar 包内 ./config/ 下的文件

jar 包内根目录 ./ 下的文件(最低)

1.3 properties vs yml 优先级

当 properties 和 yml 文件同时存在时,properties 的优先级高于 yml

classpath:application.properties     > classpath:application.yml
file:./config/application.properties > file:./config/application.yml
💡

这个规则在 Spring Boot 2.4 之后有变化。Spring Boot 2.4 引入了 spring.config.activate.on-profile 替代了之前基于文件命名的 profile 激活方式。新旧两种方式在 2.4+ 中是并存的,但 3.x 完全移除了旧方式。

二、Profile 激活顺序 🟡

2.1 激活 profile 的多种方式

# 方式一:命令行参数(优先级最高)
java -jar app.jar --spring.profiles.active=prod

# 方式二:环境变量
export SPRING_PROFILES_ACTIVE=prod

# 方式三:JVM 参数
java -jar app.jar -Dspring.profiles.active=prod

# 方式四:application.yml 中指定
spring:
  profiles:
    active: prod
# application.yml
spring:
  profiles:
    active: dev,common  # 可以同时激活多个 profile

2.2 多 Profile 文件的加载逻辑

# application.yml(主文件,始终加载)
server:
  port: 8080
spring:
  profiles:
    active: dev

---
# application-dev.yml(dev 环境)
server:
  port: 8081
spring:
  config:
    activate:
      on-profile: dev

---
# application-prod.yml(prod 环境)
server:
  port: 443
spring:
  config:
    activate:
      on-profile: prod

加载逻辑:

  1. 先加载主文件 application.yml
  2. 根据 spring.profiles.active 加载对应的 profile 文件
  3. profile 文件中的属性覆盖主文件中的同名属性
📖 点击展开加载优先级图
flowchart TD
    A[启动应用] --> B[加载 application.yml]
    B --> C[检查 spring.profiles.active]
    C --> D[加载 application-{profile}.yml]
    D --> E[检查命令行参数]
    E --> F[最终配置合并完成]
    D --> G["优先级:命令行 > jar外config > jar外根目录 > jar内"]
    B --> G

三、@PropertySource 不加载 yml 🟡

3.1 @PropertySource 的局限性

@PropertySource 是 Spring 提供的注解,用于加载额外的 properties 文件:

@SpringBootApplication
@PropertySource(value = "classpath:custom.properties")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

但是!@PropertySource 不支持加载 yml/yaml 文件

// ❌ 这是无效的,@PropertySource 不认识 yml
@PropertySource(value = "classpath:custom.yml")

// ✅ @PropertySource 只支持 .properties 文件
@PropertySource(value = "classpath:custom.properties")

如果非要加载自定义的 yml 文件,需要使用 PropertySourcesPlaceholderConfigurer 或者 YamlPropertiesFactoryBean

@Configuration
public class YmlConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer properties() {
        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        yaml.setResources(new ClassPathResource("custom.yml"));
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setProperties(yaml.getObject());
        return configurer;
    }
}
⚠️

Spring Boot 2.4+ 建议使用 @ConfigurationProperties 绑定 yml 配置,而不是使用 @PropertySource。如果一定要加载自定义 yml 文件,需要自定义 Bean 配置,比较麻烦。

四、random.* 属性源 🟢

4.1 使用随机值的场景

# application.yml
server:
  port: ${random.int(8000,9000)}  # 随机端口 8000-9000

spring:
  datasource:
    username: user${random.value}
    password: pass${random.value}

myapp:
  cache:
    key: ${random.uuid}

Spring Boot 会自动注册一个 RandomValuePropertySource,它的优先级仅次于命令行参数:

命令行参数

random.* 属性源

其他所有配置源

五、❌ 错误示范

5.1 认为 dev.yml 一定覆盖主配置

候选人原话:"application-dev.yml 会覆盖 application.yml,因为 profile 就是用来区分环境的。"

问题诊断

  • 忽略了加载位置对优先级的影响
  • jar 包内的 dev.yml 不一定比 jar 包内的主配置优先级高
  • 把"profile 文件"和"更高优先级"画了等号

面试官内心 OS:"这个候选人显然没有实际部署过 Spring Boot 应用。在生产环境中,我们通常在 jar 包外的 config 目录放配置文件,如果 jar 包内的 dev.yml 先加载,jar 包外的 prod.yml 覆盖了它,那 profile 激活的逻辑就全乱了。"

5.2 把加载顺序和生效顺序搞混

候选人原话:"Spring Boot 先加载 application.yml,然后加载 application-dev.yml,dev 覆盖主配置。"

正确理解:Spring Boot 是合并所有配置源,而不是先加载一个再加载另一个。加载顺序只是决定了当同一个 key 出现多次时,哪个值最终生效。合并时后面的覆盖前面的。

5.3 不知道 properties > yml

候选人原话:"application.properties 和 application.yml 是一样的,哪个都行。"

问题诊断

  • 不知道两者同时存在时的优先级差异
  • 不知道 properties 的优先级更高

六、标准回答

6.1 P5 级别

"Spring Boot 会从多个位置加载配置文件,优先级从高到低是:命令行参数 > jar 包外 config 目录 > jar 包外根目录 > jar 包内。jar 包外的配置文件优先级比 jar 包内的高。properties 文件的优先级高于 yml 文件。"

6.2 P6 级别

"Spring Boot 的配置加载遵循'后面的覆盖前面的'原则。完整顺序是:命令行参数 --spring.config.additional-location > jar 包外 ./config/ > jar 包外 ./ > jar 包内 ./config/ > jar 包内 ./

当 properties 和 yml 同时存在时,properties 的优先级更高。注意 @PropertySource 注解不支持加载 yml 文件,只支持 properties。

Profile 激活方式有多种:命令行参数 --spring.profiles.active、环境变量 SPRING_PROFILES_ACTIVE、JVM 参数 -Dspring.profiles.active。Spring Boot 2.4+ 引入了 spring.config.activate.on-profile 来声明式地指定 profile 所属。"

6.3 P7 级别

"这道题背后考的是 Spring Boot 配置体系的设计哲学——'约定大于配置',但又'允许覆盖'。

我之前在项目中遇到过一个坑:开发环境用的是 jar 包内的配置,打包时 profile 没改,导致生产环境用了开发环境的配置。后来我们在 CI/CD 流程中强制要求 spring.config.additional-location 必须指定为 jar 包外的 config 目录,这样配置和 jar 包解耦了,任何配置修改都不需要重新打包。

Spring Boot 3.x 的 profile 激活方式完全变了——移除了基于文件命名的 application-{profile}.yml 改成了 application.yml 内使用 spring.config.activate.on-profile。这对老项目迁移来说是个大工程。"

【面试官心理】

P7 的回答重点在于"生产踩坑"和"团队规范建设"。能说出"我们团队怎么解决这类问题"的候选人,面试官会高看一眼。