Spring Boot允许将配置外部化(externalize),这样你就能够在不同的环境下使用相同的代码。你可以使用properties文件,YAML文件,环境变量和命令行参数来外部化配置。使用@Value注解,可以直接将属性值注入到beans中,然后通过Spring的Environment
抽象或通过@ConfigurationProperties
绑定到结构化对象来访问。
Spring Boot设计了一个非常特别的PropertySource
顺序,以允许对属性值进行合理的覆盖,属性会以如下的顺序进行设值:
~/.spring-boot-devtools.properties
,如果devtools激活)。SPRING_APPLICATION_JSON
的属性(环境变量或系统属性中内嵌的内联JSON)。ServletConfig
初始化参数。ServletContext
初始化参数。java:comp/env
的JNDI属性。random.*
中的属性。application-{profile}.properties
和YAML变量)。application-{profile}.properties
和YAML变量)。application.properties
和YAML变量)。application.properties
和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;
// ...
}
你可以将一个application.properties
放到应用的classpath下,为name
提供一个合适的默认属性值。当在新的环境中运行时,可以在jar包外提供一个application.properties
覆盖name
属性。对于一次性的测试,你可以使用特定的命令行开关启动应用(比如,java -jar app.jar --name="Spring"
)。
注 SPRING_APPLICATION_JSON
属性可以通过命令行的环境变量设置,例如,在一个UNIX shell中可以这样:
$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar
本示例中,如果是Spring Environment
,你可以以foo.bar=spam
结尾;如果在一个系统变量中,可以提供作为spring.application.json
的JSON字符串:
$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar
或命令行参数:
$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'
或作为一个JNDI变量java:comp/env/spring.application.json
。
在注入随机值(比如,密钥或测试用例)时RandomValuePropertySource
很有用,它能产生整数,longs或字符串,比如:
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
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
中。正如以上章节提过的,命令行属性总是优先于其他属性源。
如果不想将命令行属性添加到Environment
,你可以使用SpringApplication.setAddCommandLineProperties(false)
来禁用它们。
SpringApplication
将从以下位置加载application.properties
文件,并把它们添加到Spring Environment
中:
/config
子目录。/config
包。该列表是按优先级排序的(列表中位置高的路径下定义的属性将覆盖位置低的)。
注 你可以使用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
决定加载哪个文件,所以它们必须定义为environment属性(通常为OS env,系统属性或命令行参数)。
如果spring.config.location
包含目录(相对于文件),那它们应该以/
结尾(在被加载前,spring.config.name
关联的名称将被追加到后面,包括profile-specific的文件名)。spring.config.location
下定义的文件使用方法跟往常一样,没有profile-specific变量支持的属性,将被profile-specific的属性覆盖。
不管spring.config.location
配置什么值,默认总会按照classpath:,classpath:/config,file:,file:config/
的顺序进行搜索,优先级由低到高,也就是file:config/
获胜。如果你指定自己的位置,它们会优先于所有的默认位置(locations),并使用相同的由低到高的优先级顺序。那样,你就可以在application.properties
为应用设置默认值,然后在运行的时候使用不同的文件覆盖它,同时保留默认配置。
注 如果使用环境变量而不是系统属性,需要注意多数操作系统的key名称不允许以句号分割(period-separated),但你可以使用下划线(underscores)代替(比如,使用SPRING_CONFIG_NAME
代替spring.config.name
)。
注 如果应用运行在容器中,那么JNDI属性(java:comp/env)或servlet上下文初始化参数可以用来代替环境变量或系统属性,当然也可以使用环境变量或系统属性。
除了application.properties
文件,profile-specific属性也能通过命名惯例application-{profile}.properties
定义。Environment
(Spring的环境抽象接口)有个默认profiles集合(默认情况为[default]
),在没有设置激活的profiles时会被使用(例如,如果没有明确指定激活的profiles,application-default.properties
中的属性会被加载)。
Profile-specific属性加载路径和标准的application.properties
相同,并且profile-specific文件总是会覆盖non-specific文件,不管profile-specific文件是否被打包到jar中。
如果定义多个profiles,最后一个将获胜。例如,spring.profiles.active
定义的profiles被添加到通过SpringApplication
API定义的profiles后面,因此优先级更高。
注 如果你已经在spring.config.location
下定义所有文件(非目录),那些profile-specific的文件将不被考虑。如果想使用profile-specific属性,那就在spring.config.location
下使用目录。
当使用application.properties
定义的属性时,Spring会先通过已经存在的Environment
查找该属性,所以你可以引用事先定义的值(比如,系统属性):
app.name=MyApp
app.description=${app.name} is a Spring Boot application
注 你也可以使用该技巧为存在的Spring Boot属性创建'短'变量,具体参考Section 69.4, “Use ‘short’ command line arguments”。
YAML是JSON的一个超集,也是一种方便的定义层次配置数据的格式。只要你将SnakeYAML 库放到classpath下,SpringApplication
就会自动支持YAML,以作为properties的替换。
注 如果你使用'Starters',添加spring-boot-starter
依赖会自动加载SnakeYAML。
Spring框架提供两个便利的类用于加载YAML文档,YamlPropertiesFactoryBean
会将YAML加载为Properties
,YamlMapFactoryBean
会将YAML加载为Map
。
例如,下面的YAML文档:
environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name: My Cool App
会被转化到这些属性:
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]
间接引用作为属性keys的形式,例如下面的YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
将会转化到这些属性:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
使用Spring DataBinder
工具集绑定这些属性(这是@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;
}
}
YamlPropertySourceLoader
类能够将YAML作为PropertySource
导出到Sprig Environment
,这允许你使用常用的@Value
注解配合占位符语法访问YAML属性。
你可以在单个文件中定义多个特定配置(profile-specific)的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
profiles没有启用,则该属性的值将是192.168.1.100
。
在应用上下文启动时,如果没有明确指定激活的profiles,则默认的profiles将生效。所以,在下面的文档中我们为security.user.password
设置了一个值,该值只在"default" profile中有效:
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak
然而,在这个示例中,由于没有关联任何profile,密码总是会设置,并且如果有必要的话可以在其他profiles中显式重置:
server:
port: 8000
security:
user:
password: weak
通过!
可以对spring.profiles
指定的profiles进行取反(negated,跟java中的!
作用一样),如果negated和non-negated profiles都指定一个单一文件,至少需要匹配一个non-negated profile,可能不会匹配任何negated profiles。
YAML文件不能通过@PropertySource
注解加载,如果需要使用该方式,那就必须使用properties文件。
正如上面看到的,所有YAML最终都转换为properties,在通过一个profile覆盖"list"属性时这个过程可能不够直观(counter intuitive)。例如,假设有一个MyPojo
对象,默认它的name
和description
属性都为null
,下面我们将从FooProperties
暴露一个MyPojo
对象列表(list):
@ConfigurationProperties("foo")
public class FooProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
考虑如下配置:
foo:
list:
- name: my name
description: my description
---
spring:
profiles: dev
foo:
list:
- name: my another name
如果dev
profile没有激活,FooProperties.list
将包括一个如上述定义的MyPojo
实体,即使dev
生效,该list
仍旧只包含一个实体(name
值为my another name
,description
值为null
)。此配置不会向该列表添加第二个MyPojo
实例,也不会对该项进行合并。
当一个集合定义在多个profiles时,只使用优先级最高的:
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
值为null
)。
使用@Value("${property}")
注解注入配置属性有时会比较麻烦(cumbersome),特别是需要使用多个properties,或数据本身有层次结构。Spring Boot提供一种使用配置的替代方法,这种方法允许强类型的beans以管理和校验应用的配置,例如:
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
private String username;
private InetAddress remoteAddress;
// ... getters and setters
}
注 添加setter和getter是相当正确的,因为绑定是通过标准的Java Beans属性描述符进行的,跟Spring MVC一样,对于不可变类型或从String
强制转换的也一样。只要它们初始化了,maps,collections,arrays只需要getter,setter不是必须的,因为绑定者(binder)能够改变它们。如果有setter,maps,collections,arrays就能够被创建。Maps和collections可以仅通过getter进行扩展,而arrays需要setter。嵌套的POJO属性只能通过默认的构造器,或接收一个单一的能够转换为string的值的构造器。有些人使用Project Lombok自动添加getters和setters。
注 查看@Value和@ConfigurationProperties之间的区别。
你需要在@EnableConfigurationProperties
注解中列出要注册的属性类:
@Configuration
@EnableConfigurationProperties(ConnectionProperties.class)
public class MyConfiguration {
}
注 当@ConfigurationProperties
bean以这种方式注册时,该bean将有个约定的名称:<prefix>-<fqn>
,<prefix>
是@ConfigurationProperties
注解中定义的environment key前缀,<fqn>
是bean的全限定名。如果注解中没有提供任何前缀,那就只使用bean的全限定名。上述示例中的bean名称将是connection-com.example.ConnectionProperties
,假定ConnectionProperties
位于com.example
包下。
尽管上述配置为ConnectionProperties
创建了一个常规的bean,不过我们建议@ConfigurationProperties
只用来处理environment(只用于注入配置,系统环境之类的),特别是不要注入上下文中的其他beans。话虽如此,@EnableConfigurationProperties
注解会自动应用到你的项目,任何存在的,注解@ConfigurationProperties
的bean将会从Environment
属性中得到配置。只要确定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使用的元数据文件,具体参考Appendix B, Configuration meta-data。
此章节翻译的不好,后续整理*
@ConfigurationProperties
不仅可以注解在类上,也可以注解在public @Bean
方法上,当你需要为不受控的第三方组件绑定属性时,该方法将非常有用。
为了从Environment
属性中配置一个bean,你需要使用@ConfigurationProperties
注解该bean:
@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
...
}
和上面ConnectionSettings
的示例方式相同,所有以foo
为前缀的属性定义都会被映射到FooComponent
上。
Spring Boot将Environment
属性绑定到@ConfigurationProperties
beans时会使用一些宽松的规则,所以Environment
属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context-path
绑定到contextPath
),将environment属性转为大写字母(比如,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;
}
}
下面的属性名都能使用:
属性 | 说明 |
---|---|
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
使用的依赖。通常,在创建时期任何你需要的依赖可能都没完全初始化。
Spring Boot将尝试校验外部配置,默认使用JSR-303(如果在classpath路径中),你只需要将JSR-303 javax.validation
约束注解添加到@ConfigurationProperties
类上:
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
为了校验内嵌属性的值,你需要使用@Valid
注解关联的字段以触发它的校验,例如:
@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。
@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属性文件的属性。