24. Externalized Configuration

Spring Boot 允许您外部化配置,以便可以在不同环境中使用相同的应用程序代码。您可以使用属性文件,YAML 文件,环境变量和命令行参数来外部化配置。属性值可以使用@Value注解直接注入到您的 bean 中,可以通过 Spring 的Environment抽象访问,也可以通过@ConfigurationProperties 绑定到结构化对象访问。

Spring Boot 使用一个非常特殊的PropertySourceSequences,该 Sequences 被设计为允许明智地覆盖值。按以下 Sequences 考虑属性:

  • 您的主目录上的Devtools 全局设置属性(在 devtools 处于 Active 状态时为~/.spring-boot-devtools.properties)。

  • @TestPropertySource您的测试 Comments。

  • 测试中的@SpringBootTest#propertiesComments 属性。

  • 命令行参数。

  • SPRING_APPLICATION_JSON中的属性(嵌入在环境变量或系统属性中的内联 JSON)

  • ServletConfig个初始化参数。

  • ServletContext个初始化参数。

  • 来自java:comp/env的 JNDI 属性。

  • Java 系统属性(System.getProperties())。

  • os 环境变量。

  • 仅在random.*中具有属性的RandomValuePropertySource

  • 特定于配置文件的应用程序属性在打包的 jar 之外(application-{profile}.properties和 YAML 变体)

  • 特定于配置文件的应用程序属性包装在 jar 中(application-{profile}.properties和 YAML 变体)

  • 打包的 jar 之外的应用程序属性(application.properties和 YAML 变体)。

  • 打包在 jar 中的应用程序属性(application.properties和 YAML 变体)。

  • @Configuration个类上的@PropertySource条 Comments。

  • 默认属性(使用SpringApplication.setDefaultProperties指定)。

为了提供一个具体的示例,假设您开发了一个使用name属性的@Component

import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*

@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}

在您的应用程序 Classpath 上(例如,在 jar 中),您可以使用application.propertiesname提供合理的默认属性值。在新环境中运行时,可以在 jar 外部提供application.properties,以覆盖name;对于一次性测试,您可以使用特定的命令行开关(例如java -jar app.jar --name="Spring")启动。

Tip

SPRING_APPLICATION_JSON属性可以在命令行中提供环境变量。例如在 UN * X shell 中:

$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar

在此示例中,您将在 SpringEnvironment中以foo.bar=spam结尾。您还可以在 System 变量中以spring.application.json的形式提供 JSON:

$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar

或命令行参数:

$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'

或作为 JNDI 变量java:comp/env/spring.application.json

24.1 配置随机值

RandomValuePropertySource可用于注入随机值(例如,Importing 到机密或测试用例中)。它可以产生整数,长整数,uuid 或字符串,例如

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]}

random.int*语法为OPEN value (,max) CLOSE,其中OPEN,CLOSE是任何字符,而value,max是整数。如果提供max,则value是最小值,而max是最大值(不包括)。

24.2 访问命令行属性

默认情况下SpringApplication会将所有命令行选项参数(以'-'开头,例如--server.port=9000)转换为property并将其添加到 Spring Environment。如上所述,命令行属性始终优先于其他属性源。

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

24.3 应用程序属性文件

SpringApplication将从以下位置的application.properties文件中加载属性,并将它们添加到 Spring Environment中:

  • 当前目录的/config子目录。

  • 当前目录

  • Classpath/config

  • Classpath 根

该列表按优先级排序(在列表较高位置定义的属性会覆盖在较低位置定义的属性)。

Note

您也可以使用 YAML('.yml')文件替代“ .properties”。

如果您不喜欢application.properties作为配置文件名,则可以通过指定spring.config.name环境属性来切换到另一个。您还可以使用spring.config.location环境属性(目录位置或文件路径的逗号分隔列表)来引用显式位置。

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

or

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

Warning

spring.config.namespring.config.location很早就用于确定必须加载哪些文件,因此必须将其定义为环境属性(通常为 OS env,系统属性或命令行参数)。

如果spring.config.location包含目录(而不是文件),则它们应以/结尾(并且在加载之前将附加由spring.config.name生成的名称,包括特定于配置文件的文件名)。 spring.config.location中指定的文件按原样使用,不支持特定于配置文件的变体,并且将被任何特定于配置文件的属性覆盖。

配置位置以相反的 Sequences 搜索。默认情况下,配置的位置是classpath:/,classpath:/config/,file:./,file:./config/。结果搜索 Sequences 为:

  • file:./config/

  • file:./

  • classpath:/config/

  • classpath:/

配置自定义配置位置后,除默认位置外还将使用它们。在默认位置之前搜索自定义位置。例如,如果配置了自定义位置classpath:/custom-config/,file:./custom-config/,则搜索 Sequences 变为:

  • file:./custom-config/

  • classpath:custom-config/

  • file:./config/

  • file:./

  • classpath:/config/

  • classpath:/

通过此搜索 Sequences,您可以在一个配置文件中指定默认值,然后在另一个配置文件中有选择地覆盖这些值。您可以在默认位置之一的application.properties(或使用spring.config.name选择的其他任何基本名称)中为应用程序提供默认值。然后,可以在运行时使用自定义位置之一中的其他文件覆盖这些默认值。

Note

如果您使用环境变量而不是系统属性,则大多数 os 不允许使用句点分隔的键名,但可以使用下划线代替(例如SPRING_CONFIG_NAME而不是spring.config.name)。

Note

如果您在容器中运行,则可以使用 JNDI 属性(在java:comp/env中)或 Servlet 上下文初始化参数来代替环境变量或系统属性,也可以使用它们。

24.4Profile 特定的属性

除了application.properties文件之外,还可以使用命名约定application-{profile}.properties定义特定于配置文件的属性。 Environment具有一组默认配置文件(默认为[default]),如果未设置任何 Active 配置文件(即,如果未显式激活任何配置文件,则将加载application-default.properties的属性)。

特定于配置文件的属性是从与标准application.properties相同的位置加载的,特定于配置文件的文件始终会覆盖非特定文件,而不论特定于配置文件的文件是位于打包 jar 的内部还是外部。

如果指定了多个配置文件,则采用后赢策略。例如,由spring.profiles.active属性指定的配置文件将添加到通过SpringApplication API 配置的配置文件之后,因此具有优先权。

Note

如果您在spring.config.location中指定了任何文件,则不会考虑这些文件的特定于配置文件的变体。如果您还想使用特定于配置文件的属性,请使用spring.config.location中的目录。

24.5 属性中的占位符

application.properties中的值在使用时会通过现有的Environment进行过滤,因此您可以参考以前定义的值(例如,从“系统”属性中)。

app.name=MyApp
app.description=${app.name} is a Spring Boot application

Tip

您还可以使用此技术来创建现有 Spring Boot 属性的“简短”变体。有关详细信息,请参见* 第 72.4 节“使用'short'命令行参数” *操作方法。

24.6 使用 YAML 代替属性

YAML是 JSON 的超集,因此是用于指定分层配置数据的非常方便的格式。只要在 Classpath 上具有SnakeYAML库,SpringApplication类就会自动支持 YAML 作为属性的替代方法。

Note

如果您使用“Starter”,则 SnakeYAML 将通过spring-boot-starter自动提供。

24.6.1 加载 YAML

Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。 YamlPropertiesFactoryBean将 YAML 加载为Properties,而YamlMapFactoryBean将 YAML 加载为Map

例如,以下 YAML 文档:

environments:
    dev:
        url: http://dev.bar.com
        name: Developer Setup
    prod:
        url: http://foo.bar.com
        name: My Cool App

将被转换为以下属性:

environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App

YAML 列表用[index]解引用器表示为属性键,例如,以下 YAML:

my:
   servers:
       - dev.bar.com
       - foo.bar.com

将被转换为以下属性:

my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

要绑定到使用 Spring DataBinderUtil(@ConfigurationProperties所做的事情)之类的属性,您需要在java.util.List(或Set)类型的目标 bean 中具有一个属性,或者需要提供一个 setter 或使用可变变量初始化它值,例如这将绑定到上面的属性

@ConfigurationProperties(prefix="my")
public class Config {

    private List<String> servers = new ArrayList<String>();

    public List<String> getServers() {
        return this.servers;
    }
}

Note

以这种方式配置列表时,需要格外小心,因为覆盖将无法正常工作。在上面的示例中,在多个位置重新定义了my.servers时,各个元素的目标是覆盖,而不是列表。为确保具有较高优先级的PropertySource可以覆盖列表,您需要将其定义为单个属性:

my:
servers: dev.bar.com,foo.bar.com

24.6.2 在 Spring 环境中将 YAML 公开为属性

YamlPropertySourceLoader类可用于在 Spring Environment中将 YAML 公开为PropertySource。这使您可以使用带有占位符语法的熟悉的@Value注解来访问 YAML 属性。

24.6.3 多配置文件 YAML 文档

您可以使用spring.profiles键在一个文件中指定多个特定于配置文件的 YAML 文档,以指示该文档何时适用。例如:

server:
    address: 192.168.1.100
---
spring:
    profiles: development
server:
    address: 127.0.0.1
---
spring:
    profiles: production
server:
    address: 192.168.1.120

在上面的示例中,如果development配置文件处于 Active 状态,则server.address属性将是127.0.0.1。如果未启用developmentproduction配置文件,则该属性的值为192.168.1.100

如果启动应用程序上下文时未显式激活默认配置文件,则会激活默认配置文件。因此,在此 YAML 中,我们为security.user.password设置了一个值,该值仅在"default"配置文件中可用:

server:
  port: 8000
---
spring:
  profiles: default
security:
  user:
    password: weak

而在此示例中,始终设置密码,因为它没有附加到任何配置文件,并且必须根据需要在所有其他配置文件中显式重置密码:

server:
  port: 8000
security:
  user:
    password: weak

可以选择使用!字符否定使用“ spring.profiles”元素指定的 Spring 轮廓。如果为单个文档同时指定了否定的配置文件和否定的配置文件,则至少一个非否定的配置文件必须匹配并且否定的配置文件不能匹配。

24.6.4 YAML 缺点

无法通过@PropertySourceComments 加载 YAML 文件。因此,在需要以这种方式加载值的情况下,需要使用属性文件。

24.6.5 合并 YAML 列表

作为我们已经在上面看到了,最终将任何 YAML 内容转换为属性。当通过配置文件覆盖“列表”属性时,该过程可能很不直观。

例如,假设一个MyPojo对象的namedescription属性默认为null。让我们公开FooProperties中的MyPojo列表:

@ConfigurationProperties("foo")
public class FooProperties {

    private final List<MyPojo> list = new ArrayList<>();

    public List<MyPojo> getList() {
        return this.list;
    }

}

考虑以下配置:

foo:
  list:
    - name: my name
      description: my description
---
spring:
  profiles: dev
foo:
  list:
    - name: my another name

如果devProfile 无效,则FooProperties.list将包含一个MyPojo条目,如上所述。但是,如果启用了dev配置文件,则list仍然仅包含一个条目(名称为“我的另一个名称”,描述为null)。此配置不会将第二个MyPojo实例添加到列表中,并且不会合并项目。

在多个配置文件中指定一个集合时,将使用优先级最高的一个(并且仅使用那个):

foo:
  list:
    - name: my name
      description: my description
    - name: another name
      description: another description
---
spring:
  profiles: dev
foo:
  list:
     - name: my another name

在上面的示例中,考虑到devProfile 处于 Active 状态,FooProperties.list将包含一个 MyPojo条目(名称为“ my another name”和描述null)。

24.7 类型安全的配置属性

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

package com.example;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

@ConfigurationProperties("foo")
public class FooProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    public boolean isEnabled() { ... }

    public void setEnabled(boolean enabled) { ... }

    public InetAddress getRemoteAddress() { ... }

    public void setRemoteAddress(InetAddress remoteAddress) { ... }

    public Security getSecurity() { ... }

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        public String getUsername() { ... }

        public void setUsername(String username) { ... }

        public String getPassword() { ... }

        public void setPassword(String password) { ... }

        public List<String> getRoles() { ... }

        public void setRoles(List<String> roles) { ... }

    }
}

上面的 POJO 定义了以下属性:

  • 默认为foo.enabledfalse

  • foo.remote-address,其类型可以从String强制转换

  • foo.security.username,具有嵌套的“安全性”,其名称由属性名称决定。特别是,返回类型根本不使用,可能是SecurityProperties

  • foo.security.password

  • foo.security.roles,其中包含String

Note

Getter 和 Setter 通常是强制性的,因为绑定是通过标准 Java Beans 属性 Descriptors 进行的,就像在 Spring MVC 中一样。在某些情况下,可以忽略二传手:

  • 只要对 Map 进行了初始化,它们就需要使用吸气剂,但不一定需要使用 setter,因为它们可以通过 Binder 进行突变。

  • 可以通过索引(通常使用 YAML)或使用单个逗号分隔的值(属性)来访问集合和数组。在后一种情况下,必须使用二传手。我们建议始终为此类类型添加设置器。如果初始化集合,请确保它不是不可变的(如上例所示)

  • 如果嵌套的 POJO 属性已初始化(如上面示例中的Security字段),则不需要设置器。如果您希望 Binder 使用其默认构造函数即时创建实例,则将需要一个 setter。

有些人使用 Lombok 项目自动添加获取器和设置器。确保 Lombok 不会为这种类型生成任何特定的构造函数,因为容器将自动使用它来实例化该对象。

您还需要列出要在@EnableConfigurationProperties注解中注册的属性类:

@Configuration
@EnableConfigurationProperties(FooProperties.class)
public class MyConfiguration {
}

Note

当以这种方式注册@ConfigurationProperties bean 时,该 bean 将具有常规名称:<prefix>-<fqn>,其中<prefix>@ConfigurationProperties注解中指定的环境密钥前缀,而<fqn>是 bean 的全限定名。如果 Comments 不提供任何前缀,则仅使用 Bean 的完全限定名称。

上例中的 Bean 名称为foo-com.example.FooProperties

即使上面的配置将为FooProperties创建一个常规 bean,我们还是建议@ConfigurationProperties只处理环境,尤其不要从上下文中注入其他 bean。话虽如此,@EnableConfigurationProperties注解也会自动应用到您的项目中,从而可以从Environment配置任何以@ConfigurationProperties注解的现有 bean。您可以通过确保FooProperties已经是一个 bean 来快捷方式MyConfiguration

@Component
@ConfigurationProperties(prefix="foo")
public class FooProperties {

    // ... see above

}

这种配置样式特别适用于SpringApplication外部 YAML 配置:

# application.yml

foo:
    remote-address: 192.168.1.1
    security:
        username: foo
        roles:
          - USER
          - ADMIN

# additional configuration as required

要使用@ConfigurationProperties bean,您可以像其他任何 bean 一样注入它们。

@Service
public class MyService {

    private final FooProperties properties;

    @Autowired
    public MyService(FooProperties properties) {
        this.properties = properties;
    }

     //...

    @PostConstruct
    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }

}

Tip

使用@ConfigurationProperties还可以让您生成元数据文件,IDE 可以使用这些元数据文件为自己的键提供自动补全功能,有关详细信息,请参见附录 B,配置元数据附录。

24.7.1 第三方配置

除了使用@ConfigurationPropertiesComments 类,您还可以在公共@Bean方法上使用它。当您想将属性绑定到控件之外的第三方组件时,这特别有用。

要通过Environment属性配置 bean,请将@ConfigurationProperties添加到其 bean 注册中:

@ConfigurationProperties(prefix = "bar")
@Bean
public BarComponent barComponent() {
    ...
}

bar前缀定义的任何属性都将以与上面FooProperties示例类似的方式 Map 到该BarComponent bean。

24.7.2 轻松绑定

Spring Boot 使用一些轻松的规则将Environment属性绑定到@ConfigurationProperties bean,因此Environment属性名称和 bean 属性名称之间不需要完全匹配。有用的常见示例包括虚线分隔(例如context-path绑定到contextPath)和大写(例如PORT绑定到port)环境属性。

例如,给定以下@ConfigurationProperties类:

@ConfigurationProperties(prefix="person")
public class OwnerProperties {

    private String firstName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

}

可以使用以下属性名称:

表 24.1. 轻松绑定

PropertyNote
person.firstName标准驼峰式语法。
person.first-name虚线符号,建议在.properties.yml文件中使用。
person.first_name下划线表示法,用于.properties.yml文件的替代格式。
PERSON_FIRST_NAME大写格式。在使用系统环境变量时推荐使用。

24.7.3 属性转换

当 Spring 绑定到@ConfigurationProperties bean 时,它将尝试将外部应用程序属性强制为正确的类型。如果需要自定义类型转换,则可以提供ConversionService bean(bean ID 为conversionService)或自定义属性编辑器(通过CustomEditorConfigurer bean)或自定义Converters(bean 定义标注为@ConfigurationPropertiesBinding)。

Note

由于在应用程序生命周期中非常早就请求了此 bean,因此请确保限制您的ConversionService使用的依赖项。通常,您需要的任何依赖项可能在创建时未完全初始化。如果配置键强制不需要自定义ConversionService,则可能需要重命名,而仅依赖于@ConfigurationPropertiesBinding限定的自定义转换器。

24.7.4 @ConfigurationProperties 验证

每当使用 Spring 的@ValidatedComments 对@ConfigurationProperties类进行 Comments 时,Spring Boot 都会尝试验证它们。您可以在配置类上直接使用 JSR-303 javax.validation约束 Comments。只需确保您的 Classpath 上有兼容的 JSR-303 实现,然后将约束 Comments 添加到您的字段即可:

@ConfigurationProperties(prefix="foo")
@Validated
public class FooProperties {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}

为了验证嵌套属性的值,必须将关联的字段 Comments 为@Valid以触发其验证。例如,以上面的FooProperties示例为基础:

@ConfigurationProperties(prefix="connection")
@Validated
public class FooProperties {

    @NotNull
    private InetAddress remoteAddress;

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

    // ... getters and setters

    public static class Security {

        @NotEmpty
        public String username;

        // ... getters and setters

    }

}

您还可以通过创建名为configurationPropertiesValidator的 bean 定义来添加自定义 Spring Validator@Bean方法应声明为static。配置属性验证器是在应用程序生命周期的早期创建的,并且将@Bean方法声明为 static 可以使创建该 Bean 而不必实例化@Configuration类。这样可以避免早期实例化可能引起的任何问题。有一个属性验证 samples,因此您可以了解如何进行设置。

Tip

spring-boot-actuator模块包含一个公开所有@ConfigurationProperties bean 的端点。只需将您的 Web 浏览器指向/configprops或使用等效的 JMX 端点即可。请参阅* 生产就绪功能 *。详细信息。

24.7.5 @ConfigurationProperties 与@Value

@Value是核心容器功能,它不提供与类型安全的配置属性相同的功能。下表总结了@ConfigurationProperties@Value支持的功能:

Feature@ConfigurationProperties@Value
Relaxed bindingYesNo
Meta-data supportYesNo
SpEL评估NoYes

如果您为自己的组件定义了一组配置键,我们建议您将它们组合在以@ConfigurationPropertiesComments 的 POJO 中。另请注意,由于@Value不支持宽松的绑定,因此如果您需要使用环境变量来提供值,则不是最佳选择。

最后,尽管您可以在@Value中编写SpEL表达式,但不会从应用程序属性文件处理此类表达式。