文章作者:Tyan 博客:noahsnail.com | CSDN | 简书
24. 外部配置
Spring Boot允许你进行外部化配置,因此可以将同样的应用代码在不同的环境中运行。你可以使用属性文件,YAML文件,环境变量和命令行参数来进行外部化配置。属性值可以使用@Value
注解直接注入到你的beans中,通过Spring的Environment
抽象或通过@ConfigurationProperties
绑定到结构化对象上来访问。
Spring Boot使用非常特别的PropertySource
顺序,这个顺序的设计是为了允许值的合理重写。属性被认为是按照以下顺序:
- 根目录下的开发工具全局设置属性(当开发工具激活时为
~/.spring-boot-devtools.properties
)。 - 测试中的
@TestPropertySource
注解。 - 测试中的
@SpringBootTest#properties
注解特性。 - 命令行参数。
SPRING_APPLICATION_JSON
中的属性(环境变量或系统属性中的内联JSON嵌入)。ServletConfig
初始化参数。ServletContext
初始化参数。java:comp/env
的JNDI特性。- Java系统属性 (
System.getProperties()
)。 - 操作系统环境变量。
RandomValuePropertySource
只在random.*
中有属性。- jar包之外的指定配置文件的应用属性(
application-{profile}.properties
和YAML变量)。 - jar包之内的指定配置文件的应用属性(
application-{profile}.properties
和YAML变量)。 - jar包之外的应用属性 (
application.properties
和YAML变量)。 - jar包之内的应用属性 (
application.properties
和YAML变量)。 @Configuration
类中的@PropertySource
注解 。- 默认属性(通过
SpringApplication.setDefaultProperties
指定).
为了提供一个具体的例子,假设你开发了一个使用名字属性的@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"
)。
代码语言:javascript复制
SPRING_APPLICATION_JSON
可以在命令行中通过环境变量提供。例如在UNIX shell中:
$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar
代码语言:javascript复制在这个例子中,Spring的
Environment
中会有foo.bar=spam
。你也可以在系统变量中提供JSON作为spring.application.json
。
$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar
代码语言:javascript复制或命令行参数:
$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'
或JNDI变量
java:comp/env/spring.application.json
24.1 配置随机值
当注入随机值时,RandomValuePropertySource
是很有用的(例如,注入秘密或测试用例)。它可以产生integers
,longs
,uuids
或strings
,例如:
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
是最大值(不包含)。
24.2 访问命令行属性
默认情况下,SpringApplication
会将任何命令行参数(以--
开头,例如--server.port=9000
)转换成property
并将其添加到Spring的Environment
中。正如前面提到的那样,命令行属性总是优先于其它的配置源。
如果你想命令行属性添加到Environment
中,你可以使用SpringApplication.setAddCommandLineProperties(false)
禁用它。
24.3 应用属性文件
SpringApplication
会从以下位置的application.properties
文件中加载属性并将它们添加到Spring的Environment
中:
- 当前目录的子目录
/config
- 当前目录
- classpath中的
/config
包 - classpath的根目录
这个列表是按优先级排序的(在更高位置的属性会重写定义在更低位置的属性)。
你也可以使用YAML(
.yml
)文件来代替.properties
文件。
如果你不喜欢用application.properties
作为配置文件的名字,你可以通过指定spring.config.name
环境属性来来改变配置文件的名字。你也可以使用spring.config.location
环境属性来引用一个显式的位置(目录位置或文件路径以逗号分隔)。
$ java -jar myproject.jar --spring.config.name=myproject
或
代码语言:javascript复制$ 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
中指定的文件按原样使用,不支持指定配置文件变量,将会被任何指定配置文件的属性覆盖。
默认搜索路径一直用classpath:,classpath:/config,file:,file:config/
,不管spring.config.location
中的值。搜索路径按从低到高排序(file:config/
最高)。如果你指定了自己的位置,它们优先于所有的默认位置并使用同样的从低到高的顺序。这样你可以在application.properties
中为你的应用设置默认值(或你可以选择spring.config.name
的其它生成文件基本名),在运行时用其它的文件覆盖它,同时保留默认值。
如果你使用环境变量而不是系统属性,大多数操作系统不允许句号分隔的关键字,但你可以用下划线代替(例如,
SPRING_CONFIG_NAME
代替spring.config.name
)。 如果你在容器中运行,那么JNDI属性(在java:comp/env
中)或servlet上下文初始化参数也可以用来代替环境变量或系统属性。
24.4 特定的profile属性
除了application.properties
文件之外,特定的profile属性也可以使用命名规范application-{profile}.properties
来定义。Environment
有一系列默认配置文件(默认为[default]
),如果没有设置激活的配置文件,会使用默认的配置文件(例如,如果没有激活显式的配置文件,则会加载application-default.properties
中的属性)。
特定的profile属性会从相同位置加载application.properties
,特定的profile文件总是覆盖非特定的配置文件,无论特定profile文件在你打包的jar内部还是外部。
如果指定了几个配置文件,将会应用最后一个。例如,spring.profiles.active
属性指定的配置文件在那些配置的文件之后通过SpringApplication
API添加,因此优先级更高。
如果你在
spring.config.location
中指定了任何文件,那些文件的特定profile版本将不会被考虑。如果你也想使用特定的profile属性,在spring.config.location
中使用目录。
24.5 属性中的占位符
当使用application.properties
中的值时,会通过现有的Environment
进行过滤,因此你可以参考前面定义的值(例如从系统属性中)。
app.name=MyApp
app.description=${app.name} is a Spring Boot application
你也可以使用这个技术来创建现有的Spring Boot属性的
short
版本。怎样使用的更多细节请看70.4小节,“Use ‘short’ command line arguments”。
24.6 使用YAML代替Properties
YAML是JSON的超集,它可以用一种非常方便的形式来指定分层配置数据。当你的类路径有SnakeYAML库时,SpringApplication
类自动支持YAML作为properties的一个替代品。
如果你使用‘Starters’,SnakeYAML将由
spring-boot-starter
自动提供。
24.6.1 加载YAML
Spring框架提供了两个类用来方便的加载YAML文档。YamlPropertiesFactoryBean
将加载YAML作为Properties
,YamlMapFactoryBean
将加载YAML作为Map
。
例如,下面的YAML文档:
代码语言:javascript复制environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name: My Cool App
将被转换成这些属性:
代码语言:javascript复制environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App
YAML列表通过[index]
解引用表示为属性的key,例如这个YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
将被转换成这些属性:
代码语言:javascript复制my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
为了像使用Spring的DataBinder
一样(@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;
}
}
24.6.2 在Spring Environment中公开YAML为属性
YamlPropertySourceLoader
类可以在Spring的Environment
中将YAML作为PropertySource
。这允许你使用熟悉的@Value
注解和占位符语法来访问YAML属性。
24.6.3 多profile的YAML文档
你可以在单个文件中指定多个特定profile的YAML文档,当应用文档时,通过spring.profiles
关键字来表明使用哪个文档。例如:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production
server:
address: 192.168.1.120
在上面的例子中,如果development
profile被激活,server.address
的值为127.0.0.1
。如果development
和production
profile不可用,server.address
的值为192.168.1.100
。
当应用上下文启动时,如果没有显式的激活profile,则激活默认的profile。因此在这个YAML文件中我们仅在”default” profile中设置了security.user.password
。
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak
在这个例子中,密码总是设置的,因为它没有添加到如何profile中,必要时我们必须在其它的profile中显式的对它重新设置:
代码语言:javascript复制server:
port: 8000
security:
user:
password: weak
Spring profiles被设计为使用”spring.profiles”元素可以选择性的用!
字符进行否定。如果否定的和非否定的profile指向一个单独的文档,必须至少匹配一个非否定的profile,可能没有否定的profile进行匹配。
24.6.4 YAML缺点
YAML文件不能通过@PropertySource
注解进行加载。因此在这种情况下如果你需要加载值,你需要使用属性文件。
24.6.5 合并YAML列表
正如我们上面看到的,任何YAML内容最终都要转换成属性。当通过profile重写“list“属性时,这个过程可能有违直觉。
例如,假设MyPojo
对象的name
和description
属性默认情况下为空。从FooProperties
使用一个MyPojo
列表:
@ConfigurationProperties("foo")
public class FooProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
考虑下面的配置:
代码语言:javascript复制foo:
list:
- name: my name
description: my description
---
spring:
profiles: dev
foo:
list:
- name: my another name
如果dev
profile没有激活,FooProperties.list
将包含一个上面定义的MyPojo
输入。然而如果dev
profile可用,lists
仍只包含一个输入(name为“my another name”,description为空)。这个配置将不能添加第二个MyPojo
到list
中,并且它将不能合并这些项。
当在多个profiles中指定一个集合时,将会使用最高优先级的那个集合(唯一的哪个):
代码语言:javascript复制foo:
list:
- name: my name
description: my description
- name: another name
description: another description
---
spring:
profiles: dev
foo:
list:
- name: my another name
在上面的例子中,假设dev
profile被激活,FooProperties.list
将包含一个MyPojo
输入(name为“my another name”,description为空)。
24.7 类型安全的配置属性
Boot提供了一种处理属性的可替代方法,允许强类型的beans管理和验证你的应用的配置。例如:
代码语言:javascript复制@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
private String username;
private InetAddress remoteAddress;
// ... getters and setters
}
建议添加getters和setters,绑定是通过标准的Java Beans属性描述符,像在Spring MVC中一样。对于不可变类型或那些从
String
中可直接强制转换的类型,它们是强制性的。只要它们被初始化,maps,集合或数组需要getter方法,但不需要setter方法因为通过绑定起它们可以直接变化。如果有setter,可以创建Maps,集合和数组。Maps和集合可以通过getter扩展,数组扩展需要setter。如果它们有默认的构造函数,或构造函数接收可以从String
类型强制转换的值,嵌入的POJO属性也可以创建(因此setter不是强制性的)。一些人使用Lombok项目来自动添加getter和setter。请看
@Value
和@ConfigurationProperties
之间的不同。
你也需要在@EnableConfigurationProperties
注解中列出要注册的属性类:
@Configuration
@EnableConfigurationProperties(ConnectionProperties.class)
public class MyConfiguration {
}
当
@ConfigurationProperties
以那种方式注册时,这个bean将有一个常规的名字:<prefix>-<fqn>
,<prefix>
是在@ConfigurationProperties
注解中指定的环境关键字的前缀,<fqn>
是bean的完整合格的名字。如果注解没有提供任何前缀,则只用bean的完整合格的名字。 在上面的例子中bean名字是connection-com.example.ConnectionProperties
,假设ConnectionProperties
在com.example
包中。
即使上面的配置会为ConnectionProperties
创建一个正规的bean,我们建议@ConfigurationProperties
只处理环境,特别是不从上下文中注入其它的beans。已经说过,为了任何带有@ConfigurationProperties
注解的bean可以根据Environment
属性进行配置,@EnableConfigurationProperties
注解也自动应用到你的工程中。确保ConnectionProperties
已经是一个bean,你可以简写上面的MyConfiguration
:
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
// ... getters and setters
}
这种风格的配置在SpringApplication
的外部化YAML配置中工作的尤其好:
# application.yml
connection:
username: admin
remoteAddress: 192.168.1.1
# additional configuration as required
为了同@ConfigurationProperties
beans一起工作,你可以像任何其它bean一样以相同的方式注入它们:
@Service
public class MyService {
private final ConnectionProperties connection;
@Autowired
public MyService(ConnectionProperties connection) {
this.connection = connection;
}
//...
@PostConstruct
public void openConnection() {
Server server = new Server();
this.connection.configure(server);
}
}
使用
@ConfigurationProperties
也允许你生成IDEs可以使用的元数据文件。更多细节请看附录B,配置元数据附录。
24.7.1 第三方配置
也可以使用@ConfigurationProperties
来注解一个类,你也可以在公有的@Bean
方法上使用它。当你想绑定属性到你控制之外的第三方组件上时尤其有用。
@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
...
}
任何定义的带有foo
前缀的属性都将以类似于上面的ConnectionProperties
例子中的方式映射到FooComponent
bean中。
24.7.2 松散绑定
Spring Boot使用一些松散的规则将Environment
属性绑定到@ConfigurationProperties
beans上,因此不需要在Environment
属性名和bean属性名之间进行确切的匹配。常见的有用例子包括破折号分隔(例如,context-path绑定到contextPath),大小写(例如PORT
绑定到port
,)环境属性。
例如,给定下面的@ConfigurationProperties
类:
@ConfigurationProperties(prefix="person")
public class OwnerProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
下面的属性名都可以使用:
表24.1. 松散绑定
Property | Note |
---|---|
person.firstName | 标准的驼峰写法 |
person.first-name | 破折号注解,建议在.properties和.yml文件中使用 |
person.first_name | 下划线注解,.properties和.yml文件中的可替代写法 |
PERSON_FIRST_NAME | 大写形式。当使用系统变量时推荐 |
24.7.3 属性转换
当Spring绑定属性到@ConfigurationProperties
beans时,它将试图将外部的应用属性强制转换成正确的类型。如果你需要定制类型转换你可以提供一个ConversionService
bean(bean id为conversionService
),或定制属性编辑器(通过CustomEditorConfigurer
bean),或定制Converters
(带有@ConfigurationPropertiesBinding
注解的bean定义)。
bean要求在应用生命周期中的早期,要确保限制
ConversionService
使用的依赖。通常,任何你要求的依赖可能在创建时不是完整初始化的。如果你定制的ConversionService
不要求配置关键字强制转换,你可能想重新命名你定制的ConversionService
,并且只依赖满足@ConfigurationPropertiesBindings
的定制转换器。
24.7.4 @ConfigurationProperties验证
Spring Boot会试图验证外部化配置,默认使用JSR-303(如果它在classpath中)。你可以简单的添加JSR-303 javax.validation
约束注解到你的@ConfigurationProperties
类中:
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
为了验证嵌入的属性值,你必须注解相关的字段作为@Valid
来触发它的校验。例如,在上面的ConnectionProperties
例子上构建:
@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
方法应该声明静态的。配置属性验证器在应用生命周期的早期创建,声明@Bean
方法为静态方法,允许不必实例化@Configuration
类就创建bean。这避免了任何早期实例化可能引起的问题。这儿有一个属性验证的例子因此你可以看一下怎样设置它
spring-boot-actuator
模块包含一个端点,这个端点将公开所有的@ConfigurationProperties
beans。简单的将你的web浏览器指向/configprops
或用等价的JMX端点。更多细节请看产品级功能
24.7.5 @ConfigurationProperties和@Value
@Value
是一种核心的容器功能,它不能作为类型安全配置属性提供同样的功能。下面的表中总结了@ConfigurationProperties
和@Value
支持的功能:
功能 | @ConfigurationProperties | @Value |
---|---|---|
松散绑定 | Yes | No |
元数据支持 | Yes | No |
SpEL评估 | No | Yes |
如果你为自己的组件定义了一些配置关键字,我们建议你将它们分组到带有@ConfigurationProperties
注解的POJO中。也请注意@Value
不支持松散绑定,如果你需要用环境变量提供值,它不是一个好的选择。
最后,虽然你可以在@Value
中写表达式,但这种表达式不能从应用属性文件中处理。