Spring Boot 2系列(五十三):Spring Boot 配置外部化
Spring Boot 允许将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。可以使用 properties
文件、YAML
文件、环境变量和命令行参数将配置外部化。
属性值可以通过@Value
注解直接注入到 Bean 的属性,通过 Spring 的 Environment
抽象访问,或者通过@ConfigurationProperties
绑定到结构化对象。
之前写过 Spring Boot 配置的相关文件,但不完整。本篇基于官方文档(Externalized Configuration)行详细描述。
Spring Boot 2实践系列(十四):配置文件profile属性和部署jar包,Spring Boot 2实践系列(二十): 配置文件加载及参数绑定
属性顺序
顺序
Spring Boot 使用一个非常特殊的 PropertySource
顺序,该顺序旨在允许合理地覆盖值。 属性按以下顺序考虑属性:
在
home
(主)目录上的 Devtools global settings properties(~/.spring-boot-devtools.properies
当 devtools 处于活动状态时)。测试的
@TestPropertySource
注解。测试的
properties
属性。在@SpringBootTest
和测试注解(@Test
)中可用的属性,用于测试应用程序的特定部分。命令行参数(Command line arguments)。
来自
SPRING_APPLICATION_JSON
的属性(嵌入在环境变量或系统属性中的内联 JSON)。ServletConfig
初始化参数。ServletContext
初始化参数。来自
java:comp/env
的 JNDI 属性。Java 系统属性(
System.getProperties()
)。操作系统环境变量。
RandomValuePropertySource
中以random.*
为前缀的 属性。jar 包外部的特定的
profile
配置(application-{profile}.properties
和YAML
文件)。jar 包内部的特定的
profile
配置(application-{profile}.properties
和YAML
文件)。jar 包外部的 Application properties 文件(
application.properties
和YAML
文件)。jar 包内部的 Application properties 文件(
application.properties
和YAML
文件)。@Configuration
注解作用类上的@PropertySource
注解。注意,在刷新应用程序上下文之前,不会将此类属性源添加到环境(
Environment
)中。 此时置某些属性(如logging.*
和spring.main.*
)为时已晚,这些属性在刷新开始之前就已被读取。默认的属性(由
SpringApplication.setDefaultProperties
设置指定)。
示例
一个具体的示例,假设开发了一个定义了 name
属性的 @Component
,如下示例:
1 | import org.springframework.stereotype.*; |
在的应用程序类路径上(例如,在 jar 内部),可以有一个 application.properties
文件,该配置文件为 name
提供合理的默认属性值。
当在一个新的环境运行时,可以在外部提供一个 application.properties
文件,覆盖 name
属性的值。对于一次性测试,可以使用特定的命令行开关启动(例如,java -jar app.jar --name ="Spring"
)。
备注:
SPRING_APPLICATION_JSON
属性文件可以在带有环境变量的命令行上提供。例如,可以在UN*X shell
中使用以下行:
1 | SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar |
上面的示例,最终在 Spring Environment
中使用 acme.name=test
。还可以在系统属性中将 JSON 作为spring.application.json
提供,如下例所示:
1 | java -Dspring.application.json='{"name":"test"}' -jar myapp.jar |
也可以通过使用命令行参数来提供 JSON。如下示例:
1 | java -jar myapp.jar --spring.application.json='{"name":"test"}' |
也可以将 JSON 作为 JNDI 的变量来提供,如:java:comp/env/spring.application.json
。
配置随机数
RandomValuePropertySource
对于注入随机值(例如,在机密或测试用例中)非常有用。它可以生成整数、long、uuid或字符串,如下例所示:
1 | ${random.value} = |
random.int*
语法是 OPEN value (, max) CLOSE
,其中 OPEN,CLOSE
是任何字符,value, max
是整数。 如果提供了max
,则 value
是最小值,而 max
是最大值(不包括)。
访问命令行属性
默认情况下,SpringApplication 会转换任何命令行参数(即,以 --
开头的参数,如,--server.port=9000
)为属性,并将它将加入到 Spring Environment
。如前所述,命令行属性始终优先于其他属性源。
如果不想将命令行属性加入到 Environment
,可以使用下面设置禁用它们:
1 | SpringApplication.setAddCommandLineProperties(false) |
示例:
1 | public static void main(String[] args) { |
应用属性文件
加载位置
SpringApplication 从以下位置查找 application.properties
文件加载属性,并将它们加入到 Spring Environment
中。
- 当前目录的
/config
子目录。 - 当前目录。
- 类路径中的
/config
目录。 - 根类路径。
注意:该列表按照优先级的顺序排列(在列表中较高的位置定义的属性将会覆盖在较低位置定义的属性)。
例如,当前目录的 /config
与根类路径下都有一份相同的配置文件,则根类路径下的配置文件会覆盖当前目录 /config
下的配置文件。
注:可以使用 YAML
(.yml
)文件替代 .properties
文件。
指定配置文件
如果不喜欢 application.properties
作为配置文件名,可以通过 spring.config.name
环境属性切换到另一个文件名。还可以通过使用spring.config.location
环境属性来引用显式位置(这是目录位置或文件路径的逗号分隔列表)。 以下示例显示如何指定其他文件名:
1 | java -jar myproject.jar --spring.config.name=myproject |
下面的示例演示如何指定两个位置:
1 | java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties |
备注:spring.config.name
和 spring.config.location
会早早地使用,用于确定必须加载的文件。所以必须将它们定义为环境属性(通常是 OS 环境变量,系统属性或命令行参数)
如果 spring.config.location
包含目录,应以 /
结束(并且,在运行时,被附加到spring.config.name
指定的名称后加载,包括特定的 profile
文件名)。
在spring.config.location
中指定的文件按原样使用,不支持特定于配置文件的变量({profile}
),并且会被任何特定于配置文件(profile)的属性覆盖。
搜索顺序
默认路径
默认情况下,配置文件的路径有 classpath:/, classpath:/config/, file:../, file:./config/
,而搜索配置文件的顺序是相反的。实际的搜索顺序如下:
file:./config/
file:./
classpath:/config/
classpath:/
location
当通过 spring.config.location
自定义了配置文件的路径,则会替换认的路径。示例如下:
1 | classpath:/custom-config/,file:./custom-config/ = |
上面示例的搜索顺序是:
file:./custom-config/
classpath:custom-config/
additional-location
另外,当使用spring.config.additional-location
设置自定义的配置路径时,自定义的的配置路径和默认的路径都会被使用。 自定义的配置路径的搜索会先于默认的配置路径。示例如下:
1 | classpath:/custom-config/,file:./custom-config/ = |
上面示例的搜索顺序是:
file:./custom-config/
file:custom-config/
file:./config/
file:./
classpath:/config/
classpath:/
此搜索顺序允许在一个配置文件中指定默认值,然后在另一个配置文件中有选择地覆盖这些值。
可以在默认位置之一的application.properties
(或使用spring.config.name
选择的任何其他基本名称)中为应用程序提供默认值。然后,可以在运行时使用自定义位置之一中的的其他配置文件覆盖这些默认值。
注意:
如果使用的是环境变量,而不是系统属性,大多数操作系统不允许使用点号分隔(.
)的键名,但可以使用下划线(_
)代替(示例:SPRING_CONFIG_NAME
代替 spring.config.name
)。
如果应用程序运行在容器中,则可以使用 JNDI 属性(在 java:comp/env
中)或 Servlet 上下文初始化参数代替环境变量或系统属性。
Profile配置文件
除了 application.properties
文件外,还可以使用以下命名约定来定义特定于配置文件的属性:application-{profile}.properties
。
Environment
有一组默认配置文件(默认情况下,[default]
),如果未设置活动配置文件,则使用默认的。即,如果没有显式激活配置文件(profiles
),则从application-default.properties
中加载属性。
加载profiles
配置文件的位置与标准application.properties
的位置相同,无论 profiles 配置文件是否在打包的 jar 内部或外部,profiles配置文件的属性始终覆盖非 profiles 配置文件中的属性。
如果指定了多个 profiles 配置文件,则采用后赢策略。 例如,在通过 SpringApplication API
设置的配置文件之后,添加了spring.profiles.active
属性指定的 profiles
配置文件,后者具有优先权。
注意:如果在spring.config.location
中指定了任何配置文件,则不考虑使用 profiles
配置文件。如果还想使用 ,则将 profiles
配置文件存放在spring.config.location
目录中。
属性中的占位符
使用 application.properties
中的值时,它们会通过现有 Environment
进行过滤,因此可以参考以前面义的值(例如,从 System 属性中)
1 | # a Spring Boot application |
备注:还可以使用此技术来创建现有 Spring Boot 属性的 简短 变体。 有关详细信息,参考Section 77.4, “Use ‘Short’ Command Line Arguments”。
加密属性
Spring Boot 没有提供对属性值进行加密的任何内置支持,但是,它提供了修改 Spring Environment
中包含的值所需要的勾子。 EnvironmentPostProcessor
接口许在应用程序启动之前操做 Environment
。有关详细信息,参考 Section 76.3, “Customize the Environment or ApplicationContext Before It Starts”
如果正在寻找一种安全的方式来存储 凭证 或 密码,Spring Cloud Vault项目为在 HashiCorp Vault 中存储外部化配置提供了支持。
使用YAML文件
YAML 是 JSON 的超集,因此,其是一种用于具有层次结构来配置数据的便捷格式。 只要在类路径上具有 SnakeYAML库,SpringApplication
类就会自动支持 YAML 作为 properties
的替代方法。
备注:如果使用的是 Starters,SnakeYAML 由spring-boot-starter
自动提供。
加载 YAML
Spring Framework 提供了两个方便的类,可用于加载 YAML 文件。YamlPropertiesFactoryBean
加载 YAML 文件做为 Properties
,YamlMapFactoryBean
加载 YAML 文件作为 Map
。
示例,如下 YAML 文件:
1 | environments: |
上面示例转为 Properties 配置如下:
1 | https://dev.example.com = |
YAML 列表用 [index]
解引用器表示为属性键。 例如,考虑以下 YAML:
1 | my: |
上面示例转为 Properties 配置如下:
1 | dev.example.com = |
要使用 Spring Boot 的 Binder
工具(@ConfigurationProperties
所做的)绑定到类似的属性,需要在类型为java.util.List
(或Set
)的目标 bean
中拥有一个属性,或者需要提供一个setter 或使用一个可变值对其进行初始化。 例如,以下示例绑定到前面显示的属性:
1 |
|
YamlPropertySourceLoader
类可用于在 Spring Environment
中将 YAML 公开为 PropertySource
。 这样做可以让使用@Value
注解和占位符语法来访问 YAML 属性。
多profile YAML文件
可以使用spring.profiles
键在一个文件中指定多个 Profiles
YAML 配置文件,以指示何时应用该文档,如以下示例所示:
1 | server: |
上面示例,如果激活了 development
profile 文件,server.address
属性值是 127.0.0.1。类似的,如果 production
和 eu-central
profiles 激活,server.address属性值是 192.168.1.120。如果 development, production, eu-central 都没有启用,则属性的值为 192.168.1.100。
备注:因此 spring.profiles
可以包含一个简单的 profile
文件名称(例如 production
)或 profile 表达式。 一个profile 表达式允许更复杂的 profile 文件逻辑,例如 production & (eu-central | eu-west)
。 有关更多详细信息,请参阅参考指南。
如果在启动应用程序上下文时未显式激活任何配置,则会激活默认配置文件。 因此,在以下 YAML 中,我们为spring.security.user.password
设置了一个值,该值仅在 default
配置文件中可用:
1 | server: |
在下面的示例中,始终设置密码,因为它没有附加到任何 profile 文件,并且必须在所有其他配置文件中根据需要显式重置密码:
1 | server: |
spring.profiles
元素指定的 Spring profile 文件可以通过使用 !
字符来表示否定。如果为单个文件同时指定了否定(negated ) profile 文件和非否定(non-negated) profile文件,则必须至少有一个非否定配置文件匹配,并且不能有否定配置文件匹配。
YAML 缺点
YAML 文件不能被 @PropertySource
注解加载。因此,如果需要以这种方式加载配置,则需要使用 properties
文件。
在指定的 profile
文件的 YAML文件中使用多YAML文档语法可能会导致意外行为。 例如,考虑文件中的以下配置:
application-dev.yml
1 | server: |
如果使用 --spring.profiles.active=dev
参数运行应用,期望的security.user.password
被设置为 secret
,但这种情况并非如此。
嵌套文档将被过滤,因为主文件名为application-dev.yml
,它已经被认为是 profile
文件的,并且嵌套文档将被忽略。
备注:建议不要混用 profile 文件的 YAML 文件和多 个YAML 文档,坚持只使用其中之一。
类型安全配置属性
使用@Value(${property})
注释注入配置属性有时会很麻烦,特别是在处理多个属性或数据本质上是分层的情况下。Spring Boot 提供了另一种处理属性的方法,这种方法允许强类型bean
控制和验证应用程序的配置,如下例所示:
1 | package com.example; |
上面的 POJO 定义了如下属性:
- acme.enable:默认值为 false。
- acme.remote-address:可以使用 String 类型。
- acme.security.username:嵌套了
security
对象,其名称由属性名称决定。 特别是,返回类型根本不使用,可能是SecurityProperties
。 - acme.security.password。
- acme.security.roles:一个 String 集合。
注意:映射到 Spring Boot 中 @ConfigurationProperties
注解作用的类的属性(通过属性文件,YAML文件,环境变量等进行配置)是 public API,但类本身的访问器(getter
/ setter
)不是直接使用的。
注意:Getter 和 Setter 通常是必需的,因为绑定是通过标准 Java Beans 属性描述符进行的,就像 Spring MVC 一样。 在以下情况下,可以省略 Setter:
映射只要被初始化,就需要一个 getter,但不一定是 setter,因为绑定器可以对它们进行修改。
可以通过索引(通常使用 YAML)或使用单个逗号分隔的值(属性)访问集合和数组。在后一种情况下,setter 是必须的。官方建议始终为此类类型添加 setter。如果初始化集合,请确保它不是不可变的(如前一个示例所示)。
如果嵌套的 POJO 属性已初始化(与前面示例中的 Security 字段类似),则不需要 setter。如果希望绑定器使用其默认构造方法动态创建实例,则需要一个 setter。
有些人使用 Lombok 项目自动添加 getters 和 setters。 需要确保 Lombok 不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化该对象。
最后,仅考虑标准 Java Bean 属性,并且不支持对静态属性的绑定。
注意:可查看 @Value 与 @ConfigurationProperties 的区别。
相关参考
Spring Boot 2系列(五十三):Spring Boot 配置外部化
http://blog.gxitsky.com/2020/04/18/SpringBoot-53-externalized-configuration/