← 文章

Spring Boot 外部配置的加载方式与优先级

梳理命令行参数、环境变量与外部配置文件,便于在不重新打包时调整应用

文章目录10 节
  1. 改个端口号,还要重新打包?
  2. 三种配置方式对比
  3. 方式一:命令行参数
  4. 方式二:环境变量
  5. 方式三:外部配置文件
  6. 放在应用启动目录
  7. 指定配置文件路径
  8. 追加额外配置
  9. 配置加载优先级
  10. 如何验证哪个配置生效了?
  11. 打开配置加载日志
  12. 使用 Actuator 端点
  13. 代码中打印
  14. 多环境配置最佳实践
  15. 配置管理检查清单
  16. 小结

文章目录

10 节
  1. 改个端口号,还要重新打包?
  2. 三种配置方式对比
  3. 方式一:命令行参数
  4. 方式二:环境变量
  5. 方式三:外部配置文件
  6. 放在应用启动目录
  7. 指定配置文件路径
  8. 追加额外配置
  9. 配置加载优先级
  10. 如何验证哪个配置生效了?
  11. 打开配置加载日志
  12. 使用 Actuator 端点
  13. 代码中打印
  14. 多环境配置最佳实践
  15. 配置管理检查清单
  16. 小结

Spring Boot 提供了灵活的外部配置机制,让我们无需重新打包就能调整应用行为。本文梳理了几种常用的外部配置方式及其优先级关系,帮你在不同部署场景下做出正确选择。

改个端口号,还要重新打包?

你一定遇到过这种场景:项目打成 jar 包部署到服务器上,结果发现端口号冲突了,或者数据库地址要换。难道就为了改一行配置,要重新走一遍构建流程?当然不用。

Spring Boot 早就考虑到了这个问题,它提供了多种外部配置方式,让同一个 jar 包可以在不同环境中灵活运行。掌握这些配置方法和它们的优先级关系,是每个 Spring Boot 开发者的必备技能。

三种配置方式对比

在深入细节之前,先看一下各方式的适用场景:

配置方式适用场景优点缺点
命令行参数临时调试、启动多实例即时生效,无需改文件参数多时命令行冗长
环境变量Docker/K8s 容器化部署配置与代码解耦不易管理大量配置
外部配置文件生产环境、多配置项集中管理,支持多环境需维护文件同步

方式一:命令行参数

最简单粗暴的方式,直接在启动命令里加参数:

nohup java -jar myapp.jar --server.port=8081

这种方式适合临时调整,比如排查问题时临时换个端口,或者在不同终端启动多个实例。缺点也很明显——参数一多,命令行就变得又臭又长。

方式二:环境变量

在容器化部署(Docker、K8s)的场景下,环境变量是最主流的配置方式:

export SERVER_PORT=8081
java -jar myapp.jar

Spring Boot 会通过宽松绑定把环境变量映射到配置属性。常用规则是:把点换成下划线、删除连字符,再把字母转成大写。

配置属性对应环境变量
server.portSERVER_PORT
spring.datasource.urlSPRING_DATASOURCE_URL
spring.data.redis.hostSPRING_DATA_REDIS_HOST
app.jwt.secretAPP_JWT_SECRET

这种方式的好处是配置和代码完全解耦,特别适合 12-Factor App 的理念1

方式三:外部配置文件

当需要修改的配置项比较多时,外部配置文件是最合适的选择。

Spring Boot 默认支持 application.propertiesYAML.yml / .yaml)。其他格式需要额外的配置加载扩展,不能直接当作内置能力使用。

放在应用启动目录

最省事的做法:在应用启动时的当前目录创建 application.ymlapplication-{profile}.yml。Spring Boot 会从当前目录及其 config/ 子目录查找配置,并用外部文件覆盖 jar 包内部的同名属性。注意,当前目录不一定等于 jar 文件所在目录。

指定配置文件路径

如果配置文件放在别的目录,用 --spring.config.location 来指定:

java -jar myapp.jar --spring.config.location=file:/path/to/application.yml

追加额外配置

有时候你不想替换默认配置,只想在默认基础上加一些覆盖项。这时候用 --spring.config.additional-location

java -jar myapp.jar --spring.config.additional-location=file:/path/to/application-prod.yml --spring.profiles.active=prod
警告

locationadditional-location 的区别很关键:location完全替换默认的配置搜索路径,而 additional-location 是在默认路径之外追加。搞混了会导致很多默认配置突然失效,排查起来非常头疼。

配置加载优先级

说了这么多配置方式,那它们之间冲突了怎么办?Spring Boot 有一套明确的属性源顺序,后加入的属性源可以覆盖前面的同名属性。下面只列出应用部署时最常见的一组来源,不是完整的 PropertySource 清单(从高到低):

graph TD
    A["① 命令行参数\n--server.port=9090"] --> B["② Java 系统属性\n-Dserver.port=9090"]
    B --> C["③ 环境变量\nSERVER_PORT=9090"]
    C --> D["④ 外部 profile 配置\napplication-prod.yml"]
    D --> E["⑤ 外部默认配置\napplication.yml"]
    E --> F["⑥ 内部 profile 配置\nresources/application-prod.yml"]
    F --> G["⑦ 内部默认配置\nresources/application.yml"]
    style A fill:#ef4444,color:#fff,stroke:none
    style G fill:#3b82f6,color:#fff,stroke:none

用表格形式总结一下:

优先级配置来源示例
1(最高)命令行参数--server.port=9090
2Java 系统属性-Dserver.port=9090
3操作系统环境变量export SERVER_PORT=9090
4外部 profile 配置文件jar 同级目录的 application-prod.yml
5外部默认配置文件jar 同级目录的 application.yml
6内部 profile 配置文件resources/application-prod.yml
7(最低)内部默认配置文件resources/application.yml

在这组常见的运行时来源里,命令行参数可以覆盖系统属性和环境变量;外部配置文件又可以覆盖 jar 包内部的配置。同名属性由高优先级来源覆盖,不同名属性会共同组成最终配置。测试注解、Devtools 等其他属性源不在这张简表里,需要时以官方完整顺序为准。

注意
  • --spring.config.location 会完全替换默认的配置位置,用的时候要小心
  • --spring.config.additional-location 是追加,不会影响默认配置的加载
  • 后加载的配置会覆盖先加载的同名配置

如何验证哪个配置生效了?

配置来源一多,难免会遇到“我明明改了,怎么没生效”的情况。这里分享几个诊断方法。

打开配置加载日志

如果要确认 Spring Boot 查找和加载了哪些配置文件,可以单独把配置加载器的日志级别调到 TRACE

java -jar myapp.jar \
  --logging.level.org.springframework.boot.context.config=TRACE

这类日志适合排查“文件有没有被加载”。如果要确认某个最终配置值来自哪个属性源,再结合 Actuator 的 envconfigprops 端点查看。

使用 Actuator 端点

如果项目引入了 spring-boot-starter-actuator,可以通过 /actuator/env 端点查看所有生效的配置及其来源:

curl http://localhost:8080/actuator/env/server.port
提示

生产环境记得用 Spring Security 保护 Actuator 端点,不要把配置信息暴露给外部。

代码中打印

最简单粗暴的方式,在启动类里直接打印关键配置值:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        var ctx = SpringApplication.run(Application.class, args);
        var env = ctx.getEnvironment();
        log.info("server.port = {}", env.getProperty("server.port"));
        log.info("active profiles = {}", Arrays.toString(env.getActiveProfiles()));
    }
}

多环境配置最佳实践

实际项目中,通常会有 dev、test、prod 等多套环境。我的经验是这样组织:

jar 包内部只放通用的默认配置和开发环境配置:

resources/
├── application.yml           # 通用默认配置
├── application-dev.yml       # 开发环境(默认激活)

服务器上通过外部配置文件覆盖环境相关的值:

/opt/app/
├── myapp.jar
├── application-prod.yml      # 生产环境配置(数据库、Redis 等)

启动命令:

java -jar myapp.jar --spring.config.additional-location=file:./application-prod.yml --spring.profiles.active=prod
重要

敏感配置(数据库密码、JWT 密钥等)不要写进代码仓库。生产环境的配置文件应该只存在于服务器上,或者通过配置中心(如 Nacos)统一管理。

配置管理检查清单

  • jar 包内部只保留通用默认配置和开发环境配置
  • 生产环境敏感配置(密码、密钥)不提交到代码仓库
  • 外部配置文件放在 jar 包同级目录或通过 --spring.config.additional-location 指定
  • 使用 --spring.profiles.active 明确指定当前环境的 profile
  • 通过 Actuator /actuator/env 端点验证配置是否正确生效
  • Actuator 端点已配置 Spring Security 访问控制
  • 多实例部署时确保配置文件版本一致,避免配置漂移

小结

回到开头的问题——改配置不需要重新打包,只需要选择合适的外部配置方式。日常开发中,我的习惯是:开发环境用 jar 包内部的默认配置,测试和生产环境通过外部配置文件覆盖,临时调试用命令行参数。理解了覆盖顺序,配置管理就不会乱。更多细节可参考 Spring Boot 外部配置文档常用配置属性清单

Footnotes

  1. 12-Factor App 是 Heroku 团队提出的构建 SaaS 应用的方法论,其中第三条原则「在环境中存储配置」主张将配置与代码严格分离,通过环境变量注入配置信息,从而实现同一份代码在不同环境中运行。