一、外部化配置
Spring Boot将你的配置外部化,因此你可以在不同的环境下运行相同的代码。你可以使用properties
文件,YAML
文件,环境变量,命令行参数在外部配置。使用@Value注解可以直接将属性值注入到bean中,通过Spring的Environment
抽象访问,或通过@ConfigurationProperties
绑定到结构化对象。
Spring Boot有多种外部配置方式,优先级如下:
- 当devtools开启时,
$HOME/.config/spring-boot
下devtools的全局设置属性。 - 测试上的@TestPropertySource注解
- 测试中的
properties
属性。 在@SpringBootTest
和测试注释上可用,用于测试应用程序的特定部分。 - 命令行参数
- 来自
SPRING_APPLICATION_JSON
的属性(嵌入在环境变量或系统属性中的内联JSON) ServletConfig
的初始参数.ServletContext
初始参数- 来自
Java:comp / env
的JNDI属性。 - Java系统属性(
System.getProperties()
). - 操作系统环境变量.
RandomValuePropertySource
仅具有random.*
属性.- jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
- jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
- jar包外部的application-{profile}.properties或application.yml(不带spring.profile)配置文件
- jar包内部的application-{profile}.properties或application.yml(不带spring.profile)配置文件
- @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; // ... }
在您的应用程序类路径上(例如,在jar内),您可以拥有一个application.properties
文件,该文件为名称提供合理的默认属性值。 在新环境中运行时,可以在jar外部提供一个覆盖名称的application.properties
文件。 对于一次性测试,可以使用特定的命令行开关启动(例如,java -jar app.jar --name =“ Spring”
)。
SPRING_APPLICATION_JSON属性可以在命令行中使用环境变量来提供。 例如,您可以在UN * X shell中使用如下命令:
$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar
在前面的示例中,您可以在Spring Environment
中使用acme.name=test
。您还可以将JSON作为系统属性spring.application.json
示例如下:
$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar
你可以将JSON当做命令行参数。示例如下
$ java -jar myapp.jar --spring.application.json='{"name":"test"}'
你也可以提供JSON作为一个JNDI变量。示例如下
java:comp/env/spring.application.json
1、配置随机值
RandomValuePropertySource
用于注入随机值(例如,注入测试用例)。 它可以产生整数,longs,uuids或字符串,如以下示例所示:
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]}
2、访问命令行属性
默认情况下,SpringApplication
将所有命令行选项参数(即以--
开头的参数,例如--server.port = 9000
)转换为属性,并将其添加到Spring Environment
中。 如前所述,命令行属性始终优先于其他属性源。
如果您不想将命令行属性添加到环境中,则可以使用SpringApplication.setAddCommandLineProperties(false)
禁用它们。
3、全局属性文件
SpringApplication
在以下位置找到application.properties
文件加载属性,并将它们添加到Spring Environment
中:
- 当前目录的
/config
子目录 - 当前目录
- 类路径下的
/config
包 - 类路径根目录
该列表按优先级排序(优先级高的属性会覆盖掉优先级低的属性)
如果您不喜欢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.location
指定的是目录(而不是文件),则它们应以/结尾(并且在运行时,应在加载之前附加从spring.config.name
生成的名称。也就是说会到spring.config.location
下找到spring.config.name
指定的文件名。
配置位置以相反的顺序搜索。 默认情况下,配置的位置是classpath:/,classpath:/ config /,file:./,file:./ config /
。 结果搜索顺序如下:
file:./config/
file:./
classpath:/config/
classpath:/
另外,当使用spring.config.additional-location
配置自定义配置位置时,除默认位置外,还会使用它们。并且 要先于默认位置查找自定义配置位置。 例如,如果配置了classpath:/ custom-config /,file:./ custom-config /
自定义位置,则搜索顺序如下:
file:./custom-config/
classpath:custom-config/
file:./config/
file:./
classpath:/config/
classpath:/
通过此搜索顺序,您可以在一个配置文件中指定默认值,然后在另一个配置文件中有选择地覆盖这些值。 您可以在默认位置之一的application.properties
(或使用spring.config.name选择的其他任何基本名称)中为应用程序提供默认值。 然后,可以在运行时使用自定义位置之一中的其他文件覆盖这些默认值。
4、特定属性配置文件
除了application.properties
文件之外,还可以使用以下命名约定来定义特定配置文件的属性:application- {profile} .properties
。 如果没有设置激活的配置文件,则Environment具有一组默认配置文件(默认为[defualt])。 换句话说,如果未显式激活任何概要文件,那么将加载application-default.properties
中的属性。
特顶属性配置文件是从与标准application.properties
相同的位置加载的,特定属性配置文件的文件总是会覆盖非特定文件,无论特定配置文件的文件是在jar包的内部还是外部。
如果指定了多个配置文件,则采用后赢策略。即后指定的配置文件里的值会被使用。
5、属性中的占位符
application.properties
中的值时在使用时会被现有Environment
过滤,因此您可以参考以前定义的值(例如,从System属性中)。
app.name=MyApp app.description=${app.name} is a Spring Boot application
6、加密属性
Spring Boot不提供对加密属性值的任何内置支持,但是,它提供了修改SpringEnvironment
中包含的值所必需的挂钩点。 EnvironmentPostProcessor
接口允许您在应用程序启动之前操纵Environment
。
7、使用YAML代替properties作为配置文件
YAML是JSON的超集,因此是一种用于指定层次结构配置数据的便捷格式。 只要在类路径上具有SnakeYAML库,SpringApplication
类就会自动支持YAML作为属性的替代方法。
If you use “Starters”, SnakeYAML is automatically provided by
spring-boot-starter
.
1)、加载YAML
Spring Framework提供了两个方便的类用于加载YAML文档。 YamlPropertiesFactoryBean
将YAML作为properties加载,而YamlMapFactoryBean
将YAML作为Map加载。
例如,下面的YAML文档:
environments: dev: url: https://dev.example.com name: Developer Setup prod: url: https://another.example.com name: My Cool App
将被转换为以下properties:
environments.dev.url=https://dev.example.com environments.dev.name=Developer Setup environments.prod.url=https://another.example.com environments.prod.name=My Cool App
YAML表示List类型
my: servers: - dev.example.com - another.example.com
对应的properties:
my.servers[0]=dev.example.com my.servers[1]=another.example.com
将上面yaml中的属性绑定到对应的类的属性上,示例如下:
@ConfigurationProperties(prefix="my") public class Config { private List<String> servers = new ArrayList<String>(); public List<String> getServers() { return this.servers; } }
2)、将YAML像properties一样公布到spring Environment中
YamlPropertySourceLoader
类可在Environment
中将YAML公开为PropertySource
。 这样做可以让您使用@Value批注和占位符语法来访问YAML属性。
3)、多环境配置文件
您可以使用spring.profiles
在一个文件中指定多个特定配置文件的YAML文档,以指示何时应用该文档,如以下示例所示:
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
环境被激活,则server.address
属性为127.0.0.1
。 同样,如果production
和eu-central
环境激活,则server.address
属性为192.168.1.120
。 如果未激活development
,production
和eu-central
环境,则该属性的值为192.168.1.100
。
因此spring.profiles
可以是一个简单名称(例如development
)或表达式。表达式允许表达更复杂的逻辑,例如production&(eu-central | eu-west)
。
如果在启动应用程序上下文时未显式激活任何环境,则会激活默认配置文件。 因此,在以下YAML中,我们为spring.security.user.password
设置了一个值,该值仅在“default”
配置文件中可用:
server: port: 8000 --- spring: profiles: default security: user: password: weak
而在以下示例中,始终会设置password,因为password并不是在profile中设置的。
server: port: 8000 spring: security: user: password: weak
4)、YAML的缺点
无法使用@PropertySource
注解加载YAML文件。 因此,需要加载值的话,需要使用properties文件。
在特定配置文件的YAML文件中使用多YAML文档语法可能会导致意外行为。 示例如下:
application-dev.yml
server: port: 8000 --- spring: profiles: "!test" security: user: password: "secret"
如果使用参数--spring.profiles.active = dev
运行应用程序,则可能希望将security.user.password
设置为“ secret”,但结果并非如此。
嵌套文档将被过滤,因为主文件名为application-dev.yml
。 它已经被认为是特定配置文件.
建议不要混用特定配置文件的YAML文件和多个YAML文档。 应该只使用其中之一。
8、安全配置属性
使用@Value(“ $ {property}”)
注解来注入配置属性有时会很麻烦,尤其是当你需要注入大量属性或属性本来就是有层次结构的时候。 Spring Boot提供了一种使用属性的替代方法,该方法使强类型的Bean可以管理和验证属性配置。
1)、javaBean属性绑定
可以如下示例一样绑定标准的javaBean属性
package com.example; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("acme") public class AcmeProperties { private boolean enabled; private InetAddress remoteAddress; private final Security security = new Security(); public boolean isEnabled() { ... } public void setEnabled(boolean enabled) { ... } public InetAddress getRemoteAddress() { ... } public void setRemoteAddress(InetAddress remoteAddress) { ... } public Security getSecurity() { ... } public static class Security { private String username; private String password; private List<String> roles = new ArrayList<>(Collections.singleton("USER")); public String getUsername() { ... } public void setUsername(String username) { ... } public String getPassword() { ... } public void setPassword(String password) { ... } public List<String> getRoles() { ... } public void setRoles(List<String> roles) { ... } } }
前面的POJO定义了以下属性:
acme.enabled
, 默认为falseacme.remote-address
, 可以由字符串强转.acme.security.username
, 嵌套的类acme.security.password
.acme.security.roles
,String集合
@ConfigurationProperties注解的类的属性映射是通过(通过属性文件,YAML文件,环境变量等进行配置)公共API,该类本身的访问器(getter/setter)并没有直接使用 。
2)、构造器绑定
上一节中的示例可以重写,如下例所示:
package com.example; import java.net.InetAddress; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.bind.DefaultValue; @ConstructorBinding @ConfigurationProperties("acme") public class AcmeProperties { private final boolean enabled; private final InetAddress remoteAddress; private final Security security; public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) { this.enabled = enabled; this.remoteAddress = remoteAddress; this.security = security; } public boolean isEnabled() { ... } public InetAddress getRemoteAddress() { ... } public Security getSecurity() { ... } public static class Security { private final String username; private final String password; private final List<String> roles; public Security(String username, String password, @DefaultValue("USER") List<String> roles) { this.username = username; this.password = password; this.roles = roles; } public String getUsername() { ... } public String getPassword() { ... } public List<String> getRoles() { ... } } }
在此设置中,@ConstructorBinding
注解用于指示应使用构造函数绑定。 这意味着绑定器将找到希望绑定的参数的构造函数。
@ConstructorBinding
类的内部类(例如上例中的Security)也将通过其构造函数进行绑定。
可以使用@DefaultValue
指定默认值.
要使用构造函数绑定,必须使用@EnableConfigurationProperties
或配置属性扫描来启用该类。 您不能对通过常规Spring机制创建的bean使用构造函数绑定(例如@Component
bean,通过@Bean
方法创建的bean或使用@Import
加载的bean)
如果您的类具有多个构造函数,则还可以直接在应绑定的构造函数上使用@ConstructorBinding
。
3)、启用@ConfigurationProperties
注解
Spring Boot提供了绑定@ConfigurationProperties
注解的类并将其注册为Bean的基础功能。 你可以在类上逐个添加此注解,也可以像扫描组件包一样扫描。
有时,用@ConfigurationProperties
注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置,或者想要有条件地启用它们。 在这些情况下,请使用@EnableConfigurationProperties
注解指定要处理的类型列表。 可以在任何@Configuration类上完成此操作,如以下示例所示:
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(AcmeProperties.class) public class MyConfiguration { }
要使用配置属性扫描,请将@ConfigurationPropertiesScan
注解添加到您的应用程序。 通常,它被添加到以@SpringBootApplication
注解的主应用程序类中,但是可以将其添加到任何@Configuration类中。 默认情况下,将从声明注释的类的包中进行扫描。 如果要定义要扫描的特定程序包,可以按照以下示例所示进行操作:
@SpringBootApplication @ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" }) public class MyApplication { }
使用配置属性扫描或通过@EnableConfigurationProperties
注册@ConfigurationProperties
Bean时,该Bean具有常规名称:<prefix>-<fqn>
,其中<prefix>
是@ConfigurationProperties
注解中指定的,<fqn>
是bean的全类名。 如果注解不提供任何前缀,则仅使用Bean的全类名。
上例中的bean名称是acme-com.example.AcmeProperties
。
建议@ConfigurationProperties
仅处理环境,尤其不要在上下文中注入其他bean。 对于极端情况,可以使用setter注入或框架提供的任何* Aware
接口(例如,需要访问Environment
,可以用EnvironmentAware
)。 如果仍要使用构造函数注入其他bean,则必须使用@Component
注解配置属性bean,并使用基于JavaBean的属性绑定。
4)、使用@ConfigurationProperties
注解类型
在SpringApplication
的外部YAML属性文件使用下面风格的配置方法:
# application.yml acme: remote-address: 192.168.1.1 security: username: admin roles: - USER - ADMIN # additional configuration as required
要使用@ConfigurationProperties
注解的 Bean,可以像其他Bean一样注入它们,如以下示例所示:
@Service public class MyService { private final AcmeProperties properties; @Autowired public MyService(AcmeProperties properties) { this.properties = properties; } //... @PostConstruct public void openConnection() { Server server = new Server(this.properties.getRemoteAddress()); // ... } }
使用@ConfigurationProperties
还可让您生成元数据文件,IDE可以使用这些元数据文件为键提供自动完成功能。
5)、第三方配置
@ConfigurationProperties
除了可以注解在类上,你也可以在一个@Bean
标注的方法上使用此注解。这在绑定你无法控制的第三方组件的属性时十分有用。
要通过Environment
属性去配置一个bean,将@ConfigurationProperties
添加到这个bean的注册方法上。示例如下:
@ConfigurationProperties(prefix = "another") @Bean public AnotherComponent anotherComponent() { ... }
another
前缀下的javaBean属性可以映射到AnotherComponent
bean中
6)、宽松绑定
Spring Boot使用了一些宽松匹配规则来将Environment
的属性绑定到@ConfigurationProperties
beans中,因此Environmnent
属性和bean属性名之前不需要太过匹配。
常见示例包括破折号分隔的环境属性(例如,context-path
会绑定到contextPath
)和大写的环境属性(例如PORT
绑定到port
)。
示例如下:
@ConfigurationProperties(prefix="acme.my-project.person") public class OwnerProperties { private String firstName; public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } }
可以将下面的属性绑定到上面的属性中
Property | Note |
---|---|
acme.my-project.person.first-name |
在.properties 和 .yml 文件中推荐的写法. |
`acme.myProject.person.firstName | 标准的驼峰式写法 |
acme.my_project.person.first_name |
下划线,.properties 和.yml 中的另一种写法 |
ACME_MYPROJECT_PERSON_FIRSTNAME |
大写格式,使用系统环境变量时建议使用 |
宽松绑定的规则:
Property Source | Simple | List |
---|---|---|
Properties Files | 驼峰式,短横线,下划线 | 使用[]或逗号分隔 |
YAML Files | 驼峰式,短横线,下划线 | Standard YAML list syntax or comma-separated values |
Environment Variables | 使用下划线加大写 | 用下划线包围数字MY_ACME_1_OTHER = my.acme[1].other |
System properties | 驼峰式,短横线,下划线 | 使用[]或逗号分隔 |
建议以小写加短横线的方式,例如my.property-name=acme
绑定Map类型属性时,如果键包含小写字母数字字符和短横线以外的任何其他字符,则需要使用方括号表示法,以便保留原始值。 如果键没有被[]包围,则所有非字母数字或-的字符都将被删除。 例如,考虑将以下属性绑定到Map:
acme: map: "[/key1]": value1 "[/key2]": value2 /key3: value3
上面的属性将以/ key1
,/ key2
和key3
作为Map中的键绑定到Map。
对于YAML文件,方括号需要用引号括起来,以便正确地解析。
7)、复合类型合并
当lists的值配置在多个地方,list整个都会被覆盖掉,不会合并。
例如,假定有一个MyPojo
对象,有name
和description
属性并且默认为null。在AcmeProperties
中有MyPojo
类型的list。
@ConfigurationProperties("acme") public class AcmeProperties { private final List<MyPojo> list = new ArrayList<>(); public List<MyPojo> getList() { return this.list; } }
配置文件中的配置如下:
acme: list: - name: my name description: my description --- spring: profiles: dev acme: list: - name: my another name
如果dev
profile没有激活,AcmeProperties.list
只包含前面定义的一个实体。如果dev
profile激活,list
仍然只有一个实体(但是name
的值为my another name
并且description
为空)。这个配置没有添加第二个MyPojo实例到list,没有进行合并。
在多个配置文件中指定List
时,将使用优先级最高的list(并且仅使用那个list)
acme: list: - name: my name description: my description - name: another name description: another description --- spring: profiles: dev acme: list: - name: my another name
在上面这个配置中,当dev
被激活时,list仍然只有一个实体。
对于Map类型的属性,你可以从多个源中来绑定值。但是,在多个源中相同的属性,会使用高优先级文件中的属性值。
@ConfigurationProperties("acme") public class AcmeProperties { private final Map<String, MyPojo> map = new HashMap<>(); public Map<String, MyPojo> getMap() { return this.map; } }
属性配置如下
acme: map: key1: name: my name 1 description: my description 1 --- spring: profiles: dev acme: map: key1: name: dev name 1 key2: name: dev name 2 description: dev description 2
当dev
profile没有激活时,map只有一个实体,但是当dev
激活时,map有两个实体,并且key1
的值(name=dev name 1
,description: my description 1
)。说明两个profile中的map值合并了。
前述合并规则不仅适用于YAML文件,而且适用于所有属性源中的属性。
8)、属性转换
Spring Boot尝试将外部应用属性转换成正确的类型绑定到@ConfigurationProperties
beans中。如果你需要自定义转化方式,又可以创建一个ConversionService
bean(通过名字为conversionService
的bean),或者自定义属性编辑器(通过名字为CustomEditorConfigurer
的bean)或者自定义Converters
(注册bean时使用@ConfigurationPropertiesBinding
)
(1)、转换时间
Spring Boot为表达持续时间提供了专门的支持。 如果公开java.time.Duration属性,则应用程序属性中的以下格式可用:
- 常规的
long
表示形式(使用毫秒作为默认单位,除非已指定@DurationUnit) java.time.Duration
使用的标准ISO-8601格式- 值和单位相结合的更易读的格式(例如10s表示10秒)
@ConfigurationProperties("app.system") public class AppSystemProperties { @DurationUnit(ChronoUnit.SECONDS) private Duration sessionTimeout = Duration.ofSeconds(30); private Duration readTimeout = Duration.ofMillis(1000); public Duration getSessionTimeout() { return this.sessionTimeout; } public void setSessionTimeout(Duration sessionTimeout) { this.sessionTimeout = sessionTimeout; } public Duration getReadTimeout() { return this.readTimeout; } public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; } }
要指定30秒的会话超时,则30
,PT30S
和30s
都是等效的。 可以使用以下任意形式指定500ms的读取超时:500
,PT0.5S
和500ms
。
你可以使用任何支持的单位:
ns
纳秒us
微秒ms
(毫秒)s
秒m
分钟h
小时d
天
默认单位是毫秒,可以使用@DurationUnit覆盖,如上面的示例所示。
(2)、转换数据大小
Spring Framework具有DataSize
值类型,以字节为单位表示大小。 如果公开DataSize
属性,则应用程序属性中的以下格式可用:
- 常规的
long
表示形式(除非已指定@DataSizeUnit
,否则使用字节作为默认单位) - 值和单位耦合在一起的更易读的格式(例如10MB表示10兆字节)
@ConfigurationProperties("app.io") public class AppIoProperties { @DataSizeUnit(DataUnit.MEGABYTES) private DataSize bufferSize = DataSize.ofMegabytes(2); private DataSize sizeThreshold = DataSize.ofBytes(512); public DataSize getBufferSize() { return this.bufferSize; } public void setBufferSize(DataSize bufferSize) { this.bufferSize = bufferSize; } public DataSize getSizeThreshold() { return this.sizeThreshold; } public void setSizeThreshold(DataSize sizeThreshold) { this.sizeThreshold = sizeThreshold; } }
若要指定10 MB的缓冲区大小,则10
和10MB
是等效的。 256个字节的大小阈值可以指定为256
或256B
。
你也可以使用下面的单位:
B
代表字节KB
千字节MB
(兆字节)GB
(千兆字节)TB
(兆兆字节)
默认单位是字节,可以使用@DataSizeUnit
覆盖,如上面的示例所示
9、@ConfigurationProperties校验
每当使用@Validated
注解@ConfigurationProperties
注解的类时,Spring Boot就会进行校验。
您可以在配置类上直接使用JSR-303标准 javax.validation
约束注释。 为此,请确保在类路径上有兼容的JSR-303实现,然后将约束注释添加到字段中,如以下示例所示:
@ConfigurationProperties(prefix="acme") @Validated public class AcmeProperties { @NotNull private InetAddress remoteAddress; // ... getters and setters }
为了确保始终为嵌套属性触发验证,即使未找到任何属性,也必须使用@Valid注释关联的字段。 以下示例基于前面的AcmeProperties示例:
@ConfigurationProperties(prefix="acme") @Validated public class AcmeProperties { @NotNull private InetAddress remoteAddress; @Valid private final Security security = new Security(); // ... getters and setters public static class Security { @NotEmpty public String username; // ... getters and setters } }
您还可以通过创建一个名为configurationPropertiesValidator
的bean定义来添加自定义的Spring Validator
。 @Bean方法应声明为静态。 配置属性验证器是在应用生命周期的早期创建的,并且将@Bean方法声明为static可以使创建该Bean而不必实例化所在@Configuration
类。 这样做避免了由早期实例化引起的问题。
10、@ConfigurationProperties和@Value的不同
@Value
批注是核心容器功能,它没有提供与类型安全的配置属性相同的功能。 下表总结了@ConfigurationProperties
和@Value
支持的功能:
Feature | @ConfigurationProperties |
@Value |
---|---|---|
松散绑定 | Yes | No |
元数据支持 | Yes | No |
SpEL 表达式 |
No | Yes |
如果为自己的组件定义了一组配置键,建议您将它们组合在以@ConfigurationProperties
注释的POJO中。 您还应该意识到,由于@Value
不支持宽松的绑定,因此如果您需要使用环境变量来提供值,则它不是一个很好的选择。
最后,尽管您可以在@Value中编写SpEL表达式,但是并不会处理这个表达式。
来源:https://www.cnblogs.com/ylcc-zyq/p/12599987.html