Skip to content

外部化配置 (Externalized Configuration)

Spring Boot 允许你将配置外部化,以便可以在不同环境中使用相同的应用程序代码。你可以使用多种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。

属性值可以通过 @Value 注解直接注入到 Bean 中,通过 Spring 的 Environment 抽象访问,或通过 @ConfigurationProperties 绑定到结构化对象。

1. 配置优先级

Spring Boot 使用特定的 PropertySource 顺序来允许合理地覆盖值。后面的属性源可以覆盖前面定义的值。

配置源按以下顺序考虑(数字越大优先级越高):

  1. 默认属性(通过 SpringApplication.setDefaultProperties 指定)
  2. @Configuration 类上的 @PropertySource 注解
  3. 配置数据文件(如 application.properties
  4. RandomValuePropertySource(仅限 random.* 属性)
  5. 操作系统环境变量
  6. Java 系统属性(System.getProperties()
  7. JNDI 属性(来自 java:comp/env
  8. ServletContext 初始化参数
  9. ServletConfig 初始化参数
  10. SPRING_APPLICATION_JSON 中的属性
  11. 命令行参数
  12. 测试中的 properties 属性
  13. 测试中的 @DynamicPropertySource 注解
  14. 测试中的 @TestPropertySource 注解
  15. Devtools 全局设置属性

配置文件优先级

配置数据文件按以下顺序考虑:

  1. jar 包内的应用属性(application.properties 和 YAML 变体)
  2. jar 包内的特定 Profile 应用属性(application-{profile}.properties 和 YAML 变体)
  3. jar 包外的应用属性(application.properties 和 YAML 变体)
  4. jar 包外的特定 Profile 应用属性(application-{profile}.properties 和 YAML 变体)

建议

建议在整个应用程序中坚持使用一种格式。如果在同一位置同时有 .properties 和 YAML 格式的配置文件,.properties 优先。

示例

假设你开发了一个使用 name 属性的 @Component

java
@Component
public class MyBean {
    @Value("${name}")
    private String name;
    // ...
}
kotlin
@Component
class MyBean {
    @Value("\${name}")
    private val name: String? = null
    // ...
}

在应用程序类路径中(例如在 jar 内),你可以有一个 application.properties 文件为 name 提供合理的默认值。在新环境中运行时,可以在 jar 外提供覆盖 nameapplication.properties 文件。对于一次性测试,可以使用特定的命令行开关启动(例如 java -jar app.jar --name="Spring")。

2. 访问命令行属性

默认情况下,SpringApplication 将任何命令行选项参数(即以 -- 开头的参数,如 --server.port=9000)转换为 property 并将其添加到 Spring Environment 中。命令行属性始终优先于基于文件的属性源。

如果不希望将命令行属性添加到 Environment,可以使用 SpringApplication.setAddCommandLineProperties(false) 禁用它们。

3. JSON 应用属性

环境变量和系统属性通常有限制,某些属性名称无法使用。为了解决这个问题,Spring Boot 允许你将属性块编码为单个 JSON 结构。

应用程序启动时,任何 spring.application.jsonSPRING_APPLICATION_JSON 属性都将被解析并添加到 Environment

例如,可以在 UN*X shell 的命令行中将 SPRING_APPLICATION_JSON 属性作为环境变量提供:

shell
$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar

也可以作为系统属性提供:

shell
$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar

或作为命令行参数:

shell
$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'

4. 外部应用属性

Spring Boot 在应用程序启动时会自动从以下位置查找并加载 application.propertiesapplication.yaml 文件:

  1. 从类路径
    • 类路径根目录
    • 类路径 /config
  2. 从当前目录
    • 当前目录
    • 当前目录中的 config/ 子目录
    • config/ 子目录的直接子目录

列表按优先级排序(较低项的值覆盖较早项)。

4.1 自定义配置文件名

如果不喜欢 application 作为配置文件名,可以通过指定 spring.config.name 环境属性切换到另一个文件名:

shell
$ java -jar myproject.jar --spring.config.name=myproject

4.2 自定义配置位置

你还可以使用 spring.config.location 环境属性引用显式位置。此属性接受一个或多个要检查的位置的逗号分隔列表:

shell
$ java -jar myproject.jar --spring.config.location=\
    optional:classpath:/default.properties,\
    optional:classpath:/override.properties

提示

如果位置是可选的且你不介意它们不存在,请使用前缀 optional:

4.3 可选位置

默认情况下,当指定的配置数据位置不存在时,Spring Boot 将抛出 ConfigDataLocationNotFoundException,应用程序将不会启动。

如果要指定位置但不介意它并不总是存在,可以使用 optional: 前缀。可以将此前缀与 spring.config.locationspring.config.additional-location 属性以及 spring.config.import 声明一起使用。

4.4 通配符位置

如果配置文件位置在最后一个路径段中包含 * 字符,则将其视为通配符位置。加载配置时会展开通配符,以便也检查直接子目录。

例如,如果你有一些 Redis 配置和一些 MySQL 配置,你可能希望将这两部分配置分开,同时要求两者都存在于 application.properties 文件中。这可能导致两个单独的 application.properties 文件挂载在不同的位置,例如 /config/redis/application.properties/config/mysql/application.properties。在这种情况下,通配符位置 config/*/ 将导致两个文件都被处理。

::: note 注意 通配符位置必须只包含一个 *,并且对于目录搜索位置以 */ 结尾,对于文件搜索位置以 */<filename> 结尾。 :::

4.5 Profile 特定文件

除了 application 属性文件外,Spring Boot 还会尝试使用命名约定 application-{profile} 加载特定于 profile 的文件。例如,如果你的应用程序激活了名为 prod 的 profile 并使用 YAML 文件,则 application.yamlapplication-prod.yaml 都将被考虑。

特定于 profile 的属性从与标准 application.properties 相同的位置加载,特定于 profile 的文件始终覆盖非特定文件。如果指定了多个 profile,则应用最后获胜策略。

4.6 导入额外数据

应用程序属性可以使用 spring.config.import 属性从其他位置导入更多配置数据。导入在发现时处理,并被视为紧接在声明导入的文档下方插入的附加文档。

例如,你的类路径 application.properties 文件中可能有以下内容:

properties
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
yaml
spring:
  application:
    name: "myapp"
  config:
    import: "optional:file:./dev.properties"

这将触发当前目录中 dev.properties 文件的导入(如果存在这样的文件)。导入的 dev.properties 中的值将优先于触发导入的文件。

4.7 使用环境变量

在云平台(如 Kubernetes)上运行应用程序时,你经常需要读取平台提供的配置值。你可以使用环境变量,也可以使用配置树。

你甚至可以将整个配置以属性或 yaml 格式存储在(多行)环境变量中,并使用 env: 前缀加载它们。假设有一个名为 MY_CONFIGURATION 的环境变量,内容如下:

properties
my.name=Service1
my.cluster=Cluster1

使用 env: 前缀可以从此变量导入所有属性:

properties
spring.config.import=env:MY_CONFIGURATION
yaml
spring:
  config:
    import: "env:MY_CONFIGURATION"

4.8 使用配置树

在云平台上运行应用程序时,你经常需要读取平台提供的配置值。许多云平台现在允许你将配置映射到挂载的数据卷中。例如,Kubernetes 可以卷挂载 ConfigMapsSecrets

有两种常见的卷挂载模式:

  1. 单个文件包含一组完整的属性(通常写为 YAML)
  2. 多个文件写入目录树,文件名成为"键",内容成为"值"

对于第一种情况,你可以直接使用 spring.config.import 导入 YAML 或属性文件。对于第二种情况,你需要使用 configtree: 前缀,以便 Spring Boot 知道它需要将所有文件公开为属性。

例如,假设 Kubernetes 已挂载以下卷:

etc/
  config/
    myapp/
      username
      password

要导入这些属性,可以将以下内容添加到 application.propertiesapplication.yaml 文件:

properties
spring.config.import=optional:configtree:/etc/config/
yaml
spring:
  config:
    import: "optional:configtree:/etc/config/"

然后你可以从 Environment 以常规方式访问或注入 myapp.usernamemyapp.password 属性。

4.9 属性占位符

application.propertiesapplication.yaml 中的值在使用时会通过现有的 Environment 进行过滤,因此你可以引用先前定义的值(例如,从系统属性或环境变量)。标准的 ${name} 属性占位符语法可以在值中的任何位置使用。

properties
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
yaml
app:
  name: "MyApp"
  description: "${app.name} is a Spring Boot application written by ${username:Unknown}"

4.10 使用多文档文件

Spring Boot 允许你将单个物理文件拆分为多个逻辑文档,每个文档独立添加。文档按从上到下的顺序处理。后面的文档可以覆盖前面文档中定义的属性。

对于 application.yaml 文件,使用标准的 YAML 多文档语法。三个连续的连字符表示一个文档的结束和下一个文档的开始:

yaml
spring:
  application:
    name: "MyApp"
---
spring:
  application:
    name: "MyCloudApp"
  config:
    activate:
      on-cloud-platform: "kubernetes"

对于 application.properties 文件,使用特殊的 #---!--- 注释来标记文档拆分:

properties
spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes

4.11 激活属性

有时仅在满足特定条件时激活给定的一组属性很有用。你可以使用 spring.config.activate.* 有条件地激活属性文档。

以下激活属性可用:

属性说明
on-profile必须匹配的 profile 表达式
on-cloud-platform必须检测到的 CloudPlatform

例如,以下内容指定第二个文档仅在 Kubernetes 上运行时以及"prod"或"staging" profile 处于活动状态时才处于活动状态:

properties
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
yaml
myprop: "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

5. 加密属性

Spring Boot 不提供任何内置的属性值加密支持,但它提供了修改 Spring Environment 中包含的值所需的钩子点。EnvironmentPostProcessor 接口允许你在应用程序启动之前操作 Environment

如果你需要一种安全的方式来存储凭据和密码,Spring Cloud Vault 项目提供了在 HashiCorp Vault 中存储外部化配置的支持。

6. 使用 YAML

YAML 是 JSON 的超集,因此是指定分层配置数据的便捷格式。只要类路径上有 SnakeYAML 库,SpringApplication 类就会自动支持 YAML 作为属性的替代方案。

6.1 将 YAML 映射到属性

YAML 文档需要从其分层格式转换为可与 Spring Environment 一起使用的扁平结构。例如,考虑以下 YAML 文档:

yaml
environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"

为了从 Environment 访问这些属性,它们将被扁平化如下:

properties
environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

YAML 列表也需要扁平化。它们表示为带有 [index] 解引用器的属性键:

yaml
my:
  servers:
  - "dev.example.com"
  - "another.example.com"

转换为:

properties
my.servers[0]=dev.example.com
my.servers[1]=another.example.com

7. 配置随机值

RandomValuePropertySource 对于注入随机值(例如,注入秘密或测试用例)很有用。它可以生成整数、长整数、uuid 或字符串:

properties
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
yaml
my:
  secret: "${random.value}"
  number: "${random.int}"
  bignumber: "${random.long}"
  uuid: "${random.uuid}"
  number-less-than-ten: "${random.int(10)}"
  number-in-range: "${random.int[1024,65536]}"

8. 类型安全的配置属性

使用 @Value("${property}") 注解注入配置属性有时会很麻烦,特别是如果你使用多个属性或你的数据本质上是分层的。Spring Boot 提供了一种使用属性的替代方法,该方法允许强类型 Bean 管理和验证应用程序的配置。

8.1 JavaBean 属性绑定

可以绑定声明标准 JavaBean 属性的 Bean:

java
@ConfigurationProperties("my.service")
public class MyProperties {
    private boolean enabled;
    private InetAddress remoteAddress;
    private final Security security = new Security();

    // getters / setters...

    public static class Security {
        private String username;
        private String password;
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        // getters / setters...
    }
}
kotlin
@ConfigurationProperties("my.service")
class MyProperties {
    var isEnabled = false
    var remoteAddress: InetAddress? = null
    val security = Security()

    class Security {
        var username: String? = null
        var password: String? = null
        var roles: List<String> = ArrayList(setOf("USER"))
    }
}

8.2 构造函数绑定

前面的示例可以以不可变方式重写:

java
@ConfigurationProperties("my.service")
public class MyProperties {
    private final boolean enabled;
    private final InetAddress remoteAddress;
    private final Security security;

    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    // getters...

    public static class Security {
        private final String username;
        private final String password;
        private final List<String> roles;

        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }
        // getters...
    }
}
kotlin
@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
        val security: Security) {

    class Security(val username: String, val password: String,
            @param:DefaultValue("USER") val roles: List<String>)
}

8.3 启用 @ConfigurationProperties 注解的类型

Spring Boot 提供了绑定 @ConfigurationProperties 类型并将其注册为 Bean 的基础设施。你可以逐个类启用配置属性,也可以启用与组件扫描类似的配置属性扫描。

要启用配置属性,请在任何 @Configuration 类上使用 @EnableConfigurationProperties 注解:

java
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {
}
kotlin
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration

要使用配置属性扫描,请将 @ConfigurationPropertiesScan 注解添加到你的应用程序:

java
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {
}
kotlin
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication

8.4 使用 @ConfigurationProperties 注解的类型

要使用 @ConfigurationProperties Bean,你可以像注入任何其他 Bean 一样注入它们:

java
@Service
public class MyService {
    private final MyProperties properties;

    public MyService(MyProperties properties) {
        this.properties = properties;
    }

    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        server.start();
    }
}
kotlin
@Service
class MyService(val properties: MyProperties) {
    fun openConnection() {
        val server = Server(properties.remoteAddress)
        server.start()
    }
}

8.5 宽松绑定

Spring Boot 使用一些宽松的规则将 Environment 属性绑定到 @ConfigurationProperties Bean,因此 Environment 属性名称和 Bean 属性名称之间不需要完全匹配。

例如,以下 @ConfigurationProperties 类:

java
@ConfigurationProperties("my.main-project.person")
public class MyPersonProperties {
    private String firstName;
    // getters / setters...
}

可以使用以下任何属性名称:

属性说明
my.main-project.person.first-nameKebab case,推荐在 .properties 和 YAML 文件中使用
my.main-project.person.firstName标准驼峰式语法
my.main-project.person.first_name下划线表示法
MY_MAINPROJECT_PERSON_FIRSTNAME大写格式,推荐在系统环境变量中使用

8.6 @ConfigurationProperties 验证

只要使用 Spring 的 @Validated 注解,Spring Boot 就会尝试验证 @ConfigurationProperties 类。你可以直接在配置类上使用 JSR-303 javax.validation 约束注解:

java
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
    @NotNull
    private InetAddress remoteAddress;

    @Valid
    private final Security security = new Security();

    // getters / setters...

    public static class Security {
        @NotEmpty
        private String username;
        // getters / setters...
    }
}
kotlin
@ConfigurationProperties("my.service")
@Validated
class MyProperties {
    @field:NotNull
    var remoteAddress: InetAddress? = null

    @field:Valid
    val security = Security()

    class Security {
        @field:NotEmpty
        var username: String? = null
    }
}

补充教学

1. 配置优先级的实战理解

在实际开发中,配置优先级的理解至关重要。以下是一个典型场景:

场景:你有一个应用程序需要在开发、测试和生产环境中运行。

  • 开发环境:使用 application-dev.properties,数据库指向本地
  • 测试环境:使用 application-test.properties,数据库指向测试服务器
  • 生产环境:使用环境变量覆盖敏感信息(如数据库密码)

优先级链:

环境变量 > 命令行参数 > application-{profile}.properties > application.properties

2. Profile 的最佳实践

推荐的 Profile 组织方式

application.properties          # 通用配置
application-dev.properties      # 开发环境
application-test.properties     # 测试环境
application-prod.properties     # 生产环境

激活 Profile

shell
# 方式1:命令行
java -jar app.jar --spring.profiles.active=prod

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

# 方式3:application.properties
spring.profiles.active=prod

3. 配置树 vs 环境变量

何时使用配置树

  • Kubernetes ConfigMap/Secret
  • Docker Swarm Secrets
  • 需要挂载多个配置文件

何时使用环境变量

  • 简单的键值对
  • 12-Factor App 原则
  • 容器化部署

4. @ConfigurationProperties vs @Value

特性@ConfigurationProperties@Value
宽松绑定✅ 支持❌ 不支持
元数据支持✅ 支持❌ 不支持
SpEL 表达式❌ 不支持✅ 支持
复杂类型绑定✅ 支持⚠️ 有限支持
验证✅ 支持 JSR-303❌ 不支持
推荐场景结构化配置简单值注入

5. 常见陷阱与解决方案

陷阱1:环境变量命名

properties
# ❌ 错误:环境变量不支持点号
my.service.url=xxx

# ✅ 正确:使用下划线和大写
MY_SERVICE_URL=xxx

陷阱2:YAML 缩进

yaml
# ❌ 错误:缩进不一致
spring:
  application:
  name: myapp

# ✅ 正确:使用2个空格缩进
spring:
  application:
    name: myapp

陷阱3:构造函数绑定忘记编译参数

xml
<!-- Maven:确保添加 -parameters -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <parameters>true</parameters>
    </configuration>
</plugin>

6. 性能优化建议

  1. 减少配置文件扫描:明确指定 spring.config.location 而不是依赖默认扫描
  2. 使用构造函数绑定:比 JavaBean 绑定更快,且线程安全
  3. 避免过度使用 @Value:对于复杂配置,使用 @ConfigurationProperties
  4. 缓存配置:对于不变的配置,使用 final 字段

7. 安全最佳实践

  1. 敏感信息不要提交到版本控制

    gitignore
    application-prod.properties
    application-*.properties
  2. 使用配置加密

    • Spring Cloud Config Server 加密
    • Jasypt 加密
    • HashiCorp Vault
  3. 环境变量优先:生产环境的敏感信息通过环境变量注入

8. 调试配置问题

查看所有配置源

java
@Component
public class ConfigDebugger implements CommandLineRunner {
    @Autowired
    private Environment env;

    @Override
    public void run(String... args) {
        MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources();
        sources.forEach(source -> {
            System.out.println("Source: " + source.getName());
        });
    }
}

使用 Actuator 端点

yaml
management:
  endpoints:
    web:
      exposure:
        include: env,configprops

访问 /actuator/env 查看所有属性源和值。

Based on Spring Framework.