外部化配置-1

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

springboot使用了一个非常特殊的PropertySource顺序,它被设计成允许对值进行合理的重写。属性按以下顺序考虑:

  • Devtools处于活动状态时,$HOME/.config/spring boot目录中的Devtools全局设置属性。
  • 测试类上使用@TestPropertySource
  • 测试类上的属性。可在@SpringBootTest和测试注释上找到,用于测试应用程序的特定部分。
  • 命令行参数
  • 来自SPRING_APPLICATION_JSON的属性(嵌入在环境变量或系统属性中的内联JSON)。
  • ServletConfig 初始化参数
  • ServletContext 初始化参数
  • 来自java:comp/env的JNDI属性
  • 系统属性System.getProperties()
  • 操作系统环境变量
  • RandomValuePropertySource 上那些random.*属性
  • 在打包的jar之外配置特定的应用程序属性application-{profile}.properties或者application-{profile}.yaml
  • 打包在jar中的特定于概要文件的应用程序属性application-{profile}.properties或者application-{profile}.yaml
  • 打包的jar之外的应用程序属性application.properties或者application.yaml
  • 打包在jar之中的应用程序属性application.properties或者application.yaml
  • @Configuration上的@PropertySource注解,请注意,在刷新应用程序上下文之前,不会将此类属性源添加到环境中。
  • 默认属性,通过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属性。在新环境中运行时application.properties可以在jar外部覆盖这个name属性,对于一次性测试,可以使用特定的命令行开关来启动(例如, java -jar app.jar --name="Spring")。

在加载配置文件时,springboot还支持通配符位置。默认情况下,支持jar外部的config/*/通配符位置。指定时还支持spring.config.additional-location和spring.config.location

当配置属性有多个源时,通配符位置在Kubernetes这样的环境中特别有用。例如,如果你有一些Redis配置和一些MySQL配置,你可能希望将这两个配置分开,同时要求这两个配置都存在于application.properties中,这可能导致两个独立的application.properties安装在不同位置的文件,如/config/redis/application.properties和/config/mysql/application.properties。在这种情况下,如果通配符位置为config/*/,则会导致两个文件都被处理。

通配符位置只能包含一个并以/结尾(对于目录搜索位置)或*/<filename>(对于文件搜索位置)。带有通配符的位置将根据文件名的绝对路径按字母顺序排序。

可以在命令行中使用环境变量提供SPRING_APPLICATION_JSON属性。例如UN*X shell:
$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar

在前面的示例中,你将在Spring环境中得到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

尽管来自JSON的null值将被添加到结果属性源中,但是PropertySourcesPropertyResolver将空属性视为缺少的值。这意味着JSON不能用空值覆盖来自低阶属性源的属性。

配置随机值

RandomValuePropertySource用于注入随机值(例如,注入机密或测试用例中)。它可以生成整数、long、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) CLOS,其中OPEN,CLOSE是任何字符和值,max是整数。如果提供了max,则value是最小值,max是最大值(不包括)。

访问命令行属性

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

应用程序属性文件

SpringApplication从application.properties加载属性并将其添加到Spring Environment中:

  • 当前目录的/config子目录
  • 当前目录
  • 类路径/config包
  • 类路径 root

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

也可以使用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用于确定必须加载哪些文件。它们必须定义为环境属性(通常是操作系统环境变量、系统属性或命令行参数)。

如果spring.config.location包含目录(与文件相反),它们应该以/结尾(并且在运行时,附加从spring.config.name加载之前,包括配置文件特定的文件名)。
spring.config.location指定的文件还是按原样使用,不支持profile-specific的变量,并被任何特定于概要文件的属性覆盖。无论是直接指定还是包含在目录中,配置文件的名称中都必须包含文件扩展名。现成支持的典型扩展是.properties、.yaml和.yml。

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

  • file:./config/
  • file:./config/*/
  • file:./
  • classpath:/config/
  • classpath:/

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

  • file:./custom-config/
  • classpath:custom-config/

或者,当使用spring.config.additional-location,除默认位置外还使用它们。在默认位置之前搜索其他位置。例如,如果类路径的其他位置:classpath:/custom-config/,file:./custom-config/配置后,搜索顺序将变为:

  • file:./custom-config/
  • classpath:custom-config/
  • file:./config/
  • file:./config/*/
  • file:./
  • classpath:/config/
  • classpath:/

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

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

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

Profile配置属性

除了application.properties,还可以用application-{profile}.properties来配置属性。环境中有一组默认的配置文件(默认情况下,[default]),如果未设置活动配置文件,则默认使用这些配置文件。换句话说,若并没有显式激活配置文件,则来自应用程序的application-default.properties属性会被加载。

如果spring.profiles.active被添加到SpringApplication API 配置的属性后,指定的profile文件具有优先加载的权利。

如果你在spring.config.location配置了特定的文件,则不会再考虑这些profile文件。如果你还想应用profile配置文件,则应该在spring.config.location配置目录路径。

属性中的占位符

application.properties 在使用这些值时,将在现有环境中进行筛选,因此你可以引用以前定义的值(例如,从系统属性)。

app.name=MyApp
app.description=${app.name} is a Spring Boot application
加密属性

springboot不提供任何对加密属性值的内置支持,但是它提供了修改Spring环境中包含的值所必需的钩子点。EnvironmentPostProcessor接口允许你在应用程序启动之前操作环境。

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

使用YAML代替properties

YAML是JSON的超集,因此是一种方便的格式,用于指定分层配置数据。

SpringApplication是自动支持YAML 作为properties的替代品的,只要类路径上有Snake YAML库。SnakeYAML 由spring-boot-starter提供。

加载YAML

Spring框架提供了两个方便的类,可以用来加载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

要通过使用springboot的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环境中将YAML公开为PropertySource。这样可以使用@Value注释和占位符语法来访问YAML属性。

多profile YAML配置文件

可以在单个文件中通过spring.profiles属性key指定多个profile,如下所示:

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处于活动状态,则服务器地址属性为127.0.0.1。同样,如果production 和eu-central是激活的,则服务器地址属性为192.168.1.120。如果未启用如果development、production 和eu-central配置文件,则属性的值为192.168.1.100。

spring.profiles可以包含profile文件名(例如production)或profile表达式,profile表达式允许表达更复杂的逻辑,例如production & (eu-central | eu-west)。

如果应用程序上下文启动时没有显式激活,则激活默认配置文件。因此,在下面的YAML中,我们在"default" profile中为spring.security.user.password设置了密码:

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

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

server:
  port: 8000
spring:
  security:
    user:
      password: weak
YAML的缺点

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

类型安全配置属性

使用@Value(${property})注释注入配置属性有时会很麻烦,特别是当你使用多个属性或数据本质上是分层的时候。springboot提供了一种处理属性的替代方法,这种方法允许强类型bean管理和验证应用程序的配置。

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) { ... }

    }
}
构造器绑定

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

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指定默认值,并且将应用相同的转换服务将字符串值强制为缺少属性的目标类型。默认情况下,如果没有属性绑定到Security,AcmeProperties实例将为security设置一个null值。如果希望返回Security 的非空实例,即使没有属性绑定到该实例,也可以使用空的@DefaultValue注释来执行此操作:

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, @DefaultValue Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }
}

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

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

启用@ConfigurationProperties注解

 

使用@ConfigurationProperties注解
第三方配置

 

宽松的绑定

 

合并复杂类型

 

属性转换
@ConfigurationProperties验证

 

@ConfigurationProperties vs @Value

 

posted @ 2020-08-22 10:41  codedot  阅读(323)  评论(0编辑  收藏  举报