从零开始学Spring Boot系列-外部化配置

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

Spring Boot 使用一种非常特殊的 PropertySource 顺序,其设计目的是允许合理地覆盖值。属性按以下顺序考虑:

(1)主目录上的 Devtools 全局设置属性(Devtools 处于活动状态时 ~/.spring-boot-devtools.properties)。
(2)测试中的 @TestPropertySource 注解。
(3)测试中的 properties 属性。在 @SpringBootTest 和测试注解中提供,用于测试应用程序的特定部分。
(4)命令行参数。
(5)来自 SPRING_APPLICATION_JSON(内嵌在环境变量或系统属性中的 JSON)的属性。
(6)ServletConfig 初始化参数。
(7)ServletContext 初始化参数。
(8)来自 java:comp/env 的 JNDI 属性。
(9)Java 系统属性(System.getProperties())。
(10)OS(操作系统)环境变量。
(11)仅在 random.* 中具有属性的 RandomValuePropertySource。
(12)打包的 jar 之外的特定配置的应用程序属性(application-{profile}.properties 和 YAML 变体)。
(13)打包到 jar 内的特定配置的应用程序属性(application-{profile}.properties 和 YAML 变体)。
(14)打包的 jar 之外的应用程序属性(application.properties 和 YAML 变体)。
(15)打包到 jar 内的应用程序属性(application.properties 和 YAML 变体)。
(16)@Configuration 类上的 @PropertySource 注解。
(17)默认属性(通过设置 SpringApplication.setDefaultProperties 指定)。

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

```

    import org.springframework.stereotype.*;
    import org.springframework.beans.factory.annotation.*;
    
    @Component
    public class MyBean {
    
        @Value("${name}")
        private String name;
    
        // ...
    
    }
```

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

提示:SPRING_APPLICATION_JSON 属性可以在带有环境变量的命令行上提供。例如,可以在 UN*X shell 中使用以下行:

```
SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar
```

在前面的示例中,你在 Spring Environment 中使用 acme.name=test 结尾。你还可以在系统属性中将 JSON 作为 spring.application.json 提供。
$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar
还可以使用命令行参数提供 JSON,如下面的示例所示:
$ java -jar myapp.jar --spring.application.json='{"name":"test"}'
还可以将 JSON 作为 JNDI 变量提供,如下所示:java:comp/env/spring.application.json 。

配置随机值

RandomValuePropertySource 用于注入随机值(例如,在机密或测试用例中)。它可以产生 integers、longs、uuids 或字符串,如下面示例所示:

```
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 是最大值(不包含 max)。

访问命令行属性

默认情况下,SpringApplication 将任何命令行选项参数(即以 -- 开头的参数,例如:--server.port=9000)转换为一个 property,并将它们添加到 Spring Environment。如前所述,命令行属性始终优先于其他属性源。

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

应用程序属性文件

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

(1)当前目录的 /config 子目录
(2)当前目录
(3)类路径的 /config 包
(4)类路径的根目录

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

注释:你还可以使用 YAML('.yml') 文件作为“.properties”的替换。

如果你不喜欢将 application.properties 作为配置文件名,则你可以通过指定 spring.config.name 环境属性切换到另一个文件名。还可以使用 spring.config.location 环境属性(目录位置或文件路径的逗号分隔列表)引用显示位置。下面的示例显示如何指定不同的文件名。
java -jar myproject.jar --spring.config.name=myproject
下面的示例显示如何指定两个位置:
java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
警告:spring.config.name 和 spring.config.location 很早就用于确定必须加载哪些文件,因此必须将它们定义为环境属性(通常是 OS 环境变量、系统属性或命令行参数)。

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

按照相反的顺序搜索配置位置。默认情况下,配置的位置是 classpath:/,classpath:/config/,file:./,file:./config/。结果搜索顺序如下:

(1)file:./config/
(2)file:./
(3)classpath:/config/
(4)classpath:/

当使用 spring.config.location 配置自定义配置位置时,它们将替换默认位置。例如,如果 spring.config.location 配置了值 classpath:/custom-config/,file:./custom-config/,则搜索顺序如下:

(1)file:./custom-config/
(2)classpath:custom-config/

或者,当使用 spring.config.additional-location 配置自定义配置位置时,除了默认位置之外,还将使用它们。在默认位置之前搜索其他位置。例如,如果配置了 classpath:/custom-config/,file:./custom-config/,则搜索顺序如下:

(1)file:./custom-config/
(2)classpath:custom-config/
(3)file:./config/
(4)file:./
(5)classpath:/config/
(6)classpath:/

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

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

注释:如果你的应用程序在容器中运行,那么可以使用 JNDI 属性(在java:comp/env中)或 servlet 上下文初始化参数,而不是环境变量或系统属性。

特定配置的属性

除了 application.properties 文件外,还可以使用以下命名约定定义特定配置的属性:application-{profile}.properties。Environment 有一组默认配置(默认情况下是 [default]),如果未设置活动配置,则使用这些配置。换句话说,如果没有显示激活配置文件,则加载 application-default.properties 中的属性。

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

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

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

属性中的占位符

application.properties 中的值在使用时通过现有 Environment 进行筛选,因此可以引用以前定义的值(例如,来自系统属性的)。
app.name=MyApp app.description=${app.name} is a Spring Boot application
提示:你还可以使用此技术创建现有 Spring Boot 属性的“短”变体。

使用 YAML 代替 Properties

YAML 是 JSON 的超集,因此,它是一种用于指定分层配置数据的便捷格式。只要类路径上有 Snake YAML 库,SpringApplication 类就会自动支持 YAML 作为 properties 的替代者。

注释:如果你使用“Starters”,Snake YAML 将由 spring.boot.starter 自动提供。

加载 YAML

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

例如,考虑下面的 YAML 文档:
environments: dev: url: https://dev.example.com name: Developer Setup prod: url: https://another.example.com name: My Cool App
前面的示例将转换为以下属性:
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] 取消引用(dereferencers)的属性键表示。例如,考虑以下 YAML:
my: servers: - dev.example.com - another.example.com
前面的示例将转换为这些属性:
my.servers[0]=dev.example.com my.servers[1]=another.example.com
要通过使用 Spring Boot 的 Binder 工具类(这就是 @ConfigurationProperties 所做的)绑定到类似的属性,需要在目标 bean 中有一个 java.util.List(或 Set) 类型的属性,并且需要提供 setter 或使用可变值初始化它。例如,下面的示例绑定到前面显示的属性:
```
@ConfigurationProperties(prefix="my")
public class Config {

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

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

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

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

多配置的 YAML 文档

通过使用 spring.profiles 键指示文档何时应用,可以在单个文件中指定多个特定配置的 YAML 文档,如下面示例所示:

server:
    address: 192.168.1.100
---
spring:
    profiles: development
server:
    address: 127.0.0.1
---
spring:
    profiles: production & eu-central
server:
    address: 192.168.1.120

在上面的例子中,如果激活 development 配置,则 server.address 属性是 127.0.0.1。类似地,如果激活 production 和 eu-central 配置,则 server.address 属性是 192.168.1.120。如果未启用 development、production 和 eu-central 配置,那么该属性值是 192.168.1.100。

注释:因此,spring.profiles 可以包含一个简单的配置文件名(例如:production)或配置文件表达式。profile 表达式允许表达更复杂的 profile 逻辑,例如:production & (eu-central|eu-west)。

如果应用程序上下文启动时没有显示激活配置文件,则将激活默认的。因此,在下面的 YAML 中,我们为 spring.security.user.password 设置一个值,它仅在“默认”配置文件中可用:

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

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

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

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

YAML 的缺点

无法使用 @PropertySource 注解加载 YAML 文件。因此,如果需要以这种方式加载值,则需要使用属性文件。

在特定配置的 YAML 文件中使用多个 YAML 文档语法可能会导致意外行为。例如,在名为 application-dev.yml 的文件中考虑以下配置,其中 dev 配置文件处于活动状态:

server:
  port: 8000
---
spring:
  profiles: !test
  security:
    user:
      password: weak

在上面的例子中,profile 否定和 profile 表达式的行为将不符合预期。我们建议你不要将特定配置的 YAML 文件和多个 YAML 文档组合在一起,而只使用其中的一个。

类型安全的配置属性

使用 @Value(${property}) 注解注入配置属性有时会很麻烦,特别是在处理多个属性或数据本身是分层的情况下。Spring Boot 提供了另一种处理属性的方法,这种方法允许强类型 bean 控制和验证应用程序的配置,如下面示例所示:

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("acme")
public class AcmeProperties {

    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 定义了以下属性:

(1)acme.enabled,默认值为 false。
(2)acme.remote-address,其类型可以从 String 转换过来。
(3)acme.security.username,具有嵌套的“security”对象,其名称由属性的名称确定。特别是,返回类型根本没有使用,它可能是 SecurityProperties。
(4)acme.security.password。
(5)acme.security.roles,包含一个字符串集合。

注释:getter 和 setter 通常是必需的,因为绑定是通过标准的 Java Beans 属性描述符进行的,就像 Spring MVC 一样。在下列情况下,可省略 setter:

(1)Maps,只要它们被初始化,就需要一个 getter,但不一定需要 setter,因为绑定器可以对它们进行修改。
(2)可以通过索引(通常使用 YAML)或使用单个逗号分隔值(属性)访问集合和数组。在后一种情况下,setter 是必需的。我们建议始终为这样的类型添加 setter。如果初始化集合,请确保它不是不可变的(如前一个示例所示)。
(3)如果嵌套的 POJO 属性被初始化(就像前面例子中 Security 字段),则 setter 不是必须的。如果希望绑定器使用实例的默认构造函数动态创建它,则需要一个 setter。

有些人使用 Project Lombok 自动添加 getter 和 setter。确保 Lombok 不会为这样的类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。

最后,只考虑标准的 Java Bean 属性,不支持绑定静态属性。

你还需要在 @EnableConfigurationProperties 注解中列出要注册的属性类,如下面的示例所示:

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

注释:当 @ConfigurationProperties bean 以这种方式注册时,bean 有一个常规名称:<prefix>-<fqn>,其中 <prefix> 是在 @ConfigurationProperties 注解中指定的环境键前缀,<fqn> 是 bean 的完全限定名。如果该注解没有提供任何前缀,则只使用 bean 的全完限定名。

上面例子中的 bean 名称是 acme-com.example.AcmeProperties。

前面的配置为 AcmeProperties 创建一个常规 bean。我们建议 @ConfigurationProperties 只处理环境,特别是不要从上下文中注入其他 bean。请记住,@EnableConfigurationProperties 注解也会自动应用到你的项目中,以便从 Environment 配置任何带有 @ConfigurationProperties 注解的现有 bean。不是用 @EnableConfigurationProperties(AcmeProperties.class) 注解 MyConfiguration,你可以使 AcmeProperties 成为一个 bean,如下面的示例所示:

@Component
@ConfigurationProperties(prefix="acme")
public class AcmeProperties {

    // ... see the preceding example

}

这种配置方式与 SpringApplication 外部的 YAML 配置配合得特别好,如下面示例所示:

# application.yml

acme:
    remote-address: 192.168.1.1
    security:
        username: admin
        roles:
          - USER
          - ADMIN

# additional configuration as required

要使用 @ConfigurationProperties bean,可以用与任何其他 bean 相同的方式注入它们,如下所示:

@Service
public class MyService {

    private final AcmeProperties properties;

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

    //...

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

}

提示:使用 @ConfigurationProperties 还可以生成元数据文件,IDE 可以使用这些文件为自己的 keys 提供自动完成功能。

第三方配置

除了使用 @ConfigurationProperties 注解类之外,还可以在公共 @Bean 方法上使用它。如果要将属性绑定到不在你控制范围内的第三方组件,那么这样做特别有用。

要从 Environment 属性配置 bean,请将 @ConfigurationProperties 添加到其 bean 注册中,如下所示:

@ConfigurationProperties(prefix = "another")
@Bean
public AnotherComponent anotherComponent() {
    ...
}

用 another 前缀定义的任何属性都映射到 AnotherComponent bean,其方式与前面的 AcmeProperties 示例类似。

宽松的绑定

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

例如,考虑以下 @ConfigurationProperties 类:

@ConfigurationProperties(prefix="acme.my-project.person")
public class OwnerProperties {

    private String firstName;

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

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

}

在上面的示例中,以下属性名都可以使用:

宽松绑定

属性 注释
acme.my-project.person.first-name 烤串式,推荐在 .properties 和 .yml 文件中使用。
acme.myProject.person.firstName 标准的驼峰大小写语法。
acme.my_project.person.first_name 下划线表示法,这是在 .properties 和 .yml 文件中使用的另一种格式。
ACME_MYPROJECT_PERSON_FIRSTNAME 大写格式,建议在使用系统环境变量时使用。

注释:该注解的 prefix 值必须是烤串式(小写并用“-”分隔,例如:acme.my-project.person)。

每个属性源的宽松绑定规则

属性源 简单的(Simple) 列表(List)
属性文件 驼峰式、烤串式或下划线式 使用“[]”的标准列表语法或逗号分隔值。
YAML 文件 驼峰式、烤串式或下划线式 标准 YAML 列表语法或逗号分隔值。
环境变量 以下划线为分隔符的大写格式。“_”不应在属性名中使用。 下划线环绕的数字值,例如:MY_ACME_1_OTHER = my.acme[1].other
系统属性 驼峰式、烤串式或下划线式 使用“[]”的标准列表语法或逗号分隔值。

提示:我们建议尽可能将属性存储为小写的烤串格式,例如:my.property-name=acme。

在绑定到 Map 属性时,如果 key 包含除小写字母-数字字符或“-”之外的任何内容,则需要使用方括号,以便保留原始值。如果没有用 [] 包围 key,则会删除不是字母数字或“-”的任何字符。例如,考虑将以下属性绑定到 Map:

acme:
  map:
    "[/key1]": value1
    "[/key2]": value2
    /key3: value3

上面的属性将绑定到以 /key1、/key2 和 key3 作为键的 Map。

合并复杂类型

当在多个位置配置列表时,重写通过替换整个列表来工作。

例如,假设一个 MyPojo 对象的 name 和 description 属性默认为空。下面的示例公开来自 AcmeProperties 的 MyPojo 对象列表:

@ConfigurationProperties("acme")
public class AcmeProperties {

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

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

}

考虑下面的配置:

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

如果 dev profile 未激活,则 AcmeProperties.list 包含一个 MyPojo 实体,如前所定义。但是,如果启用了 dev profile,则 list 仍然只包含一个实体(name:my another name,description:null)。此配置不会向列表中添加第二个 MyPojo 实例,也不会合并实例。

在多个 profiles 中指定 List 时,将使用优先级最高的(且仅使用该 List)。请考虑以下示例:

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

在上面的示例中,如果 dev profile 已激活,则 AcmeProperties.list 包含一个 MyPojo 实体(name:my another name ,description:null)。对于 YAML,可以使用逗号分隔的列表和 YAML 列表来完全覆盖列表的内容。

对于 Map 属性,你可以绑定来自多个源的属性值。但是,对于多个源中的同一属性,将使用优先级最高的属性。以下示例公开来自 AcmeProperties 的 Map<String,MyPojo>:

@ConfigurationProperties("acme")
public class AcmeProperties {

    private final Map<String, MyPojo> map = new HashMap<>();

    public Map<String, MyPojo> getMap() {
        return this.map;
    }

}

考虑下面的配置:

acme:
  map:
    key1:
      name: my name 1
      description: my description 1
---
spring:
  profiles: dev
acme:
  map:
    key1:
      name: dev name 1
    key2:
      name: dev name 2
      description: dev description 2

如果 dev profile 未激活,则 AcmeProperties.map 包含一个键为 key1 的实体(name:my name 1,description:my description 1)。但是,如果启用了 dev profile,那么 map 包含两个实体,其中键为 key1(name :my name 1,description:my description 1)和 key2(name :my name 2,description:my description 2)。

注释:前面的合并规则适用于来自所有属性源的属性,而不仅仅是 YAML 文件。

24.8.4、属性转换

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

注释:由于此 bean 在应用程序生命周期的早期被请求,请确保限制 ConversionService 正在使用的依赖项。通常,你需要的任何依赖项在创建时都可能未完全初始化。如果自定义 ConversionService 对配置键强制(coercion)来说不是必须的,并且它仅依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器,则可能需要重命名它。

转换持续时间

Spring Boot 对表示持续时间有专门的支持。如果公开 java.time.Duration 属性,则应用程序属性中的以下格式可用:

(1)常规的 long 表示(如果没有指定 @DurationUnit,则使用毫秒作为默认单位)
(2)java.time.Duration 使用的标准 ISO-8601 格式
(3)一种更可读的格式,其中值和单位是结合在一起的(例如,10s 表示 10 秒)

考虑以下示例:

```
@ConfigurationProperties("app.system")
public class AppSystemProperties {

    @DurationUnit(ChronoUnit.SECONDS)
    private Duration sessionTimeout = Duration.ofSeconds(30);

    private Duration readTimeout = Duration.ofMillis(1000);

    public Duration getSessionTimeout() {
        return this.sessionTimeout;
    }

    public void setSessionTimeout(Duration sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }

    public Duration getReadTimeout() {
        return this.readTimeout;
    }

    public void setReadTimeout(Duration readTimeout) {
        this.readTimeout = readTimeout;
    }

}
```

要指定 30 秒的会话超时,30、PT30S 和 30s 都是等价的。读取超时 500ms 可以用以下任何形式指定:500、PT0.5S 和 500ms。

还可以使用任何受支持的单位。它们是:

(1)ns:纳秒
(2)us:微秒
(3)ms:毫秒
(4)s:秒
(5)m:分钟
(6)h:小时
(7)d:天

默认单位是毫秒,可以使用 @DurationUnit 重写,如上面的示例所示。

提示:如果你是从简单使用 Long 来表示持续时间的以前版本升级,请确保在切换到 Duration 的同时定义单位(如果不是毫秒,则使用 @DurationUnit 定义)。这样做提供了一个透明的升级路径,同时支持更丰富的格式。

转换数据大小

Spring Framework 有一个 DataSize 值类型,允许以字节表示大小。如果公开 DataSize 属性,则应用程序属性中的以下格式可用:

(1)常规的 long 表示(如果没有指定 @DataSizeUnit,则使用字节作为默认单位)
(2)一种更可读的格式,其中值和单位是结合在一起的(例如,10MB 表示 10 兆字节)

考虑下面的示例:

@ConfigurationProperties("app.io")
public class AppIoProperties {

    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize bufferSize = DataSize.ofMegabytes(2);

    private DataSize sizeThreshold = DataSize.ofBytes(512);

    public DataSize getBufferSize() {
        return this.bufferSize;
    }

    public void setBufferSize(DataSize bufferSize) {
        this.bufferSize = bufferSize;
    }

    public DataSize getSizeThreshold() {
        return this.sizeThreshold;
    }

    public void setSizeThreshold(DataSize sizeThreshold) {
        this.sizeThreshold = sizeThreshold;
    }

}

要指定 10 兆字节的缓冲区大小,10 和 10MB 是等价的。256 字节的大小阈值可以指定为 256 或 256B。

还可以使用任何受支持的单位。它们是:

(1)B:字节
(2)KB:千字节
(3)MB:兆字节
(4)GB:千兆字节
(5)TB:兆兆字节

默认单位是字节,可以使用 @DataSizeUnit 重写,如上面的示例所示。

提示:如果你是从简单使用 Long 来表示大小的以前版本升级,请确保在切换到 DataSize 的同时定义单位(如果不是字节,则使用 @DataSizeUnit 定义)。这样做提供了一个透明的升级路径,同时支持更丰富的格式。

@ConfigurationProperties 验证

每当使用 Spring 的 @Validated 注解对 @ConfigurationProperties 类进行注解时,Spring Boot 就会尝试验证它们。你可以直接在配置类上使用 JSR-303 javax.validation 约束注解。为此,请确保类路径上有一个兼容的 JSR-303 实现,然后将约束注解添加到字段上,如下面示例所示:

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}

提示:你还可以通过注解 @Bean 方法来触发验证,该方法使用 @Validated 创建配置属性。

虽然在绑定时也会验证嵌套属性,但最好还是将关联字段标注为 @Valid。这确保即使找不到嵌套属性,也会触发验证。以下示例基于前面的 AcmeProperties 示例:

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {

    @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 类。这样做可以避免任何可能由早期实例化引起的问题。有一个属性验证示例,演示了如何设置。

提示:spring-boot-actuator 模块包括一个端点,该端点公开所有 @ConfigurationProperties bean。将 web 浏览器指向 /actuator/configprops 或使用等价的 JMX 端点。详见“生产就绪功能”章节。

@ConfigurationProperties 和 @Value

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

功能 @ConfigurationProperties @Value
宽松的绑定
元数据支持
SpEL

如果你为自己的组件定义了一组配置键,我们建议你将它们分组到一个带有 @ConfigurationProperties 注解的 POJO 中。你还应该注意到,由于 @Value 不支持宽松绑定,因此如果你需要使用环境变量来提供值,那么它就不是一个好的选择。

热门相关:恶魔总裁霸道宠:老婆,太惹火   我在镇夜司打开地狱之门   驭房有术   龙皇缠身:爱妃,来生蛋!   九星之主