Spring Boot2(011):外部化配置 Externalized Configuration

Spring Boot2系列文章可以通过这里进行回顾:SpringBoot2(001):入门介绍、官网参考和博客汇总


  本文主要针对 SpringBoot 应用的外部化配置进行介绍,包括配置的加载顺序、如何配置随机数、访问命令行参数 等,主要参考官方文档:24.Externalized Configuration ,目录结构如下:

 

  SpringBoot 支持配置外部化 ( Externalized Configuration ),这样同一套应用代码就可以在不同的环境中运行。开发者可以通过 properties 文件YAML 文件环境变量命令行参数 等方式进行外部化配置。属性值可以通过 @Value 注解直接注入到 bean 里面,也可以通过使用 Spring 的 Environment 接口来进行访问,或者是通过 @ConfigurationProperties 绑定到结构化对象里面。

  SpringBoot 使用了一个非常特殊的 PropertySource 顺序,用于允许合理地覆盖属性值。属性读取顺序如下(越靠前的优先级越高):

  • Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  • @TestPropertySource annotations on your tests.
  • properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  • Command line arguments. 命令行参数
  • Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  • ServletConfig init parameters.
  • ServletContext init parameters.
  • JNDI attributes from java:comp/env.
  • Java System properties (System.getProperties()). Java 系统属性
  • OS environment variables. 操作系统环境变量
  • A RandomValuePropertySource that has properties only in random.*. 随机值属性来源
  • Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  • Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  • Application properties outside of your packaged jar (application.properties and YAML variants).
  • Application properties packaged inside your jar (application.properties and YAML variants).
  • @PropertySource annotations on your @Configuration classes.
  • Default properties (specified by setting SpringApplication.setDefaultProperties).

  举个例子,如果有一个通过 @Component 注解的 bean,其内部有个 name 属性,则可以通过如下方式来进行配置赋值:

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

@Component
public class MyBean {

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

// ...

}

   在应用的类路径 classpath(例如,在 jar 包中),可以有一个 application.properties 文件来为 name 属性提供一个合理的默认值。当运行在一个新环境时,可以通过提供外置(jar 包之外)的 application.properties 来覆盖 name 属性配置。对于一次性测试,还可以通过命令行参数来进行 name 属性的切换(例如:java -jar app.jar --name="Spring")。

注:

  • SPRING_APPLICATION_JSON 属性可以通过命令行进行提供并作为环境变量,例如,通过 unix shell 命令:
SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar
  • 上面这个例子中,其实是给 Spring 的 Environment 提供了属性 acme.name=test。也可以通过 spring.application.json 系统变量来提供 JSON:
java -Dspring.application.json='{"name":"test"}' -jar myapp.jar
  •  还可以通过以下命令行的方式来提供 JSON:
java -jar myapp.jar --spring.application.json='{"name":"test"}'
  •  通过 JNDI 变量的方式来提供 JSON 也是支持的:
java:comp/env/spring.application.json

  

1、配置随机数

  RandomValuePropertySource 可以用于注入随机数(例如:注入密码或者测试案例),支持 integers, longs, uuids, 或者 strings,如下示例:

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

   The random.int* syntax is OPEN value (,max) CLOSE where the OPEN,CLOSE are any character and value,max are integers. If max is provided, then value is the minimum value and max is the maximum value (exclusive).

 

2、访问命令行属性参数

  默认情况下,SpringApplication 会将所有命令行可选参数(-- 开头的参数,例如 --server.port=9000)转换成属性并添加到 Spring 的 Environment 中。正如前面提到的,命令行参数优先于其他属性源。

  开发者如果不想将命令行参数添加到 Environment 中,可以选择关闭:

SpringApplication.setAddCommandLineProperties(false);

  

3、应用属性文件(application.properties or application.yml)

  SpringApplication 会从以下位置加载 application.properties 并将属性添加到 Spring 的 Environment 中:

  • 1、当前目录下的 config 目录
  • 2、当前目录
  • 3、classpath 下的 /config 目录
  • 4、classpath 根目录

  这是按照优先级排序的,前面的会覆盖后面的。

注:可以使用 .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.namespring.config.location 会比较早地用于确认需要加载哪些文件,因此需要被定义为环境变量(操作系统环境变量、系统属性或者命令行参数)

  如果 spring.config.location 包含目录,需要以 / 结尾(并且在运行时会自动追加 spring.config.name 的名称,包括特定属性文件名 profile-specific file)。spring.config.location 中指定的文件的配置会原样使用,会被任意的 profile-specific properties 覆盖。(Files specified in spring.config.location are used as-is, with no support for profile-specific variants, and are overridden by any profile-specific properties.

  application.properties(.yml)最终的搜索顺序:

  • 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 来指定追加路径的话,那么这个追加的路径会被添加到默认路径之前进行搜索。例如,如果 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 (或者其他通过 spring.config.name 设置的任意文件名)中设置默认值,然后在指定的路径中的任意一个配置文件中进行覆盖。

注:如果使用环境变量而不是系统变量,大多数操作系统不允许点分开的键值名称,应该使用下划线进行替代(例如,使用 SPRING_CONFIG_NAME 替代 spring.config.name)。

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

 

4、特定配置属性 Profile-specific Properties

  除了 application.properties 文件外,还可以通过这样的命名约束来加载特定的属性配置文件:

application-{profile}.properties

   如果没有设置配置文件的话,Spring 的 Environment 会有一个默认的 default 文件,换句话说,没有其他配置文件的情况下,Environment 会加载默认的 application-default.properties 文件。

  Profile-specific 属性文件会从 application.properties 所在目录进行加载,profile-specific 文件总是会覆盖非 profile-specific 文件的设置,不管 profile-specific 文件在 jar 包内或 jar 包外。

  如果指定了多个配置文件,则会采取最后配置策略(a last-wins strategy),例如,在通过 spring.profiles.active 指定的配置文件会在 SpringApplication API 配置的配置文件之后被加载进来,因此具有优先权。

注:如果 spring.config.location 中有指定配置文件(1个或者多个,可能有目录),则 profile-specific 配置文件不会被加载。如果还需要加载的话,spring.config.location 得使用目录配置。

 

5、属性中的占位符

  application.properties 文件中的配置在使用时会被已存在的 Environment 进行过滤,因此可以引用之前已定义加载的配置(例如,系统变量,因为系统变量比 application.properties 先加载)

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

 注:可以使用短变量来代替已存在的 Spring Boot 属性,参考:Section 77.4, “Use ‘Short’ Command Line Arguments”

  例如使用 --port=9000 代替 --server.port=9000

server.port=${port:8080}

  

6、加密配置

  Spring Boot 并没有提供任何内置支持来进行属性的加密,但是提供了修改 Environment 中的属性值的扩展点。EnvironmentPostProcessor 接口允许在应用启动前对 Environment 进行操作。详情参考:Section 76.3, “Customize the Environment or ApplicationContext Before It Starts”

  如果开发者正在寻找存储凭证和密码的安全方式, Spring Cloud Vault 项目提供了在 HashiCorp Vault 存储外部化配置的支持。

 

7、使用 YAML 代替属性

  YAML 是 JSON 的超集,并且是表示层级配置数据的一种便捷方式。只要应用的 classpath 下有 SnakeYAML 包,SpringApplication 就会自动支持 YAML 作为 properties 的一种替代。

注:如果使用了 springboot 的 “Starters”,spring-boot-starter 会自动添加 SnakeYAML 包依赖。

 

7.1、加载 YAML

  Spring 框架提供了2个便捷的类用于加载 YAML 文档。YamlPropertiesFactoryBean 用于将 YAML 作为属性文件进行加载,而 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] 作为区别:

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

  

7.2、在 Spring Environment 中将 YAML 公开为属性

  YamlPropertySourceLoader 可以用于将 YAML 公开为 PropertySource 供 Environment 使用。这样做可以让开发者使用 @Value 注解和占位符直接访问 YAML 属性。

 

7.3、多个 YAML 文件

  开发者可以在一个单独的文件里面声明指定多个 YAML 文档(profile-specific YAML documents),然后通过 spring.profiles 来指示使用哪个:

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 profile 被激活,server.address 属性将是 127.0.0.1 。类似地,如果 production 和 eu-central profile 被激活,server.address 属性将是 192.168.1.120 。如果 development、production 和 eu-central profile 都没有被激活,则最终使用的是 192.168.1.100 。(通过参数 spring.profiles.active 来指定使用哪个,例如 java -jar myproject.jar --spring.profiles.active=development(,production)

注:spring.profiles 可以包含一个简单的 profile 名称(例如:production)或者一个 profile 表达式。profile 表达式允许表达更复杂的逻辑,例如:production & (eu-central | eu-west) ,具体可以参考:reference guide : beans-definition-profiles-java

  应用上下文启动时,如果没有显式指定激活的 profile ,则默认的 profiles 将被激活。如下例子中,我们给 spring.security.user.password 设置了一个可用的值,而且仅在 "default" profile 中有效。 

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

  然而,在如下例子中,由于没有关联到任何 profile,password 将总被设置,并且有必要的话可以在其他的 profiles 被显式地覆盖重置: 

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

    通过 ! 可以对 spring.profiles 元素指定的 profiles 进行取反。如果 negated 和 non-negated profiles 都指定同一单一文件,至少需要匹配一个 non-negated profile,可能不会匹配任何 negated profiles。

参考:SpringBoot 使用yaml文件实现多配置

 

7.4、YAML 的缺点

  YAML 文件不能通过 @PropertySource 注解进行加载,如果需要使用这种方式进行加载,需使用 properties 文件。

  在 profile-specific YAML 文件中使用 multi YAML document 语法时,可能会导致意想不到的行为。考虑如下一个名为 application-dev.yml 的文件,并且 dev profile 被激活的场景下: 

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

  上面这个例子中,profile 取反和 profile 表达式将不会如预期一样。我们推荐不要将 profile-specific YAML 文件和 multiple YAML documents 一起使用,并且只坚持使用其中1种就可以。

 

8、类型安全的配置属性

  使用 @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 定义了以下这些属性:

  • acme.enabled:默认 false
  • acme.remote-address:使用可以从 String 强制转换的类型
  • acme.security.username:嵌套的 "security" 对象,其名称由属性的名称决定。需要特别注意的是,这里根本没有使用返回类型,有可能是 SecurityProperties。
  • acme.security.password
  • acme.security.roles:String 集合类型,String 列表

 注:getters 和 setters 通常情况下应该是强制性的,因为属性绑定是通过标准的 Java Beans 属性描述符,就像 Spring MVC 一样。在以下场景下,setter 函数或可以忽略:

  • Maps,只要有初始化过,需要有一个 getter 但 setter 非必须,因为可以通过 binder 进行修改。
  • Collections 和 arrays:既可以通过索引(通常是 YAML)访问,也可以通过使用逗号分隔的值(属性)进行确认。后者这种场景下,setter 函数是强制性的。对于这些类型,我们建议总是增加相应的 setter 函数。如果初始化了集合,需要确保是可变集合(not immutable ,即 mutable,如上面的例子)。

  如果嵌套的 POJO 属性已经初始化(如上面例子中的 Security), setter 函数并不是必须的。如果希望绑定器 binder 使用其默认构造函数动态创建实例,则需要一个 setter。

  一些项目使用了 Lombok 来自动产生 getters 和 setters 。需确保 Lombok 不会为这样的类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。

  最后需要注意的是,只会绑定标准的 Java Bean 属性,不支持静态变量的绑定

注:看 @Value 和 @ConfigurationProperties 的差别: differences between @Value and @ConfigurationProperties.

  开发者需要列出属性类以便注册到 @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 只处理 environment 相关,而且不要注入上下文的其他 bean。记住 @EnableConfigurationProperties 注解会自动应用到项目中,以便从 Environment 中配置任何带有 @ConfigurationProperties 注解的 bean 。

  不选择将 MyConfiguration 用 @EnableConfigurationProperties(AcmeProperties.class) 注解声明的话,可以将 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 提供自动完成功能。具体参考:Appendix B, Configuration Metadata

 

8.1、第三方配置

  除了使用 @ConfigurationProperties 对类进行注解外,还可以对 @Bean 方法进行注解。当开发者希望属性绑定到无法控制的第三方组件时,这样做特别有用。

  如下的例子,为了从 Environment 属性中获取配置到 bean 中,添加 @ConfigurationProperties 到这个 bean 注册中:

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

  任何有 another 前缀的属性定义将映射到 AnotherComponent 这个方法返回的 bean 中,方式类似于前面提到的 AcmeProperties 。

 

8.2、松绑定 Relaxed Binding

  SpringBoot 使用一些宽松的规则将环境属性绑定到 @ConfigurationProperties bean ,因此环境属性名和 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 Kebab case,短横线命名,.properties 和 .yml 文件的推荐格式
acme.myProject.person.firstName Camel case,标准的驼峰语法
acme.my_project.person.first_name underscore notation,下划线命名格式,.properties 和 .yml 文件的另一种选择格式
ACME_MYPROJECT_PERSON_FIRSTNAME Upper case,全大写格式,推荐在系统环境变量中使用

笔者注:这其实是几种比较流行的命名方式。

注:注解中的前缀值必须是 Kebab 格式的(短横线格式,小写字母并且被 - 分隔,例如 acme.my-project.person )。

  每种属性源对应的绑定规则:

属性配置源 简化 列表
properties 文件 Camel case, kebab case, or underscore notation驼峰、短横线 或者 下划线 标准的列表语法 [] 或者逗号分割
YAML 文件 Camel case, kebab case, or underscore notation驼峰、短横线 或者 下划线 标准的 yaml 列表语法或者逗号分隔
环境变量 Upper case format with underscore as the delimiter.大写格式且下划线 _ 作为分隔符,而且下划线 _ 不应该出现在属性名中 下划线包围的数值,例如:MY_ACME_1_OTHER = my.acme[1].other
系统变量 Camel case, kebab case, or underscore notation驼峰、短横线 或者 下划线 标准的列表语法 [] 或者逗号分割

注:properties 文件建议尽可能使用小写字母的短横线格式,例如 my.property-name=acme 。

  当映射到 Map 属性时,如果 key 需要包含除了小写字母、数字、和 - 之外的字符,需要使用括号表示法,以便保留原始值。如果 key 没有被 [] 包围,则会删除所有非字母-数字和 - 字符。

  When binding to Map properties, if the key contains anything other than lowercase alpha-numeric characters or -, you need to use the bracket notation so that the original value is preserved. If the key is not surrounded by [], any characters that are not alpha-numeric or - are removed. 

  如下例子中,绑定 key 值将会是 /key1, /key2 和 key3:

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

  

8.3、合并复杂类型

  当列表在多个地方配置时,覆盖是按照整个列表进行替换的。

  例如,假设 MyPojo 对象有 name 和 description 属性,默认是 null,如下示例中 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 元素(name: my name, decsription: my description);如果激活了,则 list 包含一个元素(下面那个:name: my another name, decsription: null)。这种配置并不会添加第二个 MyPojo 元素到列表中,也不会进行合并。

  当一个列表 List 在多个 profiles 配中指定时,其中优先级最高的那个(也只有这个)会被使用。考虑如下示例:

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 只会包含一个元素(下面那个:name: my another name, decsription: null)。对于 YAML 配置,逗号分隔列表和 YAML 列表都可以用于完全覆盖列表内容。

  对于 Map 属性,开发者也可以从多个配置源种绑定属性。然而,对于多个来源的同个属性的配置,也只会使用优先级最高的,如下例子: 

@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(MyPojo 中 name: my name 1, description: my description 1);如果被激活的话,则会包含2个元素,即 key1(name: dev name 1, description: null) 和 key2(name: dev name 2, description: dev description 2)

注:前面提到的合并规则适用于所有配置源,不仅仅是 YAML 文件。

 

8.4、属性类型转换

  当绑定到 @ConfigurationProperties bean 时,SpringBoot 会尝试将外部应用配置强制转换成合适的类型。如果开发者需要定制类型转换,可以通过提供一个 ConversionService bean(bean 名称是 conversionService ),或者定制属性编辑器(通过一个 CustomEditorConfigurer bean),又或者定制转换器 Converters (bean 定义声明中注解为 @ConfigurationPropertiesBinding )。

注:由于这种 bean 在应用的生命周期中会比较早地使用到,需确保限制 ConversionService 用到的依赖。任何需要用到的依赖在创建时可能还未完全初始化。

  如果配置的 keys 的强制转换不需要用到 ConversionService 并且只依赖于 @ConfigurationPropertiesBinding 声明的定制转换器的话,开发者可以重命名定制的 ConversionService 的 bean 名称。

 

8.4.1、转换耗时 Converting durations

  SpringBoot 已支持表示转换耗时。如果开发者公开了一个 java.time.Duration ,则应用属性中的下面这些格式是可用的:

  如下示例: 

@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 中任意一种形式来指定。

  开发者也可以使用以下任意一种支持的计时单元:

  • ns for nanoseconds,纳秒
  • us for microseconds,微秒
  • ms for milliseconds,毫秒
  • s for seconds,秒
  • m for minutes,分钟
  • h for hours,小时
  • d for days,天

  默认的是毫秒,正如上面所示,可以通过使用 @DurationUnit 进行覆盖。

注:如果开发者是从仅仅使用 Long 来表示耗时的先前的一个版本升级过来的,那么需要确保在切换过程中,如果原先不是定义为毫秒的话,需要同时定义计时单位(使用 @DurationUnit)。这样做就提供了一种透明的升级路径,同时可支持更丰富的格式。

 

8.4.2、转换的数据大小 Converting Data Sizes

  Spring 框架有个 DataSize 值类型,允许以字节表示大小。如果开发者公开了一个 DataSize 属性,则应用属性中的如下格式都是可用的:

  • 常规的 long 显示格式(使用字节作为默认单位,除非通过 @DataSizeUnit 另行制定)
  • 一种可读性更好的格式,值和计量单位耦合使用(例如 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 。

  以下任意单位都支持:

  • B for bytes
  • KB for kilobytes
  • MB for megabytes
  • GB for gigabytes
  • TB for terabytes

  默认是字节,可以通过使用 @DataSizeUnit 来进行覆盖,如同上面的例子演示的一样。

注:如果开发者是从仅仅使用 Long 来表示大小的先前的一个版本升级过来的,那么需要确保在切换过程中,如果原先不是定义为 bytes 的话,需要同时定义计量单位(使用 @DataSizeUnit)。这样做就提供了一种透明的升级路径,同时可支持更丰富的格式。

 

8.5、@ConfigurationProperties 校验

  只要 @ConfigurationProperties 类使用了 Spring 的 @Validated 进行注解时,SpringBoot 就会尝试对这些类进行验证。开发者可以使用 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 类就能够被创建。这样做可以避免早期实例化时可能导致的任何问题。参考这个演示如何设置的示例: property validation sample

注:spring-boot-actuator 模块包含了一个公开所有 @ConfigurationProperties bean 的 endpoint,将 web 浏览器指向 /actuator/configprops 或者使用对等的 JMX endpoint。

 

8.6、@ConfigurationProperties vs. @Value

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

Feature @ConfigurationProperties @Value
Relaxed binding Yes No
Meta-data support Yes No
SpEL evaluation No Yes

  如果开发者为组件定义了一组配置 keys ,建议将它们分组到一个带有 @ConfigurationProperties 注解的 POJO 中。另外开发者需要注意的是,由于 @Value 不支持 relaxed binding,在需要使用环境变量来提供值的时候 @Value 并不是一种很好的选择。

  最后,@Value 支持 SpEL 表达式,但这些表达式并不是配置文件(application.properties 或者 yaml )中处理的。

 

9、参考

 

posted @ 2020-07-22 23:32  心明谭  阅读(953)  评论(0编辑  收藏  举报