20191110 Spring Boot官方文档学习(4.2)
4.2。外部化配置
Spring Boot使您可以外部化配置,以便可以在不同环境中使用相同的应用程序代码。您可以使用Properties文件,YAML文件,环境变量和命令行参数来外部化配置。属性值可以通过@Value
注解直接注入到你的bean ,通过Spring Environment抽象的访问,或者通过@ConfigurationProperties
绑定到结构化对象。
Spring Boot使用一个非常特殊的PropertySource
顺序,该顺序旨在允许合理地覆盖值。属性按以下顺序考虑,优先级从高到低:
- 当
devtools
处于活动状态时,文件夹$HOME/.config/spring-boot中的Devtools
全局设置属性。 tests
中的@TestPropertySource
注解。tests
中的properties
属性。可用于@SpringBootTest
注解和测试应用程序的特定部分的测试注解。- 命令行参数。
- 来自
SPRING_APPLICATION_JSON
(嵌入在环境变量或系统属性中的嵌入式JSON)的属性。 ServletConfig
初始化参数。ServletContext
初始化参数。java:comp/env
的JNDI属性。- Java系统属性(
System.getProperties()
)。 - 操作系统环境变量。
- 只有在有
random.*
属性时的RandomValuePropertySource
。 - 打包的jar之外的 Profile-specific
application properties
(application-{profile}.properties
和YAML变体)。 - 打包在jar中的特定于配置文件的应用程序属性(
application-{profile}.properties
和YAML变体)。 - 打包的jar之外的应用程序属性(
application.properties
和YAML变体)。 - 打包在jar中的应用程序属性(
application.properties
和YAML变体)。 @Configuration
类上的@PropertySource
注解。- 默认属性(通过设置
SpringApplication.setDefaultProperties
指定)。
注意:
打印null:
@Controller
public class DemoController {
@Value("${myname}")
private String myname;
public DemoController(){
System.out.println(myname);
}
}
打印配置的值:
@Controller
public class DemoController {
public DemoController(@Value("${myname}") String myname){
System.out.println(myname);
}
}
使用SPRING_APPLICATION_JSON
属性:
- 在
UN*X shell
中:
$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar
- System属性
$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar
- 命令行参数
$ java -jar myapp.jar --spring.application.json='{"name":"test"}'
- 将JSON作为JNDI变量提供
java:comp/env/spring.application.json
4.2.1。配置随机值
RandomValuePropertySource
用来注入随机值,可以产生数字,uuid,字符串等。
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(10)
表示小于10的整数
4.2.2。访问命令行属性
默认情况下,SpringApplication
将所有命令行选项参数(即以 --
开头的参数,例如--server.port=9000
)转换为property
并将其添加到Spring Environment。如前所述,命令行属性始终优先于其他属性源。
如果您不想将命令行属性添加到Environment,则可以使用SpringApplication.setAddCommandLineProperties(false)
禁用它们。
4.2.3。应用程序属性文件
SpringApplication
从以下位置的application.properties
文件中加载属性并将其添加到Spring Environment中,优先级从高到低:
- 当前目录的
/config
子目录
jar包所在目录的config
目录下 - 当前目录
jar包所在目录 - 类路径的
/config
包
src\main\resources\config
- 类路径
src\main\resources
可以使用YAML(.yml
)文件来替代.properties
spring.config.name
属性指定配置文件名称(默认application.properties
),spring.config.location
属性指定配置文件位置(逗号分隔多个目录或文件)
spring.config.name=myproject
spring.config.location=classpath:/default.properties,classpath:/override.properties
spring.config.name
和spring.config.location
很早就用于确定必须加载哪些文件。必须将它们定义为环境属性(通常是OS环境变量,系统属性或命令行参数)。
如果spring.config.location
包含目录(而不是文件),则应以 /
结尾(并且在运行时,在目录后附加从 spring.config.name
加载之前生成的名称,包括 profile-specific 文件名)。spring.config.location
指定的文件按原样使用,不支持特定于配置文件的变体,并且被任何 profile-specific 文件的属性覆盖。
配置位置以相反的顺序搜索。默认情况下,配置的位置是classpath:/,classpath:/config/,file:./,file:./config/
。结果搜索顺序如下:
file:./config/
file:./
classpath:/config/
classpath:/
当使用来配置自定义配置位置时spring.config.location
,它们将替换默认位置。例如,如果spring.config.location
使用值配置classpath:/custom-config/,file:./custom-config/
,则搜索顺序将变为以下内容:
file:./custom-config/
classpath:custom-config/
使用 spring.config.additional-location
自定义配置位置时,它们和默认位置一起使用。在默认位置之前搜索自定义配置位置。例如,如果配置了位置 classpath:/custom-config/,file:./custom-config/
,则搜索顺序变为以下内容:
file:./custom-config/
classpath:custom-config/
file:./config/
file:./
classpath:/config/
classpath:/
如果您使用环境变量而不是系统属性,则大多数操作系统都不允许使用句点分隔的键名,但是您可以使用下划线代替(例如,SPRING_CONFIG_NAME
代替spring.config.name
)。
如果您的应用程序在容器中运行,则可以使用JNDI属性(在 java:comp/env
中)或Servlet上下文初始化参数来代替环境变量或系统属性。
4.2.4。Profile-specific 属性
除application.properties
文件外,还可以使用以下命名约定来定义特定于配置文件的属性:application-{profile}.properties
。如果没有 active 的 profile,在Environment具有一组默认的profile(默认[default]
)。换句话说,如果没有 active 的 profile,那么将从application-default.properties
中加载属性。
特定于配置文件的属性是从与标准的 application.properties
相同的位置加载的,指定 profile 的配置文件始终会覆盖非指定文件,无论指定 profile 的配置文件是在jar包的内部还是外部。
如果指定了多个profile,则采用后赢策略。例如,由spring.profiles.active
属性指定的配置文件会在通过SpringApplication API 配置的配置文件之后添加,因此具有优先权。
如果您在 spring.config.location
中指定了任何文件,则不会考虑profile-specific 的配置文件。如果您还想使用profile-specific 的配置文件,请使用spring.config.location
指定目录。
4.2.5。属性中的占位符
使用application.properties
中的值时,它们会通过现有的Environment进行过滤,因此您可以使用以前定义的值(例如,从“系统”属性中),引用与定义的先后顺序无关。
app.name=MyApp
app.description=${app.name} is a Spring Boot application
4.2.6。加密属性
Spring Boot不提供对加密属性值的任何内置支持,但是,它提供了修改Spring Environment包含的值所必需的挂钩点。EnvironmentPostProcessor
接口允许您在应用程序启动之前操作Environment。
4.2.7。使用YAML代替Properties
YAML是JSON的超集,是一种用于指定层次结构配置数据的便捷格式。SpringApplication
自动支持YAML,只要SnakeYAML
库在classpath中。
spring-boot-starter
自带 SnakeYAML
。
加载YAML
Spring Framework提供了两个方便的类,可用于加载YAML文档。YamlPropertiesFactoryBean
加载YAML为Properties
,YamlMapFactoryBean
加载YAML为Map
。
my:
servers:
- dev.example.com
- another.example.com
等同于
my.servers[0]=dev.example.com
my.servers[1]=another.example.com
要使用Spring Boot的Binder实用程序绑定属性(例如@ConfigurationProperties
),您需要在类型为java.util.List
(或Set)的目标bean中具有一个属性,并且需要提供setter或使用可变值对其进行初始化。例如,以下示例绑定到前面显示的属性:
@ConfigurationProperties(prefix = "my")
public class MyConfig {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}
在Spring Environment中将YAML配置文件为属性
YamlPropertySourceLoader
类可用于在Spring Environment暴露YAML作为PropertySource
。这样做使您可以将@Value
注解和占位符语法一起使用以访问YAML属性。
多Profile的YAML文档
在一个YAML文档中可以使用 spring.profiles
键来指示属性何时适用,从而文件中指定多个 profile ,如以下示例所示:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production & eu-central
server:
address: 192.168.1.120
在此示例中,如果development profile处于active状态,则server.address属性为127.0.0.1。同样,如果production 和 eu-central profiles处于active状态,则server.address属性为192.168.1.120。如果development,production和eu-central在配置文件没有启用,那么该属性的值192.168.1.100。
如果在启动应用程序上下文时未明确active任何profile,则会激活 default
profile。因此,在以下YAML中,我们设置了一个仅在default
profile 中可用的值 spring.security.user.password:
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak
spring.profiles
可以使用简单的表达式逻辑。
使用spring.profiles
元素指定的profiles可以选择使用 !
字符来否定。如果为单个文档指定了否定的profiles和非否定的profiles,则至少一个非否定的profile必须匹配,并且否定的profiles可以不匹配。
YAML的缺点
无法通过使用@PropertySource
注解加载YAML文件。
在特定于profile的YAML文件中使用多YAML文档语法可能会导致意外行为。
# 文件名application-dev.yml
server:
port: 8000
---
spring:
profiles: "!test"
security:
user:
password: "secret"
当激活dev profile时,spring.security.user.password不是"secret",因为YAML文档已被指定为dev profile,所以会忽略嵌套的profile配置。
不要混用指定profile的YAML文件和多 profiles YAML文档。坚持只使用其中之一。
4.2.8。类型安全的配置属性
JavaBean属性绑定
yml配置:
acme:
enabled: true
remoteAddress: 10.1.1.2
security:
username: abc
password: xxx
roles:
- a
- b
- c
- x
Java注入类:
@ConfigurationProperties("acme")
@Data
public class AcmeProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
@Data
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
}
}
acme.security.roles:其默认值为内容为USER的集合。
Spring Boot自动配置大量使用@ConfigurationProperties
来轻松配置自动配置的Bean。与自动配置类相似,Spring Boot中可用的@ConfigurationProperties
类仅供内部使用。通过属性文件,YAML文件,环境变量等配置的映射到类的属性是公共API,但是并不意味着类本身的内容可以直接使用。
这种安排依赖于默认的空构造器,并且getter
和setter
通常是强制性的,因为绑定是通过标准Java Beans属性描述符进行的,就像在Spring MVC中一样。在以下情况下,可以省略setter方法:
- 只要Map被初始化,它们就需要使用getter,但不一定需要使用setter,因为它们可以被binder改变。
- 可以通过索引(YAML)或使用单个逗号分隔的值(properties)来访问集合和数组。在后一种情况下,必须使用setter。我们建议始终为此类类型添加setter。如果初始化集合,请确保它不是不可变的(如上例所示)。
- 如果嵌套的POJO属性已初始化(如前面示例中的Security字段),则不需要setter。如果希望通过binder使用其默认构造函数动态创建实例,则需要一个setter。
有些人使用Lombok
自动添加setter和getter。确保Lombok不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化该对象。
最后,仅考虑标准Java Bean属性,不支持对静态属性的绑定。
我的发现
使用@ConfigurationProperties
注解,IDEA
出现警告信息:
解决方法:
加入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
作用:
点击自定义配置时,可以跳转到Java类上,也可以在编写配置文件时进行提示,但是提示功能要运行一次程序。
构造函数绑定
@ConstructorBinding
注解指示应使用构造函数绑定。@ConstructorBinding
类的嵌套成员(例如Security)也将通过其构造函数进行绑定。
可以使用@DefaultValue
指定默认值,并且将应用相同的转换服务将String值强制转为缺少属性的目标类型。
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import java.net.InetAddress;
import java.util.List;
@ToString
@ConstructorBinding
@ConfigurationProperties("acme")
public class AcmeProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security;
public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {
System.out.println("AcmeProperties...AcmeProperties...");
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
@ToString
public static class Security {
private String username;
private String password;
private List<String> roles;
public Security(String username, String password,
@DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
}
}
如果您的类具有多个构造函数,则还可以直接在应绑定的构造函数上使用@ConstructorBinding
。
要使用构造函数绑定,必须使用@EnableConfigurationProperties
或配置属性扫描(@SpringBootApplication
带有@ConfigurationPropertiesScan
,即属性扫描。)。您不能对通过常规Spring机制创建的bean使用构造函数绑定(例如,@Component
beans,通过@Bean
方法创建的beans或使用@Import
加载的bean)
启用@ConfigurationProperties
注解的类
Spring Boot提供了绑定类型并将其自动注册为Bean的基础架构。如果您的应用程序使用@SpringBootApplication
,带有@ConfigurationProperties
注解的类将被自动扫描并注册为bean。默认情况下,扫描将从声明@SpringBootApplication
注解的类的包中进行。如果要定义要扫描的特定程序包,可以在@SpringBootApplication
注解类上使用显式指令@ConfigurationPropertiesScan
来进行扫描,如以下示例所示:
@SpringBootApplication
@ConfigurationPropertiesScan({"com.example.app", "org.acme.another"})
public class MyApplication {
有时,带@ConfigurationProperties
注解的类可能不适用于扫描(或不在制定的扫描中),例如,如果您正在开发自己的自动配置。在这些情况下,您可以手动指定要在任何@Configuration类上处理的类型的列表,如以下示例所示:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(AcmeProperties.class)
public class MyConfiguration {
}
当@ConfigurationProperties
bean使用配置属性扫描或通过@EnableConfigurationProperties
注册,bean具有常规名称:<prefix>-<fqn>
,其中,<prefix>
是在@ConfigurationProperties
注解中指定的环境键前缀,<fqn>
是bean的全限定名。如果注解不提供任何前缀,则仅使用Bean的完全限定名称。
上例中的Bean名称为acme-com.example.AcmeProperties
。
我们建议@ConfigurationProperties
仅处理环境,尤其不要从上下文中注入其他bean。对于特殊情况,可以使用setter注入或框架提供的*Aware接口
(例如,如果需要访问Environment,EnvironmentAware
)。如果您仍想使用构造函数注入其他bean,则必须对配置属性bean使用@Component
注释,并使用基于JavaBean的属性绑定。
第三方配置
@ConfigurationProperties
除了用于注释类之外,您还可以在公共@Bean
方法上使用它。
@Configuration
public class MyConfiguration {
@ConfigurationProperties("acme")
@Bean
public AcmeProperties acmeProperties() {
return new AcmeProperties();
}
}
这种方式适用于不能修改源码的时候配置Bean。
宽松绑定
Spring Boot使用一些宽松的规则将Environment属性绑定到@ConfigurationProperties
Bean,因此Environment属性名称和Bean属性名称之间不需要完全匹配。常见示例包括破折号分隔的环境属性(例如,context-path
绑定到contextPath
)和大写的环境属性(例如,PORT
绑定到port
)。
示例:
@Data
@ConfigurationProperties(prefix = "acme.my-project.person")
public class OwnerProperties {
private String firstName;
}
属性文件中可以使用以下属性绑定:
属性 | 注意 |
---|---|
acme.my-project.person.first-name | 短横线式(Kebab),建议在.properties和.yml文件中使用。 |
acme.myProject.person.firstName | 标准驼峰式(Camel)语法。 |
acme.my_project.person.first_name | 下划线(Underscore)表示法,是在.properties和.yml文件中使用的另一种格式。 |
ACME_MYPROJECT_PERSON_FIRSTNAME | 大写格式,使用系统环境变量时建议使用。 |
@ConfigurationProperties
的属性prefix
的值必须为短横线式(小写并用分隔-
,例如acme.my-project.person)。不管配置文件中是my-project还是myProject。
宽松绑定的规则:
属性来源 | Simple | List |
---|---|---|
Properties文件 | 驼峰式,短横线式或下划线式 | 使用[ ]或以逗号分隔的值的标准列表语法 |
YAML文件 | 驼峰式,短横线式或下划线式 | 标准YAML List语法或逗号分隔的值 |
环境变量 | 以下划线作为分隔符的大写格式。_ 不应在属性名称中使用 |
下划线括起来的数值,例如MY_ACME_1_OTHER = my.acme[1].other |
系统属性 | 驼峰式,短横线式或下划线式 | 使用[] 或以逗号分隔的值的标准列表语法 |
我们建议,属性以小写短横线式存储,例如my.property-name=acme
。
绑定Map
属性时,如果key包含除小写字母、数字字符或 -
之外的任何内容,则需要使用方括号表示法,以便保留原始值。如果键没有被 []
包围,则所有其他字符被删除。例如:
map:
"[/key1]": value1
"[/key2]": value2
/key3: value3
Map具有 /key1
,/key2
和 key3
作为映射中的键。
合并复杂类型
List
、Map
在多个 Profiles 中指定时,将使用优先级最高的一个(并且只有该优先级)。
acme:
list:
- name: my name
description: my description
---
spring:
profiles: dev
acme:
list:
- name: my another name
如果dev profile未激活,则AcmeProperties.list包含一个MyPojo条目。但是,如果激活了dev profile,则list仍然仅包含一个条目(名称为my another name,description为null)。此配置不会将第二个MyPojo实例添加到列表中,并且不会合并项目。
属性转换
当Spring Boot绑定到@ConfigurationProperties
bean 时,它试图将外部属性强制转换为正确的类型。如果您需要自定义类型转换,则可以提供一个ConversionService
Bean(具有一个名为conversionService
的Bean)或一个定制属性编辑器(通过CustomEditorConfigurer
Bean)或定制Converters
(具有@ConfigurationPropertiesBinding
注解的Bean)。
由于在应用程序生命周期中非常早就请求了此bean,因此请确保限制正在使用的ConversionService
的依赖项。通常,您需要的任何依赖项可能在创建时未完全初始化。如果不需要配置键强制转换并且仅依赖具有限定符@ConfigurationPropertiesBinding
的自定义转换器,则您可能想重命名自定义ConversionService
。
转换时间
Spring Boot为表达 durations 提供了专门的支持。如果定义了java.time.Duration
属性,则配置文件中属性可用以下格式:
- 常规long表示形式(使用毫秒作为默认单位,除非已指定
@DurationUnit
) - 使用
java.time.Duration
的标准的ISO-8601
格式 - 值和单位耦合的更易读的格式(例如,10s表示10秒)
考虑以下示例:
@Data
@ConfigurationProperties("app.system")
public class AppSystemProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
}
要指定30秒的 sessionTimeout ,30、PT30S、30s都等效。500毫秒的 readTimeout 可以以任何形式如下指定:500,PT0.5S和500ms。
也可以使用任何受支持的单位。这些是:
- ns 纳秒
- us 微秒
- ms 毫秒
- s 秒
- m 分钟
- h 小时
- d 天
默认时间单位是毫秒,可以使用@DurationUnit
注解指定时间单位。
转换数据大小
Spring Framework的DataSize
值类型表示字节大小。如果定义DataSize属性,则配置文件中属性可用以下格式:
- 常规long表示形式(除非
@DataSizeUnit
已指定,否则使用byte
作为默认单位) - 值和单位耦合的更具可读性的格式(例如,10MB意味着10兆字节)
考虑以下示例:
@Data
@ConfigurationProperties("app.io")
public class AppIoProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
}
指定10 MB的bufferSize,10、10MB等效。可以将256个字节的 sizeThreshold 指定为256或256B。
也可以使用任何受支持的单位。这些是:
- B 字节(byte)
- KB 千字节(K byte)
- MB 兆字节(M byte)
- GB 千兆字节(G byte)
- TB 太字节(T byte)
默认单位是字节(byte)
,可以使用@DataSizeUnit
指定单位。
@ConfigurationProperties
验证
Spring Boot会验证带有@Validated
注解的@ConfigurationProperties
类。您可以直接在配置类上使用JSR-303 javax.validation
约束注解。为此,请确保在类路径上有兼容的JSR-303实现(例如,spring-boot-starter-validation
),然后将约束注解添加到字段上,如以下示例所示:
@Data
@ConfigurationProperties(prefix = "acme")
@Validated
public class AcmeProperties {
@NotNull
private InetAddress remoteAddress;
}
还可以通过使用@Validated
注解@Bean
方法来触发验证。
尽管绑定时也会验证嵌套属性,但最好在嵌套属性上使用@Valid
注解。这样可确保即使未找到嵌套属性也将触发验证。下面的示例基于前面的AcmeProperties示例:
@Data
@ConfigurationProperties(prefix = "acme")
@Validated
public class AcmeProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
@Data
public static class Security {
@NotEmpty
private String username;
private String password;
private List<String> roles;
}
}
您还可以通过创建名为configurationPropertiesValidator
的bean定义来添加自定义Spring Validator。该@Bean
方法应声明static
。配置属性验证器是在应用程序生命周期的早期创建的,并且将@Bean
方法声明为static
可以使Bean得以创建而不必实例化@Configuration
类。这样做避免了由早期实例化可能引起的问题。
spring-boot-actuator
模块包括一个公开所有@ConfigurationPropertiesbean
的功能。将您的Web浏览器指向/actuator/configprops
或使用等效的JMX端点。需要添加依赖并配置属性:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# 开启所有Endpoint
management.endpoints.web.exposure.include=*
@ConfigurationProperties
与@Value
@Value
注解是核心容器的功能,它不提供和类型安全配置属性相同的功能。下表总结了@ConfigurationProperties
和@Value
支持的功能:
功能 | @ConfigurationProperties | @Value |
---|---|---|
宽松绑定 | 是 | 否 |
元数据支持 | 是 | 否 |
SpEL 表达式 | 否 | 是 |
元数据支持即,从配置文件跳转到Java文件,还有编写配置文件时的提示功能。