Spring Boot 的外部化配置(Externalized Configuration)是其核心优势之一。它允许开发者使用相同的应用程序代码,通过外部配置在不同环境中运行,极大地提高了应用程序的灵活性和可移植性。然而,当配置来源多样化时,理解 Spring Boot 如何加载和处理这些配置,特别是它们之间的优先级顺序,就变得至关重要。本文将基于 Spring Boot 3.x 的最新机制,详细解析其配置加载的层次结构和覆盖规则。
1. 配置加载的层次结构与覆盖规则
Spring Boot 采用了一种特殊的 PropertySource 顺序,旨在实现配置值的合理覆盖。其基本原则是:后加载的配置源具有更高的优先级,能够覆盖先加载的配置源中相同名称的属性。
Spring Boot 官方文档列出了配置源的加载顺序,从最低优先级 到最高优先级依次如下 [1]:
| 优先级 | 配置来源 | 描述 |
|---|---|---|
| 最低 | 1. 默认属性 | 通过 SpringApplication.setDefaultProperties() 设置的属性。通常用于提供最基础的默认值。 |
2. @PropertySource |
在 @Configuration 类上使用 @PropertySource 注解加载的属性。 |
|
| 3. 配置数据 (Config Data) | 应用程序配置文件,如 application.properties 或 application.yml。 |
|
| 4. 随机值 | 仅包含 random.* 属性的 RandomValuePropertySource。 |
|
| 5. 操作系统环境变量 | 操作系统级别的环境变量。 | |
| 6. Java 系统属性 | 通过 -D 参数传递给 JVM 的系统属性 (System.getProperties())。 |
|
| 7. JNDI 属性 | 来自 java:comp/env 的 JNDI 属性。 |
|
| 8. ServletContext 初始化参数 | Servlet 上下文的初始化参数。 | |
| 9. ServletConfig 初始化参数 | Servlet 配置的初始化参数。 | |
10. SPRING_APPLICATION_JSON |
环境变量或系统属性中嵌入的内联 JSON 格式配置。 | |
| 11. 命令行参数 | 通过 -- 参数传递给应用程序的命令行参数(例如 --server.port=9000)。 |
|
| 12. 测试属性 | 仅在测试中生效,如 @SpringBootTest 的 properties 属性。 |
|
13. @DynamicPropertySource |
仅在测试中生效,用于动态添加属性源。 | |
| 最高 | 14. @TestPropertySource |
仅在测试中生效,用于加载测试专用的配置文件。 |
| 15. Devtools 全局设置 | 当 Devtools 激活时,加载 $HOME/.config/spring-boot 目录下的全局设置。 |
核心要点:
- 命令行参数(第 11 项)的优先级非常高,常用于临时覆盖配置。
- 环境变量 (第 5 项)和 Java 系统属性(第 6 项)是生产环境中常用的配置方式。
- 配置文件(第 3 项)虽然优先级不高,但它是提供默认配置和环境特定配置的主要手段。
2. 配置文件(Config Data)的内部加载顺序
在上述的第 3 项"配置数据"中,Spring Boot 还会根据配置文件的位置 、Profile 和格式 定义一套更细致的加载顺序。这一机制在 Spring Boot 2.4 引入 Config Data API 后变得更加清晰和强大。
2.1 按位置的优先级(由低到高)
Spring Boot 会在以下四个位置查找 application.properties 或 application.yml 文件,并按顺序加载。外部文件会覆盖内部文件:
- Jar 包内部 (Classpath) :
classpath:/(Classpath 根目录)classpath:/config/(Classpath 下的config目录)
- Jar 包外部 (文件系统) :
file:./(当前运行目录)file:./config/(当前运行目录下的config目录)file:./config/*/(当前运行目录下的config目录的直接子目录)
实际应用: 开发者可以将默认配置打包在 Jar 内部的 classpath:/application.yml 中,而在部署时,将生产环境的配置放在 Jar 外部的 ./config/application.yml 中,外部配置将自动覆盖内部默认值。
2.2 Profile 特定配置的优先级
Spring Boot 支持使用 Profile(环境)来管理不同环境下的配置。通过激活一个或多个 Profile,可以加载对应的配置文件。
- 通用文件 :
application.properties或application.yml。 - Profile 特定文件 :
application-{profile}.properties或application-{profile}.yml。
Profile 特定文件的优先级总是高于通用文件 。例如,当激活 prod Profile 时,application-prod.yml 中的属性会覆盖 application.yml 中的同名属性。
2.3 文件格式的优先级
如果同一位置同时存在 .properties 和 .yml 格式的配置文件,Spring Boot 会优先加载 .properties 文件。
表 2: 配置文件内部加载优先级总结
| 优先级 | 规则 | 示例 |
|---|---|---|
| 最高 | 命令行参数 | --server.port=8081 |
| 外部 Profile 文件 | file:./config/application-prod.yml |
|
| 外部通用文件 | file:./application.properties |
|
| 内部 Profile 文件 | classpath:/config/application-dev.yml |
|
| 最低 | 内部通用文件 | classpath:/application.yml |
3. 外部化配置的最佳实践
理解加载顺序的目的是为了更好地利用它来管理配置,实现环境隔离和配置安全。
3.1 默认值与环境覆盖
- 提供默认值 :将应用程序的通用默认配置放在 Jar 内部的
application.yml中。 - 环境隔离 :使用
application-{profile}.yml来定义特定环境(如dev,test,prod)的配置。 - 运行时覆盖 :将敏感或经常变动的配置(如数据库密码、端口号)通过环境变量 或命令行参数注入。这是最高效和最安全的做法,因为它们具有最高的优先级,且不会被打包到 Jar 文件中。
3.2 使用 SPRING_APPLICATION_JSON 进行容器化配置
在 Docker 或 Kubernetes 等容器化环境中,直接设置大量环境变量可能不方便。此时,可以使用 SPRING_APPLICATION_JSON 环境变量,将所有配置以 JSON 格式一次性注入。
bash
# 在命令行中设置 SPRING_APPLICATION_JSON
$ SPRING_APPLICATION_JSON='{"server.port": 8081, "spring.datasource.url": "jdbc:mysql://..."}' java -jar app.jar
这种方式将配置集中在一个变量中,方便管理,并且其优先级高于普通的环境变量和配置文件。
3.3 监控配置:Actuator
在运行时,如果对某个属性的来源有疑问,可以使用 Spring Boot Actuator 提供的 /actuator/env 端点。该端点会列出所有 PropertySource 及其加载的属性,帮助开发者诊断配置覆盖问题。
Spring Boot 的配置加载顺序是一个精心设计的层次结构,它赋予了应用程序极大的灵活性。从最低优先级的默认属性,到最高优先级的命令行参数和 Devtools 设置,每一个层次都有其特定的用途。掌握"后加载覆盖先加载"的核心原则,并灵活运用 Config Data 的位置和 Profile 规则,是高效管理 Spring Boot 应用程序配置的关键。通过将默认配置、环境配置和运行时配置分离,我们可以构建出健壮、安全且易于部署的现代化应用程序。