ElIauk

Spring Boot 配置文件加载顺序与多环境

配置能放在哪些地方 Spring Boot 启动时会从多个来源加载配置,后加载的覆盖先加载的,优先级从高到低大致是: 优先级 来源 示例 1 命令行参数 --server.port=9090 2 Java 系统属性 -Dserver.port=9090 3 操作系统环境变量 SERVER_PORT=

评论
Spring Boot 配置文件加载顺序与多环境

配置能放在哪些地方

Spring Boot 启动时会从多个来源加载配置,后加载的覆盖先加载的,优先级从高到低大致是:

优先级 来源 示例
1 命令行参数 --server.port=9090
2 Java 系统属性 -Dserver.port=9090
3 操作系统环境变量 SERVER_PORT=9090
4 jar 外部的 application-{profile}.yml ./config/application-prod.yml
5 jar 内部的 application-{profile}.yml classpath:/application-prod.yml
6 jar 外部的 application.yml ./application.yml
7 jar 内部的 application.yml classpath:/application.yml

这张表最实用的结论是:外部配置优先于 jar 内配置。所以部署时可以在 jar 同级的 config 目录放一份 application-prod.yml 覆盖端口、数据库密码,不用重新打包。

除了本地文件,配置还可以来自配置中心。2022 年常见的是 Nacos、Apollo,它们通过额外依赖或 bootstrap.yml 把远程配置加载进来,优先级通常高于本地文件。引入配置中心后,排查问题第一步是确认“最终生效的是哪一份”。

bootstrap.yml 在 Spring Cloud 项目里负责引导阶段的配置,比如配置中心地址、应用名;application.yml 才是业务配置。两者名字相近但加载时机不同,放错位置会出现“配置中心连上了,业务配置没拉下来”的情况。

多个 profile 可以同时激活,用逗号分隔,比如 dev,local,后激活的覆盖先激活的。但别把互斥的环境一起激活,dev 和 prod 同时打开,数据库、日志、开关全乱。

配置的优先级不是绝对的:@PropertySource、@TestPropertySource、随机值等都有各自的位置。拿不准时用 /actuator/env 看每个属性的来源,比背优先级表可靠。还可以用 --spring.config.location 显式指定配置文件,把加载范围缩小到唯一一个。

application.yml 与 profile

多环境最简单的方式是用 --- 在一个文件里分文档,或者拆成 application-dev.yml、application-prod.yml。Spring Boot 2.4 之后,激活 profile 的写法变成了 spring.config.activate.on-profile:

server:
  port: 8080
spring:
  profiles:
    active: dev
---
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 8081
---
spring:
  config:
    activate:
      on-profile: prod
server:
  port: 8082

启动时用 --spring.profiles.active=prod 覆盖。生产环境建议把数据库、Redis 的地址和密码放在 application-prod.yml,这个文件不进 Git,由部署环境单独提供。

@Value 与 @ConfigurationProperties

取单个配置用 @Value("${app.mail.host}"),简单直接,适合零散的一两个值;配置一多,字段分散在各个类里,改一个 key 就要全局搜索。

一组配置更适合 @ConfigurationProperties:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
    private String host;
    private int port;
    private String username;

    // 省略 getter / setter
}

对应配置:

app:
  mail:
    host: smtp.example.com
    port: 465
    username: no-reply@example.com

它支持松散绑定:配置里写 smtp-host,类里写 smtpHost 也能注入;@Value 则要求 key 完全一致。需要校验时还可以在类上加 @Validated 和 @NotBlank。

命令行与环境变量覆盖

  # 临时改端口
java -jar app.jar --server.port=9090

  # 切换环境
java -jar app.jar --spring.profiles.active=prod

  # 环境变量方式,点换成下划线并大写
export SERVER_PORT=9090
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar

容器部署时环境变量方式最常用,Kubernetes 的 ConfigMap、Docker 的 -e 都是这个原理。

常见坑

1. YAML 格式写错

port:8080 少了冒号后的空格,YAML 会把它当成一个字符串值而不是键值对;缩进用了 Tab 也会报解析错误。写完在 IDEA 里看有没有黄色波浪线,YAML 插件能提前发现大部分问题。

2. profile 没激活

spring.profiles.active 拼成 spring.profile.active,或者写在 --- 分隔的第二个文档里(那时已经晚了)。启动日志里会打印 The following profiles are active: ...,先确认这一行。

3. @Value 写了不存在的 key

@Value("${app.mail.host}") 配错 key,启动直接报 Could not resolve placeholder,反而好查;但如果给了默认值 @Value("${app.mail.host:smtp.example.com}"),就会静默用默认值,线上发不出邮件才被发现。

4. 外部配置意外覆盖

在 jar 同级目录放了一份旧的 application.yml 或 config/application.yml,优先级高于 jar 内部,改了代码里的配置也不生效。排查时先看启动目录下有没有这些文件。

5. 密码里有特殊字符

YAML 里 # 表示注释,: 后跟空格会被当成键值对。密码含这些字符时用单引号包起来,或者改用环境变量注入。

排查步骤

  1. 启动日志里找 The following profiles are active,确认环境;
  2. 引入 spring-boot-starter-actuator 后访问 /actuator/env,能看到每个配置项的来源;
  3. 加 --debug 启动,查看自动配置报告;
  4. 怀疑外部配置覆盖时,检查 jar 同级目录和 config 目录;
  5. 用 --spring.config.location 显式指定配置文件路径,排除加载顺序干扰。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者