当前位置: 代码迷 >> 综合 >> Spring Boot Features------2. Externalized Configuration文档翻译(part2)
  详细解决方案

Spring Boot Features------2. Externalized Configuration文档翻译(part2)

热度:12   发布时间:2023-12-20 23:21:07.0

2. Externalized Configuration

本节将深入讨论Spring Boot的细节。在这里,您可以了解您可能希望使用和自定义的关键特性。

SpringApplication类提供了一种方便的方式来引导从main()方法启动的Spring application。在很多情况下,你可以委托给静态的SpringApplication.ru()方法,如下面的例子所示(该主方法位于Spring Boot启动类中):

public static void main(String[] args) {
    SpringApplication.run(MySpringConfiguration.class, args); 
}

当你的应用程序启动,你应该看到类似以下输出:

默认情况下,会显示信息日志消息,包括一些相关的启动细节,比如启动应用程序的用户。如果您需要除INFO之外的日志级别,您可以自定义设置日志级别。

1.1. Startup Failure

如果您的应用程序启动失败,注册的故障分析器将有机会提供专用的错误消息和修复问题的具体操作。例如,如果您在端口8080上启动一个web应用程序,并且该端口已经在使用,您应该看到类似于以下消息:

***************************
APPLICATION FAILED TO START
***************************
Description:
Embedded servlet container failed to start. Port 8080 was already in use.Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

Spring Boot提供了许多FailureAnalyzer实现,您可以添加自己的实现类。

如果没有故障分析程序能够处理异常,您任然可以显示完整的条件报告,以便更好地理解出错的原因。为此,您需要为
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener启用debug属性或是启用debug级别日志。

如果没有故障分析程序能够处理异常,您仍然可以显示完整的条件报告,以便更好地理解出错的原因。为此,您需要为ConditionEvaluationReportLoggingListener启用debug属性或启用debug日志级别。

例如,如果您使用java -jar运行您的应用程序,您可以启用debug属性,如下所示:

$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug

1.2. Lazy Initialization

SpringApplication允许应用程序被延迟初始化。当启用延迟初始化时,bean是根据需要而不是在应用程序启动期间创建的。因此,启用延迟初始化可以减少应用程序启动所需的时间。在web应用程序中,启用延迟初始化将导致在接收到HTTP请求之前不会初始化许多与web相关的bean。

延迟初始化的一个缺点是,它会延迟应用程序问题的发现。如果错误配置的bean是延迟初始化的,那么在启动期间将不会出现故障,只有当这个Bean被初始化时才会出现问题。另外还必须注意确保JVM有足够的内存来容纳应用程序的所有bean,而不仅仅是那些在启动期间初始化的bean。由于这些原因,默认情况下不启用延迟初始化,建议在启用延迟初始化之前对JVM堆大小进行微调。

可以使用SpringApplicationBuilder的lazyInitialization方法或是SpringApplication的setLazyInitialization 方法以编程的方式启用延迟初始化,也可以使用spring.main.lazy-initialization配置属性来启用延迟初始化

spring.main.lazy-initialization=true

如果您希望禁用某些bean的延迟初始化,同时对应用程序的其余部分使用延迟初始化,那么可以使用@Lazy(false)注释显式地将它们的延迟属性设置为false。

1.3. Customizing the Banner

可以通过在类路径中添加一个banner.txt文件或设置spring.banner.location定位此类文件的位置,来更改在start up中打印的banner。如果文件的编码不是UTF-8,可以设置spring.banner.charset。除了文本文件,您还可以添加一个banner.gif、banner.jpg或banner.png图像文件到您的类路径或设置spring.banner.image位置属性。图像被转换成ASCII艺术表现形式,并打印在任何文本banner上。
在你的banner.txt文件中,你可以使用以下任何一个占位符:

如果希望以编程方式生成横幅,可以使用SpringApplication.setBanner(…)方法。使用org.springframework.boot.Banner接口实现您自己的printBanner()方法。

你也可以使用spring.main.banner-mode属性配置是否必须将banner输出到控制台(console),输出到日志文件(log)或是直接关闭banner生成(off)。

打印出来的banner被注册为一个单例bean,名字如下:springBootBanner。

1.4. Customizing SpringApplication

如果默认的SpringApplication类不能满足您的要求,你可以在本地创建一个实例化对象并进行配置。比如您想关闭banner的打印,可以做以下更改:

public static void main(String[] args) {
     
SpringApplication app = new SpringApplication(MySpringConfiguration.class); 	app.setBannerMode(Banner.Mode.OFF);app.run(args); 
}

传递给SpringApplication的构造函数参数是Spring Bean的配置源。在大多数情况下,这些是对@Configuration类的引用,但是它们也可以是对XML配置或应该扫描的包的引用。

也可以通过使用application.properties属性文件 来配置SpringApplication。有关详细信息,请参阅SpringApplicationJavadoc。

1.5. Fluent Builder API

如果您需要构建一个ApplicationContext层次结构(具有父/子关系的多个上下文),或者您更喜欢使用“fluent”构建器API,那么您可以使用SpringApplicationBuilder。

SpringApplicationBuilder允许您将多个方法调用链接在一起,并包含允许您创建层次结构的父方法和子方法,如下面的示例所示(在启动类的主方法中构建):

new SpringApplicationBuilder().sources(Parent.class).child(Application.class) 
.bannerMode(Banner.Mode.OFF) 
.run(args);

创建ApplicationContext层次结构时存在一些限制。例如,Web组件必须包含在子上下文中,并且父上下文和子上下文都使用相同的环境。有关详细信息,请参阅SpringApplicationBuilder Javadoc。

1.6. Application Events and Listeners

除了通常的Spring框架事件(如ContextRefreshedEvent)之外,SpringApplication还发送一些附加的应用程序事件。

有些事件实际上是在创建ApplicationContext之前触发的,因此您不能用@Bean注册listener。使用SpringApplication.addListeners(…?)或SpringApplicationBuilder.listeners(…?)方法
注册它们。

无论应用程序是如何被创建,你都希望能自动注册这些listener,那么你可以添加一个文件
META-INF/spring.factories到您的项目中,并通过使用org.springframework.context.ApplicationListener键来引用您的listener,如下例所示:

org.springframework.context.ApplicationListener=com.example.project.MyListener

当应用程序运行时,应用程序事件按以下顺序发送:
ApplicationStartingEvent在运行开始时发送,但在任何处理之前发送,侦听器和初始化器的注册除外。
当要在上下文中使用的环境已知但在上下文创建之前,将发送ApplicationEnvironmentPreparedEvent。
ApplicationContextInitializedEvent是在准备ApplicationContext和调用applicationcontextinitialalizer(但在加载任何bean定义之前)时发送的。
ApplicationPreparedEvent在启动刷新之前,但在加载bean定义之后发送。
ApplicationStartedEvent在上下文刷新之后,但在调用任何应用程序和命令行运行程序之前发送。
在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent。它表明应用程序已经准备好为请求提供服务。
如果启动时出现异常,则发送ApplicationFailedEvent。

您通常不需要使用应用程序事件,但是知道它们的存在是很方便的。在内部,Spring Boot使用事件来处理各种任务。

应用程序事件是使用Spring框架的事件发布机制发送的。此机制的一部分是确保在任何子上下文中发布给侦听器的事件也会发布给父上下文中的侦听器。因此,如果您的应用程序使用了SpringApplication实例的层次结构,则侦听器可能会接收同一类型应用程序事件的多个实例。

为了让侦听器能够区分其上下文的事件和子上下文的事件,它应该请求将其应用程序上下文注入,然后将注入的上下文与事件的上下文进行比较。上下文可以通过实现ApplicationContextAware 注入,如果listener 是bean,则可以通过使用@Autowired注入。

1.7. Web Environment

一个SpringApplication试图根据你的需求创建正确类型的ApplicationContext。用于确定WebApplicationType的算法相当简单:

如果存在Spring MVC,则会创建使用
AnnotationConfigServletWebServerApplicationContext

如果Spring MVC不存在,而Spring WebFlux存在,则会创建使用
AnnotationConfigReactiveWebServerApplicationContext

否则,则会创建使用AnnotationConfigApplicationContext

这意味着,如果您在同一个应用程序中使用Spring MVC和来自Spring WebFlux的新WebClient,那么默认情况下将使用Spring MVC。您可以通过调用
setWebApplicationType(WebApplicationType)来轻松地覆盖它。

还可以通过调用setApplicationContextClass(…)来完全控制ApplicationContext类型。

在JUnit测试中使用SpringApplication时,通常需要调用
setWebApplicationType(WebApplicationType.NONE)。

1.8. Accessing Application Arguments

如果您需要访问传递给SpringApplication.run(…)的应用程序参数,您可以注入一个org.springframework.boot.ApplicationArguments。ApplicationArguments接口提供对原始 String[] 参数、解析过的选项和非选项参数的访问,如下面的示例所示:

Import org.springframework.boot.*; 
import org.springframework.beans.factory.annotation.*; 
import org.springframework.stereotype.*; 
@Component 
public class MyBean {
    
@Autowired 
public MyBean(ApplicationArguments args) {
     
boolean debug = args.containsOption("debug"); 
List<String> files = args.getNonOptionArgs(); 
// if run with "--debug logfile.txt" debug=true, files=["logfile.txt"] 
} 
}

Spring Boot还向Spring Environment注册一个CommandLinePropertySource 。这还允许您通过使用@Value注释注入单个应用程序参数。

1.9. Using the ApplicationRunner or CommandLineRunner

如果您需要在SpringApplication启动后运行一些特定的代码,您可以实现ApplicationRunner或CommandLineRunner接口。这两个接口以相同的方式工作,并提供一个单一的运行方法,该方法在SpringApplication.run(…)完成之前被调用。

CommandLineRunner接口以简单的字符串数组的形式提供对应用程序参数的访问,而ApplicationRunner使用前面讨论的ApplicationArguments接口。下面的例子展示了一个带有run方法的CommandLineRunner:

import org.springframework.boot.*; 
import org.springframework.stereotype.*;
@Component 
public class MyBean implements CommandLineRunner {
    public void run(String... args) {
     
// Do something...}}

如果定义了几个必须以特定顺序调用的CommandLineRunner或ApplicationRunner bean,则可以另外实现org.springframework.core.Ordered接口
或使用org.springframework.core.annotation.Order注释。

1.10. Application Exit

每个SpringApplication向JVM注册一个关闭钩子,以确保ApplicationContext在退出时优雅地关闭。可以使用所有标准的Spring生命周期回调(例如DisposableBean接口或@PreDestroy注释)。

另外,beans可以实现org.springframework.boot.ExitCodeGenerator 接口,如果它们希望在调用SpringApplication.exit()时返回特定的退出代码。然后,可以将这个退出代码传递给System.exit(),将其作为状态代码返回,如下面的示例所示:

@SpringBootApplication 
public class ExitCodeApplication {
     
@Bean 
public ExitCodeGenerator exitCodeGenerator() {
    return () -> 42; 
} 
public static void main(String[] args) {
     
System.exit(
SpringApplication.exit(SpringApplication.run(ExitCodeApplication.class, 	args))
); 
}
}

此外,ExitCodeGenerator接口可以由异常实现。当遇到这样的异常时,Spring Boot将返回实现的getExitCode()方法提供的退出代码。

1.11. Admin Features

可以通过指定spring.application.admin.enabled来为应用程序启用admin-related的功能。这将在MBeanServer平台上公开SpringApplicationAdminMXBean。您可以使用此功能远程管理Spring启动应用程序。这个特性对于任何服务包装器实现都很有用。

如果您想知道应用程序在哪个HTTP端口上运行,请使用local.server.port的键获取属性。

2. Externalized Configuration

Spring Boot允许您使用外部化的配置,以便可以在不同的环境中使用相同的应用程序代码。您可以使用properties 文件、YAML文件、环境变量和命令行参数来具体化配置。属性值可以通过使用@Value注解直接注入到Beans中,可以通过Spring的Environment接口访问属性值,也可以通过@ConfigurationProperties注解绑定到结构化对象。

Spring Boot使用一种非常明确的PropertySource顺序,其设计目的是允许合理地进行属性值的覆盖。属性按以下顺序考虑:

为了提供一个具体的例子,假设您开发了一个使用name属性的@Component,如下面的例子所示:

import org.springframework.stereotype.*; 
import org.springframework.beans.factory.annotation.*; 
@Component 
public class MyBean {
    @Value("${name}") 
private String name; 
// ... 
}

在应用程序类路径上(例如,在jar中)可以有一个application.properties 文件为name提供一个合理的默认属性值。在新环境中运行时,可以提供一个在jar文件外的application.properties配置文件,在这个配置文件中覆盖name值。对于一次性测试,可以使用特定的命令行开关启动(例如,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。

2.1. Configuring Random Values

RandomValuePropertySource用于注入随机值(例如,注入密文或测试值)。它可以生成整数、long、uuid或字符串,如下面的示例所示:

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 是 int 类型的整数。如果 max 参数存在,则 value 表示取值范围的最小值,max 表示最大值(不包含 max)。

2.2. Accessing Command Line Properties

默认情况下,SpringApplication将任何命令行选项参数(即以–开始的参数,例如–server.port=9000)转换为属性,并将它们添加到Spring Environment中。如前所述,命令行属性始终优先于其他属性源。

如果不希望将命令行属性添加到环境中,可以使用
SpringApplication.setAddCommandLineProperties(false)禁用它们。

2.3. Application Property Files

SpringApplication 从以下路径加载application.properties文件资源并将其添加到 Spring Environment中:
?当前启动类目录的/config 子目录
?当前启动类目录
?classpath下的 /config包
?classpath跟目录

列表按优先级排序(在列表中较高位置定义的属性覆盖在较低位置定义的属性)。

如果你不喜欢使用 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.name 和spring.config.location在很早的时候就被应用程序使用用来决定要加载哪些文件。它们必须定义为环境属性(通常是操作系统环境变量、系统属性或命令行参数)。

如果spring.config.location包含目录(与文件相反),它们应该以“/”结尾(并且在运行时,在加载之前附加由spring.config.name生成的名称,包括特定于profile-specific文件的文件名)。在spring.config.location中指定的文件按原样使用,不支持特定于profile-specific的变体,并被任何特定于profile-specific的属性覆盖。

配置位置以相反的顺序搜索。默认情况下,配置的位置是classpath:/,classpath:/config/,file:./,file:./config/。属性搜索顺序如下:

1、file: ./config/
2、file: ./
3、classpath :/config/
4、classpath :/

当使用spring.config.location自定义属性文件位置时,它们替换了默认位置。例如,如果spring.config.location配置为classpath:/custom-config/,file:./custom-config/,搜索顺序变为:

1、file:./custom-config/
2、classpath:/custom-config/
或者可以使用spring.config.additional-location自定义附加位置,附加位置是在默认位置之前搜索的。如果类路径的附加位置配为classpath:/custom-config/,file:./custom-config/,搜索顺序变为:
1、file:./custom-config/
2、classpath:/custom-config/
3、file:./config/
4、file:./
5、classpath:/config/
6、classpath:/

这种搜索顺序允许您在一个配置文件中指定默认值,然后有选择地在另一个配置文件中覆盖这些值。可以在默认位置的application.properties中为应用程序提供默认值。(或您在spring.config.name中选择的任何其他基本名称)。然后可以在运行时使用位于自定义位置中的另一个文件覆盖这些默认值。

如果使用环境变量而不是系统属性,大多数操作系统不允许使用period-separated的键名,但是可以使用下划线(例如,SPRING_CONFIG_NAME而不是spring.config.name)。

如果您的应用程序在容器中运行,那么可以使用JNDI属性(在java:comp/env中)或servlet上下文初始化参数来代替环境变量或系统属性,或者同时使用它们。

2.4. Profile-specific Properties

除了application.properties文件,profile-specific属性也可以通过使用以下命名约定来定义:application-{profile}.properties。环境有一组默认的配置文件(默认情况下是[default]),如果没有设置活动的配置文件,就使用这些配置文件。换句话说,如果没有显式激活配置文件,则默认加载使用application-default.properties文件。

Profile-specific属性文件是从与标准应用程序配置application.properties文件相同的位置加载的。无论profile-specific是在打包的jar内部还是外部使用,profile-specific文件配置总是会覆盖non-specific文件配置。

如果指定了多个配置文件,则应用“last-wins”策略。例如,由spring.profiles.active属性指定的配置文件是在通过SpringApplication API配置的属性之后添加的,因此优先。

如果您在spring.config.location中指定了任何文件,profile-specific的变体不被考虑。如果您还想使用profile-specific 属性配置文件,请在spring.config.location属性中配置文件路径。

2.5. Placeholders in Properties

配置文件application.properties中的属性值被使用时是通过现存的Environment进行筛选的,所以你可以引用以前定义的值(例如,使用系统属性):

app.name=MyApp 
app.description=${
    app.name} is a Spring Boot application

2.6. Encrypting Properties

Spring Boot没有提供任何内建的对属性值的加密支持,然而他提供了修改Spring Environment中的值的必要的hook points(挂钩点)。EnvironmentPostProcessor接口允许你在应用程序启动之前操作Environment

如果您正在寻找一种安全的方式来存储凭证和密码,Spring Cloud Vault项目提供了在HashiCorp Vault中存储外部化配置的支持。

2.7. Using YAML Instead of Properties

YAML是JSON的一个超集,因此是指定分层配置数据的一种方便的格式。当您的类路径中有SnakeYAML库时,SpringApplication类自动支持YAML作为属性的替代。

如果您使用“Starters”,SnakeYAML是由spring-boot-starter自动提供的。

2.7.1. Loading YAML

Spring框架提供了两个方便的类,可用于加载YAML文档。YamlPropertiesFactoryBean将YAML加载为属性,YamlMapFactoryBean将YAML加载为Map。

例如,考虑以下YAML文档:

environments: 
dev: 
url:https://dev.example.com 
name: Developer Setup 
prod: 
url: https://another.example.com 
name: My Cool App

将上述例子转化为以下属性:

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

要通过使用Spring Boot的Binder实用程序(这就是@ConfigurationProperties所做的)绑定上例中的属性值,你需要在目标Bean中定义一个java.util.List(或是Set),而且你需要为这个属性提供setter方法或是使用一个可变值初始化他。例如,下面的例子绑定到前面显示的属性:

@ConfigurationProperties(prefix="my") 
public class Config {
    
private List<String> servers = new ArrayList<String>(); 
public List<String> getServers() {
     
return this.servers; 
} 
}

2.7.2. Exposing YAML as Properties in the Spring Environment

YamlPropertySourceLoader类可用于在Spring Environment中将YAML解析为
PropertySource。这样就可以使用带有占位符语法的@Value注释来访问YAML属性。

2.7.3. Multi-profile YAML Documents

通过使用spring.profiles键,可以在一个文件中指定多源的profile-specific 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 profile处于活动状态,则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 可以包含简单的概要文件名称(例如production)或概要文件表达式。概要文件表达式允许表达更复杂的概要文件逻辑,例如production & (eu-central | eu-west)。

如果在应用程序上下文启动时没有显式激活配置文件,则默认配置文件将被激活。因此,在接下来的YAML中,我们为spring.security.user.password设置了一个值。仅在“默认”配置文件中可用的密码:

server: 
port: 8000 
--- 
spring: 
profiles: default 
security: 
user: 
password: weak

然而,在下面的例子中,密码总是被设置,因为它没有附加到任何配置文件中,而且它必须在所有其他配置文件中被显式重置。

server: 
port: 8000 
spring: 
security: 
user:password: weak

被设计为使用 spring.profiles元素的Spring profiles可能会随意“!”进行否定配置。如果为单个文档指定了否定的和非否定的配置文件,则必须至少匹配一个非否定的配置文件,并且不能匹配任何否定的配置文件。

2.7.4. YAML Shortcomings

无法使用@PropertySource注释加载YAML文件。因此,在需要以这种方式加载值的情况下,需要使用属性文件。

在profile-specific YAML文件中使用multi 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。它已经被认为是profile-specific,嵌套文档将被忽略。

我们建议您不要将特定于配置文件的YAML文件和多个YAML文档混合使用。坚持只使用其中之一。

2.8. Type-safe Configuration Properties

使用@Value(“${property}”)注释来注入配置属性有时会很麻烦,特别是在处理多个属性或您的数据本质上是分层的情况下。Spring Boot提供了另一种处理属性的方法,允许强类型bean控制和验证应用程序的配置。

2.8.1. JavaBean properties binding

可以绑定到一个声明了标准JavaBean属性的bean上,如下面的例子所示:

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, with a value of false by default.

?acme.remote-address, with a type that can be coerced from String.

?acme.security.username, with a nested “security” object whose name is determined by the name of the property. In particular, the return type is not used at all there and could have been SecurityProperties.

?acme.security.password.

?acme.security.roles, with a collection of String that defaults to USER.

Spring Boot的auto-configuration大量使用@ConfigurationProperties来轻松配置
auto-configured bean。与auto-configured类类似,Spring Boot中,
@ConfigurationProperties类仅供内部使用。映射到类的属性(通过属性文件、YAML文件、环境变量等配置)是公共API,但是类本身的内容并不意味着可以直接使用。

这种安排依赖于默认的空构造函数,getter和setter通常是必需的,因为绑定是通过标准的Java bean属性描述符进行的,就像Spring MVC一样。在下列情况下可省略setter:

?只要Maps被初始化,就需要一个getter,但不一定是setter,因为绑定器可以对它们进行修改。

?可以通过索引(通常使用YAML)或使用单个逗号分隔值(属性)访问集合和数组。在后一种情况下,setter是必需的。我们建议始终为此类类型添加setter。如果您初始化一个集合,请确保它不是不可变的(如前面的示例所示)。

?如果初始化了嵌套的POJO属性(如前面示例中的Security字段),则不需要setter。如果希望绑定器使用其默认构造函数动态创建实例,则需要setter。

有些人使用Project Lombok自动添加getter和setter。确保Lombok不会为这样的类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。

最后,只考虑标准的Java Bean属性,不支持绑定静态属性。

2.8.2. Constructor binding

前一节中的例子可以用不可变的方式重写,如下面的例子所示:

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.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类的嵌套成员(如上面示例中的安全性)也将通过其构造函数绑定。

可以使用@DefaultValue指定默认值,并应用相同的转换服务将字符串值强制转换为缺失属性的目标类型。

要使用构造函数绑定,必须使用@EnableConfigurationProperties或配置属性扫描启用该类。不能使用构造函数绑定由常规Spring机制创建的bean(例如@Component bean、通过@Bean方法创建的bean或使用@Import加载的bean)

如果您的类有多个构造函数,您还可以直接在应该绑定的构造函数上使用
@ConstructorBinding。

2.8.3. Enabling @ConfigurationProperties-annotated types

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 {
     
}

当@ConfigurationProperties bean使用配置属性扫描或是
@EnableConfigurationProperties注册时, bean有一个惯用名称:-< fqn >, 是@ConfigurationProperties注释中指定environment key prefix,< fqn >是bean的完全限定名称。如果注解不提供任何前缀,则只使用bean的完全限定名。上面例子中的bean名是acme-com.example.AcmeProperties。

我们建议@ConfigurationProperties只处理环境,特别是不从上下文注入其他bean。对于极端情况,可以使用setter注入或框架提供的任何*Aware接口(例如,如果需要访问环境,可以使用EnvironmentAware)。如果您仍然希望使用构造函数注入其他bean,则必须使用@Component注释配置属性bean,并使用JavaBean-based的属性绑定。

2.8.4. Using @ConfigurationProperties-annotated types

这种配置方式在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()); // ... 
} 
}

2.8.5. Third-party Configuration

除了使用@ConfigurationProperties注释类外,您还可以在公共@Bean方法上使用它。当您希望将属性绑定到您无法控制的第三方组件时,这样做特别有用。

要从环境属性配置bean,请将@ConfigurationProperties添加到它的bean注册中,如下面的示例所示:

@ConfigurationProperties(prefix = "another") 
@Bean 
public AnotherComponent anotherComponent() {
     
... 
}

使用另一个前缀定义的任何JavaBean属性都将以类似前面的AcmeProperties示例的方式映射到另一个组件bean。

2.8.6. Relaxed Binding

Spring Boot使用一些宽松的规则将环境属性绑定到@ConfigurationProperties bean,因此环境属性名和bean属性名之间不需要精确匹配。有用的常见示例包括dash分隔的环境属性(例如,上下文路径绑定到contextPath)和大写的环境属性(例如,端口绑定到端口)。

例如,考虑以下@ConfigurationProperties类:

@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; 
} 
}

使用上述代码,可以使用以下属性名:

注释的前缀值必须是kebab case(小写,用-分隔,如acme.my-project.person)。

我们建议,如果可能的话,将属性存储为小写的kebab格式,例如my.property-name=acme。

在绑定到映射属性时,如果键包含除小写字母-数字字符或-之外的任何内容,则需要使用括号符号,以便保留原始值。如果键没有被[]包围,则删除任何不是字母数字或-的字符。例如,考虑将以下属性绑定到映射:

acme:
map: 
"[/key1]": value1 
"[/key2]": value2 
/key3: value3

上面的属性将绑定到以/key1、/key2和key3作为映射中的键的映射。
对于YAML文件,方括号需要用引号括起来,以便正确解析键。

2.8.7. Merging Complex Types

当在多个位置配置列表时,通过替换整个列表覆盖工作。

例如,假设MyPojo对象的名称和描述属性默认为null。下面的例子展示了一个来自AcmeProperties的MyPojo对象列表:

@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配置文件不是活动的,AcmeProperties。列表包含一个MyPojo条目,如前面定义的那样。但是,如果启用了dev配置文件,则列表仍然只包含一个条目(名称为我的另一个名称,描述为null)。此配置不会向列表中添加第二个MyPojo实例,也不会合并项。

当在多个配置文件中指定一个列表时,将使用具有最高优先级的列表(且仅使用该列表)。考虑下面的例子:

acme: 
list: 
- name: my name 
description: my description 
- name: another name 
description: another description 
--- 
spring: 
profiles: dev 
acme: 
list: 
- name: my another name

在前面的示例中,如果dev配置文件是活动的,则使用AcmeProperties。列表包含一个MyPojo条目(名称为我的另一个名称,描述为null)。对于YAML,可以使用逗号分隔的列表和YAML列表来完全覆盖列表的内容。

对于映射属性,您可以绑定来自多个源的属性值。但是,对于多个源中的相同属性,将使用具有最高优先级的属性。下面的例子展示了一个Map<String, MyPojo> from AcmeProperties:

@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配置文件不是活动的,AcmeProperties。地图包含一个条目与关键key1(名字我的名字和我的描述描述1)。如果启用了开发概要文件,然而,与键key1地图包含两个条目(dev名称名称1和我的描述描述1)和key2 (dev名称名称2和dev的描述描述2)。

前面的合并规则适用于来自所有属性源的属性,而不仅仅是YAML文件。

2.8.8. Properties Conversion

当Spring Boot绑定到@ConfigurationProperties bean时,它尝试将外部应用程序属性强制转换为正确的类型。如果需要自定义类型转换,可以提供一个ConversionService bean(使用一个名为ConversionService的bean)或自定义属性编辑器(通过CustomEditorConfigurer bean)或自定义转换器(使用注释为@ConfigurationPropertiesBinding的bean定义)。

由于此bean在应用程序生命周期中很早就被请求,请确保限制您的ConversionService正在使用的依赖项。通常,您需要的任何依赖项可能在创建时没有完全初始化。如果配置键强制转换不需要重命名自定义转换服务,并且只依赖使用@ConfigurationPropertiesBinding限定的自定义转换器,那么您可能希望重命名自定义转换服务。

Converting durations
Spring Boot专门支持表示持续时间。如果您公开java.time。持续时间属性,应用程序属性中的以下格式可用:

…略

2.8.9. @ConfigurationProperties Validation

当用Spring的@Validated注释注释类时,Spring引导尝试验证@ConfigurationProperties类。您可以使用JSR-303 javax。直接在配置类上的验证约束注释。要做到这一点,请确保兼容的JSR-303实现在您的类路径上,然后向您的字段中添加约束注释,如下面的示例所示:

@ConfigurationProperties(prefix="acme") 
@Validated 
public class AcmeProperties {
     
@NotNull 
private InetAddress remoteAddress;// ... getters and setters 
}

您还可以通过使用@Validated来注释创建配置属性的@Bean方法来触发验证。

为了确保总是触发对嵌套属性的验证,即使没有找到属性,也必须使用@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验证器。@Bean方法应该声明为静态的。配置属性验证器是在应用程序生命周期的早期创建的,将@Bean方法声明为静态方法可以创建bean,而不必实例化@Configuration类。这样做可以避免任何可能由早期实例化引起的问题。

spring-boot-执行器模块包含一个公开所有@ConfigurationProperties bean的端点。将web浏览器指向/执行器/configprops或使用等效的JMX端点。有关详细信息,请参阅“产品就绪特性”一节。

2.8.10. @ConfigurationProperties vs. @Value

@Value注释是一个核心容器特性,它不提供与类型安全配置属性相同的特性。下表总结了@ConfigurationProperties和@Value支持的特性:

如果您为自己的组件定义了一组配置键,我们建议您将它们分组到一个带有@ConfigurationProperties注释的POJO中。您还应该注意到,由于@Value不支持轻松绑定,因此如果您需要使用环境变量来提供值,那么它就不是一个好的选择。

最后,虽然您可以在@Value中编写SpEL表达式,但是这些表达式不会从应用程序属性文件中处理。

  相关解决方案