Spring Boot 配置信息

Spring 外部化配置

Spring Boot 允许将配置外部化(externalize),这样你就能够在不同的环境下使用相同的代码。你可以使用properties文件,YAML文件,环境变量和命令行参数来外部化配置。使用@Value注解,可以直接将属性值注入到beans中,然后通过Spring的Environment抽象或通过@ConfigurationProperties绑定到结构化对象来访问。
Spring Boot设计了一个非常特别的PropertySource顺序,以允许对属性值进行合理的覆盖,属性会以如下的顺序进行设值:

  • home目录下的devtools全局设置属性(~/.spring-boot-devtools.properties,如果devtools激活)。
  • 测试用例上的@TestPropertySource注解。
  • 测试用例上的@SpringBootTest#properties注解。
  • 命令行参数
  • 来自SPRING_APPLICATION_JSON的属性(环境变量或系统属性中内嵌的内联JSON)。
  • ServletConfig初始化参数。
  • ServletContext初始化参数。
  • 来自于java:comp/env的JNDI属性。
  • Java系统属性(System.getProperties())。
  • 操作系统环境变量。
  • RandomValuePropertySource,只包含random.*中的属性。
  • 没有打进jar包的Profile-specific应用属性(application-{profile}.properties和YAML变量)。
  • 打进jar包中的Profile-specific应用属性(application-{profile}.properties和YAML变量)。
  • 没有打进jar包的应用配置(application.properties和YAML变量)。
  • 打进jar包中的应用配置(application.properties和YAML变量)。
  • @Configuration类上的@PropertySource注解。
  • 默认的属性(使用 SpringApplication.setDefaultProperties指定)

以下是一个简单的例子:假定使用name属性开发一个@Component,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*

@Component
public class MyBean {

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

// ...

}

你可以将一个application.properties放到应用的classpath下,为name提供一个合适的默认属性值。当在新的环境中运行时,可以在jar包外提供一个application.properties覆盖name属性。对于一次性的测试,你可以使用特定的命令行开关启动应用(比如,java -jar app.jar –name=”Spring”)。
注 SPRING_APPLICATION_JSON属性可以通过命令行的环境变量设置,例如,在一个UNIX shell中可以这样:

1
$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar

本示例中,如果是Spring Environment,你可以以foo.bar=spam结尾;如果在一个系统变量中,可以提供作为spring.application.json的JSON字符串:

1
$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar

或命令行参数:

1
$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'

或作为一个JNDI变量 java:comp/env/spring.application.json

配置随机变量

在注入随机值(比如,密钥或测试用例)时RandomValuePropertySource很有用,它能产生整数,long 或字符串,比如:

1
2
3
4
5
6
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是最大值(不包含在内)。

访问命令行属性

默认情况下,SpringApplication会将所有命令行配置参数(以’–’开头,比如–server.port=9000)转化成一个property,并将其添加到Spring Environment中。正如以上章节提过的,命令行属性总是优先于其他属性源。
如果不想让其加入到 spring 的环境变量中,可以通过如下代码方式禁止:

1
SpringApplication.setAddCommandLineProperties(false).

应用属性文件

SpringApplication 将会从如下位置加载 application.properties 到 spring 的环境变量中:

  • 当前目录的子目录 /config 下
  • 当前目录
  • 类路径的 /config 下
  • 应用根目录下
    上述也是其优先使用顺序。 如果不想使用 application.properties 作为其配置文件名字,可以通过配置环境变量 spring.config.name 进行设置。也可以通过具体化位置的环境变量 spring.config.location 其指定:如下代码
    1
    2
    3
    4
    #通过第一种方式 
    java -jar myproject.jar --spring.config.name=myproject
    ##方式二
    java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

如果spring.config.location 也包含具体目录,需要使用“/”. spring.config.location 不支持具体额配置环境变量,将会被默认的配置环境变量替换。其配置搜索的顺序与配置的顺序刚好相反。例如:配置顺序为:classpath:/,classpath:/config/,file:./,file:./config/,其搜索顺序如下:

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

如果配置spring.config.additional-location属性,将其追加默认的位置。追加的位置是在默认的位置之前进行搜索。例如追加的目录为 classpath:/custom-config/,file:./custom-config/,其搜索的顺序如下:

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

特定环境配置属性(profile-specific)

作为附件的application.properties文件,profile-specific 属性可以通过使用方便的命名application-{profile}.properties进行定义。如果没有特定的环境配置被激活,其环境变量将采用默认的一系列的配置进行。换句话说,就是没有配置被激活,将采用默认的application.properties文件。 Profile-specific属性默认从标准的application.properties中加载。不管其profile-specific指定的文件是在外部还是内部,其总是被加载。

属性中的占位符

我们可以在在统一属性文件中调用之前定义的变量,代码如下:

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

使用YAML代替属性

YAML是json的一个超级子集,非常方便形成一个层次结构的配置文件。SpringApplication 默认支持YAML的配置。yaml默认被spring-boot-starter支持。

  • 加载YAML
    spring框架默认提供了2中方式加载YAML配置,使用YamlPropertiesFactoryBean作为一个属性加载YAML与使用YamlMapFactoryBean 作为一个MAP加载YAML。
    例如如下的YAML文件
    1
    2
    3
    4
    5
    6
    7
    environments:
    dev:
    url: http://dev.example.com
    name: Developer Setup
    prod:
    url: http://another.example.com
    name: My Cool App

其将会被转换为如下的属性:

1
2
3
4
environments.dev.url=http://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=http://another.example.com
environments.prod.name=My Cool App

yaml的列表可以通过{index}的方式应用,如下列子:

1
2
3
4
my:
servers:
- dev.example.com
- another.example.com

被转换为属性之后的结果为:

1
2
my.servers[0]=dev.example.com
my.servers[1]=another.example.com

通过使用 DataBinder 工具绑定变量的时候,在被绑定的类上必须要有一个List的类或者是set,提供一个setter方法,如下代码:

1
2
3
4
5
6
7
8
9
@ConfigurationProperties(prefix="my")
public class Config {

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

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

  • 在spring的环境中暴露yaml属性
    其主要通过YamlPropertySourceLoader 去暴露。

  • 多配置环境的yaml文档
    可以通过spring.profiles去指定该用哪一个环境变量,如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    address: 192.168.1.100
    ---
    spring:
    profiles: development
    server:
    address: 127.0.0.1
    ---
    spring:
    profiles: production
    server:
    address: 192.168.1.120

上述环境中,如果开发环境被触发:则sever地址为:127.0.0.1,如果为生产地址:则地址为 192.168.1.120,当其两个都不可用的时候,默认为192.168.1.100环境

  • YAML的不足
    其不能通过@PropertySource 进行加载

  • 合并YAML列表
    按照前面所讲,任何yaml都会被转换为属性,当其列表有重复的时候只有其中一个生效,并不会合并。如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ConfigurationProperties("acme")
    public class AcmeProperties {

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

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

    }
1
2
3
4
5
6
7
8
9
10
acme:
list:
- name: my name
description: my description
---
spring:
profiles: dev
acme:
list:
- name: my another name

如果 dev 没有被激活,则只包含一个元素MyPojo ,如果 dev 被激活,其仍然只包含一个元素(name值为my another name,description值为null),其不会添加第二个元素进去,其没有合并

类型安全的配置属性

  • 第三方配置
    @ConfigurationProperties不仅可以注解在类上,也可以注解在public @Bean方法上,当你需要为不受控的第三方组件绑定属性时,该方法将非常有用。
    为了从Environment属性中配置一个bean,你需要使用@ConfigurationProperties注解该bean:
    1
    2
    3
    4
    5
    @ConfigurationProperties(prefix = "foo")
    @Bean
    public FooComponent fooComponent() {
    ...
    }

所有以foo为前缀的属性定义都会被映射到FooComponent上。

  • Relaxed绑定
    Spring Boot将Environment属性绑定到@ConfigurationProperties beans时会使用一些宽松的规则,所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context-path绑定到contextPath),将environment属性转为大写字母(比如,PORT绑定port)。
    例如,给定以下@ConfigurationProperties类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @ConfigurationProperties(prefix="person")
    public class OwnerProperties {

    private String firstName;

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

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

下面的属性名都能使用:

属性 说明
person.firstName 标准驼峰规则
person.first-name 虚线表示,推荐用于.properties和.yml文件中
person.first_name 下划线表示,用于.properties和.yml文件的可选格式
PERSON_FIRST_NAME 大写形式,使用系统环境变量时推荐
  • 属性转换
    将外部应用配置绑定到@ConfigurationProperties beans时,Spring会尝试将属性强制转换为正确的类型。如果需要自定义类型转换器,你可以提供一个ConversionService bean(bean id为conversionService),或自定义属性编辑器(通过CustomEditorConfigurer bean),或自定义Converters(bean定义时需要注解@ConfigurationPropertiesBinding)。

注 由于该bean在应用程序生命周期的早期就需要使用,所以确保限制你的ConversionService使用的依赖。通常,在创建时期任何你需要的依赖可能都没完全初始化。

  • @ConfigurationProperties校验
    Spring Boot将尝试校验外部配置,默认使用JSR-303(如果在classpath路径中),你只需要将JSR-303 javax.validation约束注解添加到@ConfigurationProperties类上:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @ConfigurationProperties(prefix="connection")
    public class ConnectionProperties {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

    }

为了校验内嵌属性的值,你需要使用@Valid注解关联的字段以触发它的校验,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {

@NotNull
@Valid
private RemoteAddress remoteAddress;

// ... getters and setters

public static class RemoteAddress {

@NotEmpty
public String hostname;

// ... getters and setters

}

}

你也可以通过创建一个叫做configurationPropertiesValidator的bean来添加自定义的Spring Validator。@Bean方法需要声明为static,因为配置属性校验器在应用程序生命周期中创建的比较早,将@Bean方法声明为static允许该bean在创建时不需要实例化@Configuration类,从而避免了早期实例化(early instantiation)的所有问题。相关的示例可以看这里

注 spring-boot-actuator模块包含一个暴露所有@ConfigurationProperties beans的端点(endpoint),通过浏览器打开/configprops进行浏览,或使用等效的JMX端点,具体参考Production ready features

@ConfigurationProperties vs. @Value

@Value 是 Spring 容器的一个核心特性,它没有提供跟 type-safe Configuration Properties 相同的特性。下面的表格总结了 @ConfigurationProperties@Value 支持的特性:

特性 @ConfigurationProperties @Value
Relaxed绑定 Yes NO
Meta-data支持 Yes NO
SpEL表达式 NO Yes

如果你为自己的组件定义了一系列的配置keys,我们建议你将它们以@ConfigurationProperties注解的POJO进行分组。由于@Value不支持relaxed绑定,所以如果你使用环境变量提供属性值的话,它就不是很好的选择。最后,尽管@Value可以写SpEL表达式,但这些表达式不会处理来自Application属性文件的属性。

参考资料

  1. spring boot 2.0特性之外部化配置
  2. Spring Boot 参考指南(外部化配置)