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
12import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*
public class MyBean {
"${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
6my.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
2app.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
7environments:
dev:
url: http://dev.example.com
name: Developer Setup
prod:
url: http://another.example.com
name: My Cool App
其将会被转换为如下的属性:1
2
3
4environments.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
4my:
servers:
- dev.example.com
- another.example.com
被转换为属性之后的结果为:1
2my.servers[0]=dev.example.com
my.servers[1]=another.example.com
通过使用 DataBinder 工具绑定变量的时候,在被绑定的类上必须要有一个List的类或者是set,提供一个setter方法,如下代码:1
2
3
4
5
6
7
8
9"my") (prefix=
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
12server:
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"acme") (
public class AcmeProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
1 | acme: |
如果 dev 没有被激活,则只包含一个元素MyPojo ,如果 dev 被激活,其仍然只包含一个元素(name值为my another name,description值为null),其不会添加第二个元素进去,其没有合并
类型安全的配置属性
- 第三方配置
@ConfigurationProperties不仅可以注解在类上,也可以注解在public @Bean方法上,当你需要为不受控的第三方组件绑定属性时,该方法将非常有用。
为了从Environment属性中配置一个bean,你需要使用@ConfigurationProperties注解该bean:1
2
3
4
5"foo") (prefix =
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"person") (prefix=
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"connection") (prefix=
public class ConnectionProperties {
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"connection") (prefix=
public class ConnectionProperties {
private RemoteAddress remoteAddress;
// ... getters and setters
public static class RemoteAddress {
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属性文件的属性。