文章摘要(AI生成)
2.配置外部化Spring Boot 允许您将配置外部化,以便您可以在不同的环境中使用相同的应用程序代码。您可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。属性值可以通过使用注解直接注入到你的 bean 中@Value,通过 Spring 的抽象Environ
2.配置外部化
Spring Boot 允许您将配置外部化,以便您可以在不同的环境中使用相同的应用程序代码。您可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。
属性值可以通过使用注解@Value
直接注入到你的 bean 中,通过 Spring 的Environment
抽象访问,或者通过@ConfigurationProperties
绑定到结构化对象中.
Spring Boot 使用一种非常特殊的PropertySource
顺序,以便更好地覆盖值。属性按以下顺序取值(较低项目的值覆盖较早的项目):
- 默认属性(由
SpringApplication.setDefaultProperties
设置指定)。 - 在
@Configuration
类上的注解@PropertySource
。请注意,在刷新应用程序上下文之前,不会将此类属性源添加到Environment
中。配置某些属性(例如在刷新开始之前读取的logging.*
和spring.main.*
)为时已晚。 - 配置数据(例如
application.properties
文件)。 - 仅在
random.*
中具有属性RandomValuePropertySource
。 - 操作系统环境变量。
- Java 系统属性 (
System.getProperties()
)。 - 来自
java:comp/env
的JNDI 属性。 ServletContext
的初始化参数。ServletConfig
的初始化参数。- 来自
SPRING_APPLICATION_JSON
(嵌入在环境变量或系统属性中的内联 JSON)的属性。 - 命令行参数。
- 在测试时的
properties
属性。可用于@SpringBootTest
测试应用程序的特定部分的测试注解。 - 测试上的注解
@TestPropertySource
。 - 当 devtools 处于活动状态时,
$HOME/.config/spring-boot
目录中的Devtools 全局设置属性。
配置数据文件按以下顺序读取:
- 打包在 jar 中的应用程序属性(
application.properties
和 YAML 变体)。 - 打包 jar(
application-{profile}.properties
和 YAML 变体)中的特定于配置文件的应用程序属性。 - 打包 jar(
application.properties
和 YAML 变体)之外的应用程序属性。 - 打包的 jar(
application-{profile}.properties
和 YAML 变体)之外的特定于配置文件的应用程序属性。
**建议在整个应用程序中坚持使用一种格式。如果您在同一位置有同时具有.properties
和.yml
格式的配置文件,.properties
则优先。 **
举一个具体的例子,假设你开发了一个使用name
属性的@Component
,如下例所示:
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
在您的应用程序类路径(例如,在您的 jar 中)上,您可以有一个包含name
属性的application.properties
文件,在新环境中运行时,可以在 jar 之外提供一个application.properties
文件,该文件复写了name
属性. 对于一次性测试,您可以使用特定的命令行开关(例如,java -jar app.jar --name="Spring"
)启动。
env
和configprops
端点可用于确定属性为何具有特定值。您可以使用这两个端点来诊断意外的属性值。有关详细信息,请参阅“生产就绪功能”部分。
2.1.访问命令行属性
默认情况下,SpringApplication
将任何命令行选项参数(即以--
开头的参数,例如--server.port=9000
)转换为property
并将它们添加到 Spring Environment
。如前所述,命令行属性始终优先于基于文件的属性源。
如果您不想将命令行属性添加到Environment
中,可以使用SpringApplication.setAddCommandLineProperties(false)
禁用它们。
2.2. JSON 应用程序属性
环境变量和系统属性通常有限制,这意味着某些属性名称不能使用。为了解决这个问题,Spring Boot 允许您将属性块编码为单个 JSON 结构。
当您的应用程序启动时,任何spring.application.json
或SPRING_APPLICATION_JSON
属性都将被解析并添加到Environment
.
例如,SPRING_APPLICATION_JSON
可以在 UN*X shell 的命令行中将属性作为环境变量提供:
$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar
在前面的示例中,您最终在 Spring Environment
中设置my.name=test
。
同样的 JSON 也可以作为系统属性提供:
$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar
或者您可以使用命令行参数提供 JSON:
$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'
如果要部署到经典应用程序服务器,还可以使用名为java:comp/env/spring.application.json
的JNDI文件.
尽管JSON 中的null
值将添加到生成的属性源中,但PropertySourcesPropertyResolver
将null
属性视为缺失值。这意味着 JSON 不能用null
值覆盖来自低阶属性源的属性
2.3. 外部应用程序属性
当您的应用程序启动时,Spring Boot 将自动从以下位置查找并加载application.properties
和application.yaml
文件:
- 从类路径
- 类路径根
- 类路径
/config
包
- 从当前目录
- 当前目录
- 当前目录中的
/config
子目录 - 子目录的
/config
直接子目录
该列表按优先级排序(较低项目的值覆盖较早的项目)。加载文件中的文档被作为PropertySources
添加到 SpringEnvironment
中。
如果您不喜欢application
作为配置文件名,您可以通过指定spring.config.name
环境属性来切换到另一个文件名。例如,要查找myproject.properties
和myproject.yaml
文件,您可以按如下方式运行应用程序:
$ java -jar myproject.jar --spring.config.name=myproject
您还可以使用spring.config.location
environment 属性来引用显式位置。此属性接受以逗号分隔的一个或多个要检查的位置列表。
以下示例显示如何指定两个不同的文件:
$ java -jar myproject.jar --spring.config.location=\
optional:classpath:/default.properties,\
optional:classpath:/override.properties
如果位置是可选的并且您不介意它们不存在, 请使用前缀optional:
。
spring.config.name
, spring.config.location
, 和spring.config.additional-location
很早就用于确定必须加载哪些文件。它们必须定义为环境属性(通常是操作系统环境变量、系统属性或命令行参数)。
如果spring.config.location
包含目录(而不是文件),它们应该以/
结束. 在运行时,它们将附加在加载spring.config.name
之前生成的名称。spring.config.location
中指定的文件直接导入。
目录和文件位置值也被扩展以检查特定于配置文件的文件。例如,如果配置的spring.config.location
是classpath:myconfig.properties
,您还会发现它加载了适当的classpath:myconfig-<profile>.properties
文件。
在大多数情况下,您添加的每个spring.config.location
条目都将引用单个文件或目录。位置按照定义的顺序进行处理,后面的位置可以覆盖早期位置的值。
如果您有一个复杂的位置设置,并且您使用特定于配置文件的配置文件,您可能需要提供进一步的提示,以便 Spring Boot 知道应该如何对它们进行分组。位置组是所有被视为同一级别的位置的集合。例如,您可能希望对所有类路径位置进行分组,然后对所有外部位置进行分组。位置组中的项目应使用;
分隔。有关详细信息,请参阅“配置文件特定文件”部分中的示例。
使用spring.config.location
替换默认位置配置的位置。例如,如果spring.config.location
配置为optional:classpath:/custom-config/,optional:file:./custom-config/
,则考虑的完整位置集是:
optional:classpath:custom-config/
optional:file:./custom-config/
如果您更倾向于添加其他位置,而不是替换它们,您可以使用spring.config.additional-location
. 从其他位置加载的属性可以覆盖默认位置中的属性。例如,如果spring.config.additional-location
配置了 value optional:classpath:/custom-config/,optional:file:./custom-config/
,则考虑的完整位置集是:
optional:classpath:/;optional:classpath:/config/
optional:file:./;optional:file:./config/;optional:file:./config/*/
optional:classpath:custom-config/
optional:file:./custom-config/
这种搜索顺序允许您在一个配置文件中指定默认值,然后在另一个配置文件中选择性地覆盖这些值。您可以在默认位置之一(或您选择spring.config.name
的任何其他基本名称)中的application.properties
为您的应用程序提供默认值。然后可以在运行时使用位于自定义位置之一的不同文件覆盖这些默认值。
如果您使用环境变量而不是系统属性,大多数操作系统不允许使用句点分隔的键名,但您可以使用下划线代替(例如,SPRING_CONFIG_NAME
代替spring.config.name
)。有关详细信息,请参阅从环境变量绑定。
如果您的应用程序在 servlet 容器或应用程序服务器中运行,则可以使用 JNDI 属性(在 中java:comp/env
)或 servlet 上下文初始化参数来代替环境变量或系统属性,或者也可以使用环境变量或系统属性。
2.3.1.可选位置
默认情况下,当指定的配置数据位置不存在时,Spring Boot 会抛出一个ConfigDataLocationNotFoundException
并且你的应用程序不会启动。
如果你想指定一个位置,但你不介意它不总是存在,你可以使用optional:
前缀。您可以将此前缀与spring.config.location
和spring.config.additional-location
属性以及spring.config.import
声明一起使用。
例如,spring.config.import
值设为optional:file:./myconfig.properties
允许您的应用程序启动,即使myconfig.properties
文件丢失。
如果您想忽略所有ConfigDataLocationNotFoundExceptions
并始终继续启动您的应用程序,您可以使用该spring.config.on-not-found
属性。使用SpringApplication.setDefaultProperties(…)
方法设置为ignore
或与系统/环境变量一起使用。
2.3.2. 通配符位置
如果配置文件位置包含*
最后一个路径段的字符,则将其视为通配符位置。加载配置时会扩展通配符,以便同时检查直接子目录。当有多个配置属性来源时,通配符位置在 Kubernetes 等环境中特别有用。
例如,如果您有一些 Redis 配置和一些 MySQL 配置,您可能希望将这两个配置分开,同时要求它们都存在于一个application.properties
文件中。这可能会导致两个单独的application.properties
文件安装在不同的位置,例如/config/redis/application.properties
和/config/mysql/application.properties
. 在这种情况下,通配符位置config/*/
, 将导致两个文件都被处理。
默认情况下,Spring Boot 在默认搜索位置中包含config/*/
。这意味着将搜索 jar 之外/config
目录的所有子目录。
您可以自己将通配符位置与spring.config.location
andspring.config.additional-location
属性一起使用。
通配符位置必须仅包含一个*
并以*/
作为目录的搜索位置或*/<filename>
作为文件的搜索位置结尾。带有通配符的位置根据文件名的绝对路径按字母顺序排序。
通配符位置仅适用于外部目录。您不能在classpath:
位置中使用通配符。
2.3.3. 特定配置文件
除了application
属性文件,Spring Boot 还将尝试使用命名约定加载application-{profile}
配置文件。例如,如果您的应用程序激活了一个名为prod
并且使用 YAML 配置文件,那么application.yml
和application-prod.yml
都会被考虑。
特定配置文件的属性从与标准application.properties
相同的位置加载,特定配置文件总是覆盖非特定文件。如果指定了多个配置文件,则应用最后获胜策略。例如,如果spring.profiles.active
属性指定prod,live
配置文件,则 中的值application-prod.properties
可以被application-live.properties
中的值覆盖。
最后获胜的策略适用于位置组级别。 spring.config.location
配置为classpath:/cfg/,classpath:/ext/
不会与配置为classpath:/cfg/;classpath:/ext/
覆盖规则相同。例如,继续上面的prod,live
示例,我们可能有以下文件:
/cfg
application-live.properties
/ext
application-live.properties
application-prod.properties
当spring.config.location
配置为classpath:/cfg/,classpath:/ext/
,在所有/ext
文件之前处理所有/cfg
文件时:
-
/cfg/application-live.properties
-
/ext/application-prod.properties
-
/ext/application-live.properties
当我们改为classpath:/cfg/;classpath:/ext/
(使用;
分隔符)时,我们会将/cfg
和/ext
在同一级别处理:
/ext/application-prod.properties
/cfg/application-live.properties
/ext/application-live.properties
Environment
有一组默认配置文件(默认情况下,[default]
),如果没有设置活动配置文件,则使用这些配置文件。换句话说,如果没有显式激活配置文件,则考虑来自application-default
的属性。
属性文件只加载一次。如果您已经直接导入了特定配置的属性文件,则不会再次导入它。
2.3.4. 导入附加配置
应用程序属性可以使用spring.config.import
属性从其他位置导入更多配置数据。导入在发现时就被处理,并被视为紧接在声明导入的文件下方插入的附加文件。
例如,您的application.properties
文件中可能包含以下内容:
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
这将触发当前目录中dev.properties
文件的导入(如果存在这样的文件)。dev.properties
导入的值将优先于触发导入的文件。在上面的示例中,dev.properties
可以重新定义spring.application.name
为不同的值。
无论声明多少次,导入都只会导入一次。在 properties/yaml 文件中的单个文档中定义导入的顺序无关紧要。例如,下面的两个示例产生相同的结果:
spring.config.import=my.properties
my.property=value
my.property=value
spring.config.import=my.properties
在上述两个示例中,my.properties
文件中的值将优先于触发其导入的文件。
可以在一个spring.config.import
键下指定多个位置。位置将按照定义的顺序进行处理,以后的导入优先。
适当时,还会考虑导入特定于配置文件的变体。上面的示例将同时导入my.properties
任何my-<profile>.properties
变体。
Spring Boot 包含可插入的 API,允许支持各种不同的位置地址。默认情况下,您可以导入 Java 属性、YAML 和“配置树”。第三方 jar 可以提供对其他技术的支持(不需要文件是本地的)。例如,您可以想象配置数据来自外部存储,例如 Consul、Apache ZooKeeper 或 Netflix Archaius。如果您想支持自己的位置,请参阅包中的ConfigDataLocationResolver
和ConfigDataLoader
类org.springframework.boot.context.config
。
2.3.5. 导入无扩展名文件
某些云平台无法为卷挂载文件添加文件扩展名。要导入这些无扩展名文件,您需要给 Spring Boot 一个提示,以便它知道如何加载它们。您可以通过将扩展提示放在方括号中来做到这一点。
例如,假设您有一个/etc/config/myconfig
文件要导入为 yaml。您可以使用以下命令从您的application.properties
导入它:
spring.config.import=file:/etc/config/myconfig[.yaml]
2.3.6. 使用配置树
在云平台(例如 Kubernetes)上运行应用程序时,您通常需要读取平台提供的配置值。将环境变量用于此类目的并不少见,但这可能有缺点,尤其是在值应该保密的情况下。
作为环境变量的替代方案,许多云平台现在允许您将配置映射到挂载的数据卷。例如,Kubernetes 可以同时卷挂载ConfigMaps
和Secrets
.
可以使用两种常见的卷挂载模式:
- 单个文件包含一组完整的属性(通常写为 YAML)。
- 多个文件被写入目录树,文件名成为“键”,内容成为“值”。
对于第一种情况,您可以使用spring.config.import
方法直接导入 YAML 或属性文件。对于第二种情况,您需要使用configtree
: 使用前缀,以便 Spring Boot 知道它需要将所有文件公开为属性。
例如,假设 Kubernetes 挂载了以下卷:
etc/
config/
myapp/
username
password
该username
文件的内容将是一个配置值,其password
内容将是一个秘密。
要导入这些属性,您可以将以下内容添加到您的application.properties
或application.yaml
文件中:
spring.config.import=optional:configtree:/etc/config/
然后,您可以通常的方式访问或注入来自Environment
的myapp.username
和myapp.password
属性。
配置树下的文件夹构成属性名称。在上面的示例中,要访问username
和password
的属性,您可以设置spring.config.import
为optional:configtree:/etc/config/myapp
。
**带有点符号的文件名也被正确映射。例如,在上面的示例中,在/etc/config
中名为myapp.username
的文件将在Environment
中插入myapp.username
. **
根据预期的内容 , 配置树值可以绑定到字符串String
和byte[]
类型。
如果您要从同一个父文件夹导入多个配置树,则可以使用通配符快捷方式。任何以/*/
结尾的configtree:
位置都会将所有直接子项导入为配置树。
例如,给定以下卷:
etc/
config/
dbconfig/
db/
username
password
mqconfig/
mq/
username
password
您可以设置configtree:/etc/config/*/
用作导入位置:
spring.config.import=optional:configtree:/etc/config/*/
这将添加db.username
、db.password
、mq.username
和mq.password
属性。
使用通配符加载的目录按字母顺序排序。如果您需要不同的订单,则应将每个位置列为单独的导入
配置树也可用于 Docker secret。当 Docker swarm 服务被授予对密钥的访问权限时,该密钥将被挂载到容器中。例如,如果一个名为db.password
的 secret挂载在/run/secrets/
位置,您可以使用以下命令将db.password
提供给 Spring environment:
spring.config.import=optional:configtree:/run/secrets/
2.3.7. 属性占位符
application.properties
和application.yml
中的值在使用时会通过现有Environment
进行过滤,因此您可以参考以前定义的值(例如,来自系统属性或环境变量)。标准的${name}
属性占位符语法可以在值内的任何地方使用。属性占位符还可以使用 a:
来指定默认值,以将默认值与属性名称分开,例如${name:default}
。
以下示例显示了使用带默认值和不带默认值的占位符:
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
假设username
属性尚未在其他地方设置,app.description
则值为MyApp is a Spring Boot application written by Unknown
。
您应该始终使用其规范形式(kebab-case 仅使用小写字母)引用占位符中的属性名称。这将允许 Spring Boot 使用与宽松绑定 @ConfigurationProperties
时相同的逻辑。
例如,${demo.item-price}
将从application.properties
文件中以及从系统环境变量DEMO_ITEMPRICE
中提取demo.item-price
和demo.itemPrice
形成表格。如果你改用${demo.itemPrice}
,就不会考虑demo.item-price
和DEMO_ITEMPRICE
了。
您还可以使用此技术创建现有 Spring Boot 属性的“短”变体。有关详细信息,请参阅howto.html 操作方法。**
2.3.8. 使用多文档文件
Spring Boot 允许您将单个物理文件拆分为多个独立添加的逻辑文档。文档从上到下按顺序处理。后面的文档可以覆盖早期文档中定义的属性。
对于application.yml
文件,可以使用标准的 YAML 多文档语法。三个连续的连字符代表一个文档的结束,以及下一个文档的开始。
例如,以下文件有两个逻辑文档:
spring:
application:
name: "MyApp"
---
spring:
application:
name: "MyCloudApp"
config:
activate:
on-cloud-platform: "kubernetes"
对于application.properties
文件,特殊#---
注释用于标记文档拆分:
spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
属性文件分隔符不能有任何前导空格,并且必须正好有三个连字符。分隔符前后的行不能是注释。
多文档属性文件通常与激活属性结合使用,例如spring.config.activate.on-profile
. 有关详细信息,请参阅下一节。
不能使用@PropertySource
or@TestPropertySource
注解加载多文档属性文件。
2.3.9. 激活属性
有时仅在满足某些条件时才激活给定的一组属性。例如,您可能拥有仅在特定配置文件处于活动状态时才使用其相关的属性。
您可以使用spring.config.activate.*
有条件地激活属性配置文件。
可以使用以下激活属性:
属性名 | 备注 |
---|---|
on-profile |
要使文档处于活动状态,必须匹配的配置文件表达式。 |
on-cloud-platform |
CloudPlatform 必须检测到文档才能处于活动状态。 |
例如,以下内容指定了当在 Kubernetes 上运行时, 并且仅当“prod”或“staging”配置文件处于活动状态时第二个文档才处于活动状态:
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
2.4. 加密属性
Spring Boot 不提供对加密属性值的任何内置支持,但是,它确实提供了修改 Spring Environment
中包含的值所需的挂钩点。EnvironmentPostProcessor
接口可以允许我们在应用程序启动之前对Environment
进行操作。有关详细信息,请参阅howto.html。
如果您需要一种安全的方式来存储凭据和密码,Spring Cloud Vault项目可以支持在HashiCorp Vault中存储外部化配置。
2.5. 使用 YAML
YAML是 JSON 的超集,因此是一种用于指定分层配置数据的便捷格式。只要您的类路径中有SnakeYAML库,SpringApplication
类就会自动支持 YAML 作为属性的替代方案。
如果您使用“Starters”,SnakeYAML 会自动由spring-boot-starter
提供.
2.5.1. 将 YAML 映射到属性
YAML 文档需要从其分层格式转换为可与 Spring Environment
一起使用的平面结构。例如,考虑以下 YAML 配置文档:
environments:
dev:
url: "https://dev.example.com"
name: "Developer Setup"
prod:
url: "https://another.example.com"
name: "My Cool App"
为了从Environment
中访问这些属性,它们将被展平如下:
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 列表也需要展平。它们表示为带有[index]
游标引用的属性键。例如,考虑以下 YAML:
my:
servers:
- "dev.example.com"
- "another.example.com"
前面的示例将转换为以下属性:
my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用该[index]
符号的属性可以绑定到 使用 Spring Boot 的Binder
类的Java List
或Set
对象。有关更多详细信息,请参阅下面的“类型安全配置属性”部分。
YAML 文件不能使用@PropertySource
或@TestPropertySource
注释加载。因此,如果您需要以这种方式加载值,则需要使用属性文件。
2.5.2. 直接加载 YAML
Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。YamlPropertiesFactoryBean
将YAML 加载为Properties
并通过YamlMapFactoryBean
将YAML 加载为Map
.
如果您想将 YAML 作为 Spring PropertySource
加载,也可以使用YamlPropertySourceLoader
类。
2.6. 配置随机值
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*
语法是OPEN value (,max) CLOSE
, OPEN,CLOSE
可以是任何字符,value,max
是整数。如果设置了max
,则value
为最小值并且max
为最大值(不包括)。
2.7. 配置系统环境属性
Spring Boot 支持为环境属性设置前缀。如果系统环境由具有不同配置要求的多个 Spring Boot 应用程序共享,这将很有用。系统环境属性的前缀可以直接设置在SpringApplication
中.
例如,如果您将前缀设置为input
,则诸如在系统环境中remote.timeout
的属性也将被解析为input.remote.timeout
2.8. 类型安全的配置属性
使用@Value("${property}")
注解来注入配置属性有时会很麻烦,尤其是当您使用多个属性或者您的数据本质上是分层的时候。Spring Boot 提供了一种使用属性的替代方法,可以让强类型 bean 管理和验证应用程序的配置。
另请参见类型安全配置属性和类型安全配置属性之间的区别@Value
。
2.8.1. JavaBean 属性绑定
可以绑定声明标准 JavaBean 属性的 bean,如下例所示:
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
// getters / setters...
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
// getters / setters...
}
}
前面的 POJO 定义了以下属性:
my.service.enabled
,默认值为false
。my.service.remote-address
, 具有可以从String
强制转换的类型。my.service.security.username
,具有嵌套的“security”对象,其名称由属性的名称确定。特别是,那里根本没有使用该类型,并且可能已经使用了SecurityProperties
.my.service.security.password
.my.service.security.roles
,String
集合默认为USER
.
映射到Spring Boot 中可用的@ConfigurationProperties
类的属性,一般通过属性文件、YAML 文件、环境变量和其他机制配置,是公共 API,但类本身的访问器(getter/setter)并不意味着直接使用.
这种安排依赖于默认的空构造函数,并且 getter 和 setter 通常是强制性的,因为绑定是通过标准 Java Beans 属性描述符进行的,就像在 Spring MVC 中一样。在以下情况下可以省略 setter:
- 集合,只要它们被初始化,就需要一个 getter 但不一定需要 setter,因为它们可以被 binder 改变。
- 可以通过索引(通常使用 YAML)或使用单个逗号分隔值(属性)来访问的集合和数组。如果是数组,setter 是强制性的。我们建议始终为此类类型添加 setter。如果您初始化了一个集合,请确保它不是不可变的(如前面的示例中所示)。
- 如果嵌套 POJO 属性已初始化(如前面示例中
Security
的字段),则不需要 setter。如果您希望 binder 使用其默认构造函数动态创建实例,则需要一个 setter。
有些人使用 Project Lombok 来自动添加 getter 和 setter。那么要确保 Lombok 不会为此类类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。
最后,spring boot只考虑标准 Java Bean 属性,不支持对静态属性的绑定。
2.8.2. 构造函数绑定
上一节中的示例可以以不可变的方式重写,如下例所示:
@ConstructorBinding
@ConfigurationProperties("my.service")
public class MyProperties {
// fields...
public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
// getters...
public static class Security {
// fields...
public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
// getters...
}
}
在此设置中,@ConstructorBinding
注解用于指定使用构造函数绑定。这意味着绑定器将期望找到具有您希望绑定的参数的构造函数。如果您使用的是 Java 16 或更高版本,则可以将构造函数绑定与记录一起使用。在这种情况下,除非您的记录有多个构造函数,否则无需使用@ConstructorBinding
.
类的嵌套成员(Security
例如上面的示例)也通过@ConstructorBinding
将其使用构造函数进行绑定。
可以使用@DefaultValue
为构造函数参数指定默认值,或者在使用 Java 16 或更高版本时,使用记录组件指定默认值。将应用转换服务将String
值强制为缺失属性的目标类型。
参考前面的示例,如果没有属性绑定到Security
,则MyProperties
实例将包含一个值为null
的security
。如果您希望即使没有绑定任何属性也返回一个非空Security
实例,您可以使用空@DefaultValue
注释来执行此操作:
public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
要使用构造函数绑定,必须使用@EnableConfigurationProperties
或配置属性扫描启用类。您不能对用常规 Spring 机制创建的 bean 使用构造函数绑定(例如@Component
bean、使用@Bean
注解创建的 bean 或使用 @Import
加载的 bean)
如果你的类有多个构造函数,你也可以@ConstructorBinding
直接在应该绑定的构造函数上使用。
不推荐对java.util.Optional
类型使用@ConfigurationProperties
,因为它主要用作返回类型。因此,它不太适合配置属性注入。为了与其他类型的属性保持一致,如果您确实声明了一个Optional
属性并且它没有值,null
而不是一个空的Optional
将被绑定。
2.8.3. 启用 @ConfigurationProperties 注解
Spring Boot 提供了绑定@ConfigurationProperties
类型并将它们注册为 bean 的基础设施。您可以按类启用配置属性,也可以启用与组件扫描类似的配置属性扫描。
有时,带有@ConfigurationProperties
注解的类可能不适合扫描,例如,如果您正在开发自己的自动配置或希望有条件地启用它们。在这些情况下,请使用@EnableConfigurationProperties
注解指定要处理的类型列表。这可以在任何@Configuration
类上完成,如下例所示:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {
}
如果要使用配置属性扫描,请将@ConfigurationPropertiesScan
注解添加到您的应用程序。通常,它被添加到带有@SpringBootApplication
注解的主应用程序类中,但它也可以添加到任何@Configuration
类中。默认情况下,扫描将从声明注解的类的包中进行。如果要定义要扫描的特定包,可以按照以下示例进行操作:
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {
}
当@ConfigurationProperties
使用配置属性扫描或通过 @EnableConfigurationProperties
注册 bean时,bean 有一个约定命名方式:<prefix>-<fqn>
,其中<prefix>
是@ConfigurationProperties
注解中指定的环境键前缀,<fqn>
是 bean 的完全限定名称。如果注解不提供任何前缀,则仅使用 bean 的完全限定名称。上例中的 bean 名称是com.example.app-com.example.app.SomeProperties
.
我们建议@ConfigurationProperties
只处理环境,特别是不要从上下文中注入其他 bean。对于极端情况,可以使用 setter 注入或*Aware
框架提供的任何接口(例如,EnvironmentAware
如果您需要访问Environment
. 如果您仍想使用构造函数注入其他 bean,则配置属性 bean 必须添加@Component
注解并使用基于 JavaBean 的属性绑定。
2.8.4. 使用 @ConfigurationProperties 注解
这种配置风格特别适用于SpringApplication
外部 YAML 配置,如下例所示:
my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"
要@ConfigurationProperties
bean生效,您可以像注入任何其他 bean 一样注入它们,如以下示例所示:
@Service
public class MyService {
private final SomeProperties properties;
public MyService(SomeProperties properties) {
this.properties = properties;
}
public void openConnection() {
Server server = new Server(this.properties.getRemoteAddress());
server.start();
// ...
}
// ...
}
使用@ConfigurationProperties
还允许您生成元数据文件,IDE 可以使用这些文件为您自己的键提供自动完成功能。详情见附录。
2.8.5. 第三方配置
除了@ConfigurationProperties
用于注释类之外,您还可以在公共@Bean
方法上使用它。当您想要将属性绑定到您无法控制的第三方组件时,这样做会特别有用。
要从Environment
属性配置 bean,请添加@ConfigurationProperties
到其 bean 注册,如以下示例所示:
@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {
@Bean
@ConfigurationProperties(prefix = "another")
public AnotherComponent anotherComponent() {
return new AnotherComponent();
}
}
使用前缀another
定义的任何 JavaBean 属性都以类似于前面SomeProperties
示例的方式映射到AnotherComponent
bean。
2.8.6. 宽松绑定
Spring Boot 使用一些宽松的规则将Environment
属性绑定到@ConfigurationProperties
bean,因此Environment
属性名称和 bean 属性名称之间不需要完全匹配。这很有用的常见示例包括破折号分隔的环境属性(例如,将context-path
绑定到contextPath
)和大写环境属性(例如,PORT
绑定到port
)。
例如,考虑以下@ConfigurationProperties
类:
@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
使用上述代码,可以使用以下属性名称:
属性 | 备注 |
---|---|
my.main-project.person.first-name |
Kebab示例,推荐用于.properties 和.yml 文件。 |
my.main-project.person.firstName |
标准驼峰式语法。 |
my.main-project.person.first_name |
下划线表示法,这是在文件中使用的另一种.properties 格式.yml 。 |
MY_MAINPROJECT_PERSON_FIRSTNAME |
大写格式,在使用系统环境变量时推荐使用。 |
注释prefix
的值必须是 kebab 大小写(小写并用-
分隔,例如my.main-project.person
)。
属性来源 | 格式 | 列表 |
---|---|---|
属性文件 | Camel case、kebab case 或下划线表示法 | 使用逗号分隔值或[ ] 的标准列表语法 |
YAML 文件 | Camel case、kebab case 或下划线表示法 | 标准 YAML 列表语法或逗号分隔值 |
环境变量 | 下划线作为分隔符的大写格式(请参阅从环境变量绑定)。 | 下划线包围的数值(请参阅从环境变量绑定) |
系统属性 | Camel case、kebab case 或下划线表示法 | 使用逗号分隔值或[ ] 的标准列表语法 |
我们建议尽可能以小写的 kebab 格式存储属性,例如my.person.first-name=Rod
.
绑定Map
绑定到Map
属性时,您可能需要使用特殊的括号表示法,以便key
保留原始值。如果键没有被[]
包围,则任何不是字母数字、-
或.
的字符都将被删除。
例如,考虑将以下属性绑定到 Map<String,String>
:
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
对于 YAML 文件,括号需要用引号括起来才能正确解析键。
上面的属性将/key1
,/key2
并key3
作为Map的键绑定到一个Map
中。key3
的斜线已被删除,因为它没有被方括号包围。
当绑定到标量值时,.
后的键不需要用[]
. 标量值包括枚举和java.lang
包中除Object
的其他类型. 绑定a.b=c
到Map<String, String>
将保留键中的.
并返回带有{"a.b"="c"}
条目的 Map 。对于任何其他类型,如果您的key
包含.
,你需要使用括号符号. 例如,绑定a.b=c
到Map<String, Object>
将返回带有{"a"={"b"="c"}}
条目的 Map,而[a.b]=c
将返回带有 {"a.b"="c"}
条目的 Map。
从环境变量绑定
大多数操作系统对环境变量的名称施加了严格的规则。例如,Linux shell 变量只能包含字母(a
-z
或A
- Z
)、数字(0
- 9
)或下划线字符(_
)。按照惯例,Unix shell 变量的名称也将大写。
Spring Boot 宽松的绑定规则尽可能地与这些命名限制兼容。
要将规范形式的属性名称转换为环境变量名称,您可以遵循以下规则:
- 将点 (
.
) 替换为下划线 (_
)。 - 删除所有破折号 (
-
)。 - 转换为大写。
例如,配置属性spring.main.log-startup-info
将是一个名为SPRING_MAIN_LOGSTARTUPINFO
的系统环境变量
绑定到对象列表时也可以使用环境变量。如果要绑定到 List
,元素编号应在变量名中用下划线括起来。
例如,配置属性my.service[0].other
将表示为MY_SERVICE_0_OTHER
.
2.8.7. 合并复杂类型
当列表配置在多个位置时,重写将通过替换整个列表来实现。
例如,假设一个MyPojo
对象具有默认值null
的name
和description
属性。以下示例展示了来自MyProperties
的MyPojo
对象的列表:
@ConfigurationProperties("my")
public class MyProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
使用以下配置:
my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
如果dev
配置文件未激活,则MyProperties.list
如先前定义的那样,包含一个MyPojo
条目。但是,如果启用了dev
配置文件,则list
仍然只包含一个条目(名称为my another name
,描述为null
)。此配置不会将第二个MyPojo
实例添加到列表中,并且不会合并项目。
当在多个配置文件中指定 List
时,使用具有最高优先级的一个(并且仅使用那一个)。考虑以下示例:
my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
在前面的示例中,如果dev
配置文件处于活动状态,则MyProperties.list
包含一个 MyPojo
条目(名称为my another name
,描述为null
)。对于 YAML配置来收,逗号分隔的列表和 YAML 列表都可用于完全覆盖list的内容。
对于Map
属性,您可以绑定从多个来源提取的属性值。但是,对于多个源中的相同属性,将使用具有最高优先级的属性。以下示例展示了MyProperties
的集合Map<String, MyPojo>
:
@ConfigurationProperties("my")
public class MyProperties {
private final Map<String, MyPojo> map = new LinkedHashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
考虑以下配置:
my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
如果dev
配置文件未激活,则MyProperties.map
包含一个带有key1
键的条目(名称为my name 1
,描述为my description 1
)。但是,如果启用了dev
配置文件,则map
包含两个带有键的条目key1
(名称为dev name 1
,描述为my description 1
)和key2
(名称为dev name 2
,描述为dev description 2
)。
上述合并规则适用于所有属性源的属性,而不仅仅是文件。
2.8.8. 属性转换
Spring Boot 在将外部属性配置绑定到@ConfigurationProperties
bean时,会尝试将外部应用程序属性强制转换为正确的类型。如果您需要自定义类型转换,您可以提供一个ConversionService
bean(一个名为conversionService
的 bean )或自定义属性编辑器(通过一个CustomEditorConfigurer
bean)或自定义Converters
(带有@ConfigurationPropertiesBinding
定义的 bean )。
由于此 bean 在应用程序生命周期的早期被请求,因此请限制您的ConversionService
正在使用的依赖项。通常,您需要的任何依赖项可能不会在创建时完全初始化。如果配置键强制不需要它并且仅依赖于使用@ConfigurationPropertiesBinding
限定的自定义转换器, 你可能需要重命名你自定义的ConversionService
.
Duration转换
Spring Boot 专门支持设置持续时间。如果设置了java.time.Duration
类型的属性,则应用程序属性中的以下格式可用:
- 常规
long
类型表示(使用毫秒作为默认单位,除非使用@DurationUnit
指定单位) - 使用
java.time.Duration
的标准 ISO-8601 格式 - 一种更易读的格式,其中值和单位耦合(
10s
意味着 10 秒)
考虑以下示例:
@ConfigurationProperties("my")
public class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
// getters / setters...
}
要指定 30 秒的会话超时,30
,PT30S
和30s
都是等效的。500ms 的超时读取可以用以下任何一种形式指定:500
、PT0.5S
和500ms
。
您还可以使用任何受支持的单位。这些是:
ns
纳秒us
微秒ms
毫秒s
秒钟m
分钟h
小时d
天
默认单位是毫秒,可以使用@DurationUnit
上面示例中的说明进行覆盖。
如果您更喜欢使用构造函数绑定,也可以设置相同的属性,如下例所示:
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
// fields...
public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
@DefaultValue("1000ms") Duration readTimeout) {
this.sessionTimeout = sessionTimeout;
this.readTimeout = readTimeout;
}
// getters...
}
如果您要升级到Long
属性,如果它不是毫秒请确保定义单位(使用@DurationUnit
)。这样做提供了一个透明的升级路径,同时支持更丰富的格式。
Period转换
除了持续时间,Spring Boot 还可以使用java.time.Period
类型。以下格式可用于应用程序属性:
- 常规
int
类型表示(使用天作为默认单位,除非使用@PeriodUnit
指定单位) - 使用
java.time.Period
的标准 ISO-8601 格式 - 一种更简单的格式,其中值和单位对耦合(
1y3d
表示 1 年和 3 天)
简单格式支持以下单位:
y
年m
月w
周d
天
该java.time.Period
类型从不实际存储周数,它是一个表示“7 天”的快捷方式。
DataSize转换
Spring Framework 有一个DataSize
值类型,它以字节为单位表示大小。如果设置了DataSize
类型属性,则应用属性中的以下格式可用:
- 常规
long
类型表示(使用字节作为默认单位,除非使用@DataSizeUnit
指定单位) - 一种更易读的格式,其中值和单位是耦合的(
10MB
意味着 10 兆字节)
考虑以下示例:
@ConfigurationProperties("my")
public class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
// getters/setters...
}
指定缓冲区大小为 10 兆字节,10
并且10MB
是等价的。可以将 256 字节的大小阈值指定为256
或256B
。
您还可以使用任何受支持的单位。这些是:
B
字节KB
千字节MB
兆字节GB
千兆字节TB
太字节
默认单位是字节,可以使用@DataSizeUnit
上面示例中的说明进行覆盖。
如果您更喜欢使用构造函数绑定,则可以公开相同的属性,如下例所示:
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
// fields...
public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
@DefaultValue("512B") DataSize sizeThreshold) {
this.bufferSize = bufferSize;
this.sizeThreshold = sizeThreshold;
}
// getters...
}
如果要升级Long
属性,如果单位不是字节请确保定义单位(使用@DataSizeUnit
)。这样做提供了一个透明的升级路径,同时支持更丰富的格式。
2.8.9. @ConfigurationProperties 验证
每当使用 Spring 的@Validated
注解时,Spring Boot 都会尝试验证@ConfigurationProperties
类。您可以直接在配置类上使用 JSR-303javax.validation
约定注解。为此,请确保您的类路径中存在兼容的 JSR-303 实现,然后将约束注释添加到您的字段,如以下示例所示:
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
// getters/setters...
}
在创建使用 @Validated
注解配置属性的@Bean
方法也可以触发验证。
为确保始终为嵌套属性触发验证,即使未找到任何属性,关联字段也必须使用 @Valid
注解。以下示例基于前面的MyProperties
示例构建:
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
// getters/setters...
public static class Security {
@NotEmpty
private String username;
// getters/setters...
}
}
您还可以通过创建一个名为configurationPropertiesValidator
的spring Validator
. @Bean
方法应该声明为static
。配置属性验证器是在应用程序生命周期的早期创建的,将@Bean
方法声明为静态可以在无需实例化@Configuration
类的情况下创建 bean 。这样做可以避免早期实例化可能导致的任何问题。
spring-boot-actuator
模块包括一个暴露所有@ConfigurationProperties
bean 的端点。可以将您的 Web 浏览器指向/actuator/configprops
或使用等效的 JMX 端点。有关详细信息,请参阅“生产就绪功能”部分。
2.8.10. @ConfigurationProperties 与 @Value
@Value
注解是一个核心容器特性,它不提供与类型安全配置属性相同的特性。下表总结了@ConfigurationProperties
和@Value
支持的功能:
功能 | @ConfigurationProperties |
@Value |
---|---|---|
宽松绑定 | 支持 | 有限(见下文注释) |
元数据支持 | 支持 | 不支持 |
SpEL 表达式 |
不支持 | 支持 |
如果您确实想使用@Value
,我们建议您使用其规范形式(kebab-case 仅使用小写字母)引用属性名称。这将允许 Spring Boot 使用与宽松绑定@ConfigurationProperties
时相同的逻辑。
例如,@Value("${demo.item-price}")
将从application.properties
文件中以及从系统环境变量DEMO_ITEMPRICE
中提取demo.item-price
和demo.itemPrice
。如果你改用了@Value("${demo.itemPrice}")
,就不会考虑demo.item-price
和DEMO_ITEMPRICE
了。
如果您为自己的组件定义了一组配置属性,我们建议您将它们分组到一个使用了@ConfigurationProperties
注解的POJO 中. 这样做将为您提供结构化的、类型安全的对象,您可以将其注入到您自己的 bean 中。
在解析这些文件中的SpEL
表达式并填充到environment时,不会处理来自应用程序属性文件的表达式。但是,可以在@Value
中编写SpEL
表达式。如果应用程序属性文件中的属性值是 SpEL
表达式,则在通过使用 @Value
时将对其进行求值
评论区