SpringBoot学习(二)Externalized Configuration(外部化配置)

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

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

  1. Devtools global settings properties in the $HOME/.config/spring-boot folder when devtools is active.

  2. @TestPropertySource annotations on your tests.

  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.

  4. Command line arguments.

  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).

  6. ServletConfig init parameters.

  7. ServletContext init parameters.

  8. JNDI attributes from java:comp/env.

  9. Java System properties (System.getProperties()).

  10. OS environment variables.

  11. RandomValuePropertySource that has properties only in random.*.

  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).

  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).

  14. Application properties outside of your packaged jar (application.properties and YAML variants).

  15. Application properties packaged inside your jar (application.properties and YAML variants).

  16. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.

  17. Default properties (specified by setting SpringApplication.setDefaultProperties).

翻译后常见的(优先级由高->低):

  1. 命令行参数。
  2. 通过 System.getProperties() 获取的 Java 系统参数。
  3. 操作系统环境变量。
  4. 从 java:comp/env 得到的 JNDI 属性。
  5. 通过 RandomValuePropertySource 生成的“random.*”属性。
  6. 应用 Jar 文件之外的属性文件。(通过spring.config.location参数)
  7. 应用 Jar 文件内部的属性文件。
  8. 在应用配置 Java 类(包含“@Configuration”注解的 Java 类)中通过“@PropertySource”注解声明的属性文件。
  9. 通过“SpringApplication.setDefaultProperties”声明的默认属性。

  Spring Boot 的这个配置优先级看似复杂,其实是很合理的。比如命令行参数的优先级被设置为最高。
  这样的好处是可以在测试或生产环境中快速地修改配置参数值,而不需要重新打包和部署应用。

  SpringApplication 类默认会把以“--”开头的命令行参数转化成应用中可以使用的配置参数,如 “--name=Alex” 会设置配置参数 “name” 的值为 “Alex”。如果不需要这个功能,可以通过   “SpringApplication.setAddCommandLineProperties(false)” 禁用解析命令行参数。

为了提供一个具体的例子,假设您开发了一个使用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属性。例如在UNIX shell: 

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

也可以支持JSON,在系统变量中使用spring.application.json :
$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar
命令行参数也执行JSON:
$ java -jar myapp.jar --spring.application.json='{"name":"test"}'

1.配置随机值

RandomValuePropertySource通常被用来注入随机值(例如secrets或test cases),它可以产生整数、长整数、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]}

2.访问命令行属性

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

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

3.应用程序属性文件

SpringApplication在以下的位置中,从application.properties文件中加载属性,并且把它们加入的Spring的环境中:

a.当前目录的/config子目录

b.当前目录

c.包的/config类路径

d.根的类路径

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

注:可以使用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

通过命令行执行文件:

java -jar myproject.jar -Dconfig.path=/data/my/config  -Dspring.config.location=/data/my/config/

注:spring.config.name和spring.config.location在很早的时候就被用来决定要加载哪些文件。它们必须定义为环境属性。

如果spring.config.location包含目录,需以“/”结尾

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

  1. file:./config/

  2. file:./

  3. classpath:/config/

  4. classpath:/

如果spring.config.location配置的值是:classpath:/custom-config/,file:./custom-config/,搜索顺序将变成:

  1. file:./custom-config/

  2. classpath:custom-config/

如果附加配置classpath:/custom-config/,file:./custom-config/,那么搜索顺序将变成:

  1. file:./custom-config/

  2. classpath:custom-config/

  3. file:./config/

  4. file:./

  5. classpath:/config/

  6. classpath:/

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

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

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

4.Profile-specific Properties(特殊概要文件的属性)

除了application.properties文件,特定于概要文件的属性也可以通过使用以下命名约定来定义:application-{profile}.properties。环境有一组默认的配置文件(默认情况下是[default]),如果没有设置活动的配置文件,就使用这些配置文件。换句话说,如果没有显式激活配置文件,则使用应用程序默认的application-default.properties加载属性。

特定的文件属性总会重写配置,无论该配置文件是jar包内还是jar包外。

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

例如:

spring.profiles.active: dev

将加载配置文件:application-dev.properties

注:如果你使用spring.config.location来配置指定文件,那么这些特殊概要文件的属性都不会被考虑。如果你仍想使用特殊概要文件的属性,spring.config.location时配置目录

5.在配置文件中使用占位符

application.properties的属性值可以被过滤,通过之前使用的已存在的环境值,我们可以返回以前定义的值。例如:

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

6.加密属性

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

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

7.使用YAML替代Properties

YAML是JSON的一个超集,因此是指定分层配置数据的一种方便的格式。当您的类路径中有SnakeYAML库时,SpringApplication类自动支持YAML作为属性的替代。

注:如果您使用“starter”,SnakeYAML是由spring-boot-starter自动提供的

7.1加载YAML

Spring框架提供了两个方便的类,可用于加载YAML文档。YamlPropertiesFactoryBean将YAML加载为属性,YamlMapFactoryBean将YAML加载为map映射。

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

如果是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使用列表来替代含下标的属性:

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

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

    private List<String> servers;

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

    public void setServers(List<String> servers) {
        return this.servers = servers;
    }
}

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

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

public class Config {

    @Value("${my.servers}")
    private List<String> servers;

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

    public void setServers(List<String> servers) {
        return this.servers = servers;
    }
}

7.3Multi-profile YAML文件

可以在一个单个文件中使用key为spring.profilse的配置来指定多个profile-specific 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

在前面的示例中,如果开发配置文件处于活动状态,则为服务器。地址属性是127.0.0.1。类似地,如果生产和eu-central配置文件处于活动状态,则服务器也处于活动状态。地址属性是192.168.1.120。如果未启用开发、生产和eu-central配置文件,则该属性的值为192.168.1.100。

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

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

然而,在下面的例子中,密码总是被设置,因为它没有附加到任何配置文件中,而且它必须在所有其他配置文件中被显式重置:

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

7.4YAML的缺点

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

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

application-dev.yml

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

如果您启动应用时,使用参数--spring.profiles.active=dev。你可能希望ecurity.user.password设置为“secret”,但情况并非如此。嵌套的文档将被过滤,因为主文件名为application-dev.yml。它已经被认为是特定于概要文件的,嵌套文档将被忽略。如果想用,可以在application.yml配置。

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

8.类型安全的配置属性

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

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

Feature @ConfigurationProperties @Value

Relaxed binding

Yes

No

Meta-data support

Yes

No

SpEL evaluation

No

Yes

8.1JavaBean属性绑定

可以绑定一个声明标准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.roles, String类型的集合,默认值是USER.

注:Spring引导自动配置大量使用@ConfigurationProperties来轻松配置自动配置的bean。与自动配置类类似,在Spring Boot中可用的@ConfigurationProperties类仅供内部使用。映射到类的属性(通过属性文件、YAML文件、环境变量等配置)是公共API,但是类本身的内容并不意味着可以直接使用。

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

a.map 只要被初始化,只需要getter,没有必要用setter,用setter可能被改变。

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

c.如果初始化了嵌套的POJO属性(如前面示例中的Security字段),则不需要setter。如果希望通过使用其默认构造函数动态创建实例,则需要一个setter。

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

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

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.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类的嵌套成员(如上面示例中的安全性)也将通过其构造函数绑定。可以使用@DefaultValue指定默认值,并应用相同的转换服务将字符串值强制转换为缺失属性的目标类型。

要使用构造函数绑定,必须使用@EnableConfigurationProperties或配置属性扫描启用该类。不能使用构造函数绑定由常规Spring机制创建的bean(例如@Component bean、通过@Bean方法创建的bean或使用@Import加载的bean)

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

@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

    }

}

猜你喜欢

转载自www.cnblogs.com/muxi0407/p/12056575.html