Spring Boot 功能指南(二)

2. 外部化配置

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

Spring Boot使用一个非常特殊的PropertySource顺序,该顺序旨在允许合理地覆盖值。按以下顺序考虑属性:

  1. 当devtools处于活动状态时,文件夹$HOME/.config/spring-boot中的Devtools全局设置属性

  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. 操作系统环境变量。

  11. RandomValuePropertySource,只有在random.*属性

  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注解。请注意,在刷新应用程序上下文之前,不会将此类属性源添加到Environment中。现在配置某些属性(如logging。*和spring.main。*)为时已晚,这些属性在刷新开始之前就已读取。

  17. 默认属性(通过设置SpringApplication.setDefaultProperties指定)。

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

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

@Component
public class MyBean {

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

    // ...

}

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

这些SPRING_APPLICATION_JSON属性可以在命令行中提供环境变量。例如,您可以在UN * X shell中使用以下行:

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

在前面的示例中,您最终acme.name=test在Spring中Environment。您还可以像spring.application.json在System属性中一样提供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

2.1. 配置随机值

RandomValuePropertySource可用于注入随机值(例如,注入机密或测试用例)。它可以产生整数,longs,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为最大值(不包括)。

2.2. 访问命令行属性

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

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

2.3. 应用程序属性文件

SpringApplicationapplication.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(或您选择的其他任何基本名称spring.config.name)。然后,可以在运行时使用自定义位置之一中的其他文件覆盖这些默认值。

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

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

2.4. 特定于配置文件的属性

除了application.properties文件之外,还可以使用以下命名约定来定义特定于配置文件的属性:application- {profile} .properties。如果没有设置活动配置文件,则环境具有一组默认配置文件(默认为[default])。换句话说,如果没有明确激活任何配置文件,则将加载来自application-default.properties的属性。

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

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

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

2.5. 属性中的占位符

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

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

您还可以使用此技术来创建现有Spring Boot属性的“简短”变体。有关详细信息,请参见 howto.html 操作方法。 

2.6. 加密属性

Spring Boot不提供对加密属性值的任何内置支持,但是,它确实提供了修改Spring Environment包含的值所必需的挂钩点。该EnvironmentPostProcessor接口允许您在应用程序启动之前对Environment进行操作。有关详细信息,请参见howto.html

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

2.7. 使用 YAML 代替 Properties

YAML is a superset of JSON and, as such, is a convenient format for specifying hierarchical configuration data. The SpringApplication class automatically supports YAML as an alternative to properties whenever you have the SnakeYAML library on your classpath.

YAML是JSON的超集,因此是一种用于指定层次结构配置数据的便捷格式。只要在类路径上具有SnakeYAML库,SpringApplication类就会自动支持YAML作为属性的替代方法。

如果您使用“starter”,则SnakeYAML将由自动提供spring-boot-starter 

2.7.1. 加载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]解引用器表示为属性键。例如,考虑以下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所做的)那样,绑定到类似的属性,您需要在类型为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;
    }
}

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

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

2.7.3. 多配置文件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。如果developmentproductioneu-central在配置文件没有启用,那么该属性的值192.168.1.100

spring.profiles因此可以包含一个简单的配置文件名称(例如production)或一个配置文件表达式。简档表达式允许例如表达更复杂的简档逻辑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元素指定的弹簧轮廓可以选择使用!字符来否定。如果为单个文档指定了否定配置文件和非否定配置文件,则至少一个非否定配置文件必须匹配,并且否定配置文件不能匹配。

2.7.4. YAML 的缺点

YAML files cannot be loaded by using the @PropertySource annotation. So, in the case that you need to load values that way, you need to use a properties file.

Using the multi YAML document syntax in profile-specific YAML files can lead to unexpected behavior. For example, consider the following config in a file:

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

在特定于配置文件的YAML文件中使用多YAML文档语法可能会导致意外行为。例如,考虑文件中的以下配置:

application-dev.yml
server:
  port: 8000
---
spring:
  profiles: "!test"
  security:
    user:
      password: "secret"

如果使用--spring.profiles.active=dev参数运行应用程序,则可能希望security.user.password将其设置为“secret”,但实际情况并非如此。

嵌套文档将被过滤,因为主文件名为application-dev.yml。它已经被认为是特定于配置文件的,并且嵌套文档将被忽略。

我们建议您不要混用特定于配置文件的YAML文件和多个YAML文档。坚持只使用其中之一。

2.8. 类型安全的配置属性

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

请查阅differences between @Value and type-safe configuration properties.

2.8.1. JavaBean 属性绑定

可以绑定一个声明标准JavaBean属性的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, 其嵌套的“安全”对象的名称由属性的名称确定。特别是,返回类型根本不使用,可能已经使用过SecurityProperties

  • acme.security.password.

  • acme.security.rolesString集合,其默认值为USER

映射到Spring Boot中可用的@ConfigurationProperties类的属性通过properties 文件,YAML文件,环境变量等进行配置,它们是公共API,但是该类本身的访问器(获取器/设置器)不能直接使用。

这种安排依赖于默认的空构造函数,并且getter和setter通常是强制性的,因为绑定是通过标准Java Beans属性描述符进行的,就像在Spring MVC中一样。在以下情况下,可以省略设置器:

  • 只要对地图进行了初始化,它们就需要使用获取器,但不一定需要使用设置器,因为它们可以被binder改变。
  • 可以通过索引(通常使用YAML)或使用单个逗号分隔的值(属性)来访问集合和数组。在后一种情况下,必须使用二传手。我们建议始终为此类类型添加设置器。如果初始化集合,请确保它不是不可变的(如上例所示)。
  • 如果嵌套的POJO属性已初始化(例如上例中的Security字段),则不需要setter。如果希望binder 通过使用其默认构造函数动态创建实例,则需要一个setter。

有些人使用Lombok项目自动添加获取器和设置器。确保Lombok不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化该对象。最后,仅考虑标准Java Bean属性,并且不支持对静态属性的绑定。

2.8.2. 构造函数绑定

上一节中的示例可以以不变的方式重写,如下例所示:

package com.example;

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

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConstructorBinding
@ConfigurationProperties("acme")
public class AcmeProperties {

    private final boolean enabled;

    private final InetAddress remoteAddress;

    private final Security security;

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

    public boolean isEnabled() { ... }

    public InetAddress getRemoteAddress() { ... }

    public Security getSecurity() { ... }

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

        public String getUsername() { ... }

        public String getPassword() { ... }

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

    }

}

在此设置中,@ConstructorBinding注解用于指示应使用构造函数绑定。这意味着绑定器将期望找到带有您希望绑定的参数的构造函数。

类的嵌套成员@ConstructorBinding(例如上面的Security示例)也将通过其构造函数进行绑定。

可以使用指定默认值,并且将使用@DefaultValue相同的转换服务将String值强制为缺少属性的目标类型。

要使用构造函数绑定,必须使用@EnableConfigurationProperties或配置属性扫描来启用该类。您无法将构造函数绑定与常规Spring机制创建的bean一起使用(例如@Component的beans,通过@Bean方法创建的bean或使用@Import加载的bean ) 

如果您的类有多个构造函数,则还可以直接在应绑定的构造函数上使用@ConstructorBinding。 

2.8.3. 启用@ConfigurationProperties注解的类型

Spring Boot提供了绑定@ConfigurationProperties类型并将其注册为Bean的基础架构。您可以逐类启用配置属性,也可以启用与组件扫描类似的方式进行配置属性扫描。

有时,带@ConfigurationProperties注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置,或者想要有条件地启用它们。在这些情况下,请使用@EnableConfigurationProperties注解指定要处理的类型列表。可以在任何@Configuration类上完成此操作,如以下示例所示:

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

要使用配置属性扫描,请在应用程序中添加@ConfigurationPropertiesScan注解。通常,它将@SpringBootApplication注解添加到使用的主应用程序类中,但可以将其添加到任何@Configuration类中。默认情况下,将从声明注解的类的包中进行扫描。如果要定义要扫描的特定程序包,可以按照以下示例所示进行操作:

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

使用配置属性扫描或通过@EnableConfigurationProperties注册@ConfigurationProperties Bean时,该Bean具有常规名称:<prefix>-<fqn>,其中<prefix>是@ConfigurationProperties注释和<fqn>中指定的环境密钥前缀。是Bean的完全限定名称。如果注释不提供任何前缀,则仅使用Bean的完全限定名称。上例中的Bean名称为acme-com.example.AcmeProperties。 

我们建议@ConfigurationProperties仅处理环境,尤其不要从上下文中注入其他bean。对于极端情况,可以使用setter注入或框架提供的任何* Aware接口(例如,如果需要访问环境,则可以使用例如EnvironmentAware)。如果仍然要使用构造函数注入其他bean,则必须使用@Component注解配置属性bean,并使用基于JavaBean的属性绑定。

2.8.4. 使用带@ConfigurationProperties注解的类型

这种配置样式特别适合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 可以使用这些文件为您自己的密钥提供自动完成功能。有关详细信息,请参阅附录。

2.8.5. 第三方配置

除了使用 @ConfigurationProperties 来注释类之外,您还可以在公共 @Bean 方法上使用它。当您想要将属性绑定到您无法控制的第三方组件时,这样做会特别有用。

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

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

使用另一个前缀定义的任何 JavaBean 属性都以类似于前面的 AcmeProperties 示例的方式映射到该另一个组件 bean。

2.8.6. Relaxed Binding

Spring Boot 使用一些宽松的规则将环境属性绑定到 @ConfigurationProperties bean,因此环境属性名称和 bean 属性名称之间不需要完全匹配。这很有用的常见示例包括用破折号分隔的环境属性(例如,上下文路径绑定到上下文路径)和大写环境属性(例如,端口绑定到端口)。
例如,请考虑以下 @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;
    }

}

使用上述代码,可以使用以下属性名称:

Table 2. 松绑定
属性说明

acme.my-project.person.first-name

Kebab 案例,建议在 .properties 和 .yml 文件中使用。

acme.myProject.person.firstName

标准驼峰式大小写语法。

acme.my_project.person.first_name

下划线表示法,这是在 .properties 和 .yml 文件中使用的另一种格式。

ACME_MYPROJECT_PERSON_FIRSTNAME

大写格式,在使用系统环境变量时推荐。
注释的前缀值必须是 kebab 大小写(小写并用 - 分隔,例如 acme.my-project.person)
Table 3. 每个属性源的松绑定规则
Property SourceSimpleList

Properties Files

Camel case, kebab case, or underscore notation

使用 [ ] 或逗号分隔值的标准列表语法

YAML Files

Camel case, kebab case, or underscore notation

标准 YAML 列表语法或逗号分隔值

Environment Variables

Upper case format with underscore as the delimiter. _ should not be used within a property name

下划线包围的数值,例如 MY_ACME_1_OTHER = my.acme[1].other

System properties

Camel case, kebab case, or underscore notation

使用 [ ] 或逗号分隔值的标准列表语法

我们建议尽可能以小写的 kebab 格式存储属性,例如 my.property-name=acme。

绑定到 Map 属性时,如果键包含小写字母数字字符或 - 以外的任何内容,则需要使用括号表示法以保留原始值。如果键没有被 [] 包围,则所有不是字母数字或 - 的字符都将被删除。例如,考虑将以下属性绑定到Map:

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

上述属性将绑定到以 /key1、/key2 和 key3 作为映射中的键的映射。

对于 YAML 文件,括号需要用引号括起来才能正确解析键。

2.8.7. 合并复杂类型

当列表配置在多个位置时,覆盖通过替换整个列表来工作。
例如,假设 MyPojo 对象的名称和描述属性默认为空。以下示例定义了来自 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

如果开发配置文件未激活,则 AcmeProperties.list 包含一个 MyPojo 条目,如前所述。但是,如果启用了开发配置文件,则列表仍然只包含一个条目(名称为我的另一个名称,描述为 null)。此配置不会将第二个 MyPojo 实例添加到列表中,也不会合并项目。
当在多个配置文件中指定一个列表时,将使用具有最高优先级的一个(并且仅使用那个)。考虑以下示例:

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条目(名字是my another name,描述是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

If the dev profile is not active, AcmeProperties.map contains one entry with key key1 (with a name of my name 1 and a description of my description 1). If the dev profile is enabled, however, map contains two entries with keys key1 (with a name of dev name 1 and a description of my description 1) and key2 (with a name of dev name 2 and a description of dev description 2).

如果 dev 配置文件未激活,AcmeProperties.map 包含一个带有键 key1 的条目(名称为 my name 1,描述为 my description 1)。但是,如果启用了 dev 配置文件,则 map 包含两个条目,键为 key1(名称为 dev name 1,描述为 my description 1)和 key2(名称为 dev name 2, description 为 dev description 2) .

上述合并规则适用于所有属性源的属性,而不仅仅是 YAML 文件。

2.8.8. 属性转换

Spring Boot attempts to coerce the external application properties to the right type when it binds to the @ConfigurationProperties beans. If you need custom type conversion, you can provide a ConversionService bean (with a bean named conversionService) or custom property editors (through a CustomEditorConfigurer bean) or custom Converters (with bean definitions annotated as @ConfigurationPropertiesBinding).

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

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

转换工期
Spring Boot 专门支持表达持续时间。如果您公开 java.time.Duration 属性,则应用程序属性中的以下格式可用:

  • 常规长整型表示(使用毫秒作为默认单位,除非已指定 @DurationUnit)

  • ​​​java.time.Duration 使用的标准 ISO-8601 格式

  • 值和单位耦合的更易读的格式(例如,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 都是等效的。可以使用以下任何形式指定 500 毫秒的读取超时:500、PT0.5S 和 500 毫秒。
您还可以使用任何受支持的单位。这些是:

  • ns for nanoseconds

  • us for microseconds

  • ms for milliseconds

  • s for seconds

  • m for minutes

  • h for hours

  • d for days

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

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

转换数据大小
Spring Framework 有一个 DataSize 值类型,它以字节为单位表示大小。如果您公开 DataSize 属性,则应用程序属性中的以下格式可用:

  • 常规长整型表示(使用字节作为默认单位,除非已指定 @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 MB 的缓冲区大小,10 和 10MB 是等效的。可以将 256 字节的大小阈值指定为 256 或 256B。
您还可以使用任何受支持的单位。这些是:

  • B for bytes

  • KB for kilobytes

  • MB for megabytes

  • GB for gigabytes

  • TB for terabytes

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

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

2.8.9. @ConfigurationProperties 校验

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

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

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}
您还可以通过使用 @Validated 注释创建配置属性的 @Bean 方法来触发验证

为确保始终为嵌套属性触发验证,即使未找到任何属性,也必须使用 @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 方法应声明为静态的。配置属性验证器是在应用程序生命周期的早期创建的,将 @Bean 方法声明为静态可以创建 bean,而无需实例化 @Configuration 类。这样做可以避免早期实例化可能导致的任何问题。

spring-boot-actuator 模块包括一个公开所有 @ConfigurationProperties bean 的端点。将您的网络浏览器指向 /actuator/configprops 或使用等效的 JMX 端点。有关详细信息,请参阅“生产就绪功能”部分。

2.8.10. @ConfigurationProperties vs. @Value

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

Feature@ConfigurationProperties@Value

Relaxed binding

Yes

No

Meta-data support

Yes

No

SpEL evaluation

No

Yes

​​如果您为自己的组件定义了一组配置键,我们建议您将它们分组到使用 @ConfigurationProperties 注释的 POJO 中。您还应该注意,由于 @Value 不支持宽松绑定,因此如果您需要使用环境变量提供值,则它不是一个好的选择。
最后,虽然您可以在 @Value 中编写 SpEL 表达式,但此类表达式不会从应用程序属性文件中处理。
​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值