Spring|Spring Boot 2.0.0参考手册_中文版_Part IV_24

文章作者:Tyan
博客:noahsnail.com|CSDN|简书
24. 外部配置 Spring Boot允许你进行外部化配置,因此可以将同样的应用代码在不同的环境中运行。你可以使用属性文件,YAML文件,环境变量和命令行参数来进行外部化配置。属性值可以使用@Value注解直接注入到你的beans中,通过Spring的Environment抽象或通过@ConfigurationProperties绑定到结构化对象上来访问。
Spring Boot使用非常特别的PropertySource顺序,这个顺序的设计是为了允许值的合理重写。属性被认为是按照以下顺序:

  1. 根目录下的开发工具全局设置属性(当开发工具激活时为~/.spring-boot-devtools.properties)。
  2. 测试中的@TestPropertySource注解。
  3. 测试中的@SpringBootTest#properties注解特性。
  4. 命令行参数。
  5. SPRING_APPLICATION_JSON中的属性(环境变量或系统属性中的内联JSON嵌入)。
  6. ServletConfig初始化参数。
  7. ServletContext初始化参数。
  8. java:comp/env的JNDI特性。
  9. Java系统属性 (System.getProperties())。
  10. 操作系统环境变量。
  11. RandomValuePropertySource只在random.*中有属性。
  12. jar包之外的指定配置文件的应用属性(application-{profile}.properties和YAML变量)。
  13. jar包之内的指定配置文件的应用属性(application-{profile}.properties和YAML变量)。
  14. jar包之外的应用属性 (application.properties和YAML变量)。
  15. jar包之内的应用属性 (application.properties和YAML变量)。
  16. @Configuration类中的@PropertySource注解 。
  17. 默认属性(通过SpringApplication.setDefaultProperties指定).
为了提供一个具体的例子,假设你开发了一个使用名字属性的@Component
import org.springframework.stereotype.* import org.springframework.beans.factory.annotation.*@Component public class MyBean {@Value("${name}") private String name; // ...}

在你的应用路径中(例如在你的jar内部),你可以使用application.propertiesname提供一个合理的默认属性值。当在新环境运行时,application.properties可以在jar外部提供来重写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。你也可以在系统变量中提供JSON作为spring.application.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
24.1 配置随机值
当注入随机值时,RandomValuePropertySource是很有用的(例如,注入秘密或测试用例)。它可以产生integerslongsuuidsstrings,例如:
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) CLOSEOPEN,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中:
  1. 当前目录的子目录/config
  2. 当前目录
  3. classpath中的/config
  4. classpath的根目录
这个列表是按优先级排序的(在更高位置的属性会重写定义在更低位置的属性)。
你也可以使用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.namespring.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作为PropertiesYamlMapFactoryBean将加载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

【Spring|Spring Boot 2.0.0参考手册_中文版_Part IV_24】YAML列表通过[index]解引用表示为属性的key,例如这个YAML:
my: servers: - dev.bar.com - foo.bar.com

将被转换成这些属性:
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 servers = new ArrayList(); public List 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。如果developmentproduction 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中显式的对它重新设置:
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对象的namedescription属性默认情况下为空。从FooProperties使用一个MyPojo列表:
@ConfigurationProperties("foo") public class FooProperties {private final List list = new ArrayList<>(); public List 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 profile可用,lists仍只包含一个输入(name为“my another name”,description为空)。这个配置将不能添加第二个MyPojolist中,并且它将不能合并这些项。
当在多个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为空)。
24.7 类型安全的配置属性
Boot提供了一种处理属性的可替代方法,允许强类型的beans管理和验证你的应用的配置。例如:
@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将有一个常规的名字:
-
是在@ConfigurationProperties注解中指定的环境关键字的前缀,是bean的完整合格的名字。如果注解没有提供任何前缀,则只用bean的完整合格的名字。
在上面的例子中bean名字是connection-com.example.ConnectionProperties,假设ConnectionPropertiescom.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.ymlconnection: 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 setterspublic 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中写表达式,但这种表达式不能从应用属性文件中处理。

    推荐阅读