# 核心功能
这一节深入讨论了 Spring 引导的细节。在这里,你可以了解你可能想要使用和自定义的关键功能。如果你还没有这样做,你可能希望阅读“getting-started.html”和“using.html”部分,以便你有一个良好的基础知识。
# 1. SpringApplication
SpringApplication
类提供了一种方便的方式来引导从main()
方法启动的 Spring 应用程序。在许多情况下,可以委托给静态SpringApplication.run
方法,如以下示例所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
当应用程序启动时,你应该会看到类似于以下输出的内容:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: v2.6.4
2021-02-03 10:33:25.224 INFO 17321 --- [ main] o.s.b.d.s.s.SpringApplicationExample : Starting SpringApplicationExample using Java 1.8.0_232 on mycomputer with PID 17321 (/apps/myjar.jar started by pwebb)
2021-02-03 10:33:25.226 INFO 17900 --- [ main] o.s.b.d.s.s.SpringApplicationExample : No active profile set, falling back to default profiles: default
2021-02-03 10:33:26.046 INFO 17321 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-02-03 10:33:26.054 INFO 17900 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-02-03 10:33:26.055 INFO 17900 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-02-03 10:33:26.097 INFO 17900 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-02-03 10:33:26.097 INFO 17900 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 821 ms
2021-02-03 10:33:26.144 INFO 17900 --- [ main] s.tomcat.SampleTomcatApplication : ServletContext initialized
2021-02-03 10:33:26.376 INFO 17900 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-02-03 10:33:26.384 INFO 17900 --- [ main] o.s.b.d.s.s.SpringApplicationExample : Started SampleTomcatApplication in 1.514 seconds (JVM running for 1.823)
默认情况下,将显示INFO
日志消息,包括一些相关的启动细节,例如启动应用程序的用户。如果需要INFO
以外的日志级别,则可以设置它,如Log Levels中所述。应用程序版本是使用主应用程序类的包中的实现版本来确定的。可以通过将spring.main.log-startup-info
设置为false
来关闭启动信息日志。这也将关闭应用程序活动配置文件的日志记录。
要在启动期间添加额外的日志记录,可以在SpringApplication 的子类中覆盖logStartupInfo(boolean) 。 |
---|
# 1.1.启动失败
如果你的应用程序无法启动,注册FailureAnalyzers
将有机会提供一个专用的错误消息和具体的操作来解决问题。例如,如果你在端口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 is listening on port 8080 or configure this application to listen on another port.
Spring Boot 提供了许多FailureAnalyzer 实现,并且可以添加你自己的。 |
---|
如果没有故障分析器能够处理异常,则仍然可以显示完整的条件报告,以更好地了解出了什么问题。为此,你需要[启用debug
属性]或[启用DEBUG
日志](#features.logging.log-levels)来实现org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
。
例如,如果你正在使用java -jar
运行你的应用程序,那么你可以启用debug
属性,如下所示:
$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug
# 1.2.惰性初始化
SpringApplication
允许应用程序进行惰性初始化。当启用惰性初始化时,将根据需要而不是在应用程序启动期间创建 bean。因此,启用延迟初始化可以减少启动应用程序所需的时间。在 Web 应用程序中,启用延迟初始化将导致许多与 Web 相关的 bean 在收到 HTTP 请求之前不会被初始化。
延迟初始化的一个缺点是,它可能会延迟应用程序问题的发现。如果延迟初始化配置错误的 Bean,则在启动过程中将不再发生故障,并且只有在初始化 Bean 时,问题才会变得明显。还必须注意确保 JVM 有足够的内存来容纳所有应用程序的 bean,而不仅仅是那些在启动期间初始化的 bean。由于这些原因,默认情况下不启用惰性初始化,建议在启用惰性初始化之前对 JVM 的堆大小进行微调。
可以使用SpringApplicationBuilder
上的lazyInitialization
方法或setLazyInitialization
上的setLazyInitialization
方法以编程方式启用惰性初始化。或者,可以使用spring.main.lazy-initialization
属性启用它,如以下示例所示:
属性
spring.main.lazy-initialization=true
Yaml
spring:
main:
lazy-initialization: true
如果你希望在对应用程序的其余部分使用惰性初始化的同时禁用某些 bean 的惰性初始化,那么可以使用@Lazy(false) 注释显式地将它们的惰性属性设置为 false。 |
---|
# 1.3.自定义横幅
可以通过将banner.txt
文件添加到 Classpath 或将spring.banner.location
属性设置为此类文件的位置来更改在启动时打印的横幅。如果文件的编码不是 UTF-8,则可以设置spring.banner.charset
。除了文本文件,还可以在 Classpath 中添加banner.gif
、banner.jpg
或banner.png
图像文件,或者设置spring.banner.image.location
属性。图像被转换为 ASCII 艺术表现,并打印在任何文本横幅之上。
在banner.txt
文件中,你可以使用Environment
中可用的任何键以及以下任何占位符:
Variable | 说明 |
---|---|
${application.version} | 应用程序的版本号,如MANIFEST.MF 中声明的。例如, Implementation-Version: 1.0 打印为1.0 。 |
${application.formatted-version} | 应用程序的版本号,如在MANIFEST.MF 中声明的那样,并已格式化以供显示(周围用括号包围,前缀为v )。例如 (v1.0) 。 |
${spring-boot.version} | 你正在使用的 Spring 引导版本。 例如 2.6.4 。 |
${spring-boot.formatted-version} | 你正在使用的 Spring 引导版本,格式为显示(周围用括号和前缀v )。例如 (v2.6.4) 。 |
${Ansi.NAME} (or ${AnsiColor.NAME} , ${AnsiBackground.NAME} , ${AnsiStyle.NAME} ) | 其中NAME 是一个 ANSI 逃逸代码的名称。详见[ AnsiPropertySource ](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot/SRC/main/java/org/springframework/boot/ansi/ansipropertysource.java)。 |
${application.title} | 在MANIFEST.MF 中声明的应用程序的标题。例如 Implementation-Title: MyApp 打印为MyApp 。 |
如果你想以编程方式生成横幅,可以使用SpringApplication.setBanner(…) 方法。使用 org.springframework.boot.Banner 接口并实现你自己的printBanner() 方法。 |
---|
你还可以使用spring.main.banner-mode
属性来确定横幅是否必须在System.out
(console
)上打印、发送到配置的记录器(log
),或者根本不产生(off
)。
打印的横幅以单件 Bean 的形式注册,其名称如下:springBootBanner
。
只有当你使用 Spring 引导启动器时,${application.version} 和${application.formatted-version} 属性才可用。这些值将不会被解析如果你正在运行一个 Unpacked jar 并以 java -cp <classpath> <mainclass> 启动它。这就是为什么我们建议你总是使用 java org.springframework.boot.loader.JarLauncher 启动 Unpacked JAR。这将在构建 Classpath 并启动应用程序之前初始化 application.* 横幅变量。 |
---|
# 1.4.自定义 SpringApplication
如果SpringApplication
默认值不符合你的喜好,那么你可以创建一个本地实例并对其进行自定义。例如,要关闭横幅,你可以写:
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
传递给SpringApplication 的构造函数参数是 Spring bean 的配置源。在大多数情况下,这些参数是对 @Configuration 类的引用,但它们也可以是直接引用@Component 类。 |
---|
也可以通过使用application.properties
文件配置SpringApplication
。详见 外部化配置。
有关配置选项的完整列表,请参见[SpringApplication
Javadoc](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/springapplication.html)。
# 1.5.Fluent Builder API
如果你需要构建ApplicationContext
层次结构(具有父/子关系的多个上下文),或者如果你更喜欢使用“Fluent”Builder API,则可以使用SpringApplicationBuilder
。
SpringApplicationBuilder
允许你将多个方法调用链接在一起,并且包括parent
和child
方法,这些方法允许你创建一个层次结构,如以下示例所示:
new SpringApplicationBuilder()
.sources(Parent.class)
.child(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
在创建ApplicationContext 层次结构时会有一些限制。例如,Web 组件必须被包含在子上下文中,并且相同的 Environment 同时用于父上下文和子上下文。参见[ SpringApplicationBuilder Javadoc](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/org/applicationframework/applicbuilder/applicbuilder/applicbuilder/applatform/applicform/applicbuilder)以获取全部详细信息。 |
---|
# 1.6.应用程序可用性
当部署在平台上时,应用程序可以使用Kubernetes 探测器 (opens new window)之类的基础设施向平台提供有关其可用性的信息。 Spring 启动包括对常用的“活性”和“就绪”可用性状态的开箱即用支持。如果你使用 Spring Boot 的“actuator”支持,那么这些状态将作为健康端点组公开。
此外,你还可以通过将ApplicationAvailability
接口注入你自己的 bean 来获得可用性状态。
# 1.6.1.活性状态
应用程序的“活性”状态表示其内部状态是否允许其正确工作,或者在当前出现故障时是否允许其自行恢复。中断的“活性”状态意味着应用程序处于无法恢复的状态,基础结构应该重新启动应用程序。
通常,“活性”状态不应该基于外部检查,例如健康检查。 如果是这样,失败的外部系统(数据库、Web API、外部缓存)将触发大规模重启和跨平台的级联故障。 |
---|
Spring 引导应用程序的内部状态主要由 Spring ApplicationContext
表示。 Spring 如果应用程序上下文已成功启动,则启动假定应用程序处于有效状态。一旦上下文被刷新,应用程序就被认为是实时的,请参见Spring Boot application lifecycle and related Application Events。
# 1.6.2.准备状态
应用程序的“就绪”状态表明该应用程序是否已准备好处理流量。一个失败的“就绪”状态告诉平台,它现在不应该将流量路由到应用程序。这通常发生在启动过程中,而CommandLineRunner
和ApplicationRunner
组件正在被处理,或者在任何时候,如果应用程序决定它太忙而不能进行额外的流量。
一旦调用了应用程序和命令行运行器,就认为应用程序已准备就绪,请参见Spring Boot application lifecycle and related Application Events。
期望在启动期间运行的任务应该由CommandLineRunner 和ApplicationRunner 组件执行,而不是使用 Spring 组件生命周期回调,例如@PostConstruct 。 |
---|
# 1.6.3.管理应用程序可用性状态
通过注入ApplicationAvailability
接口并在其上调用方法,应用程序组件可以随时检索当前的可用性状态。更常见的情况是,应用程序希望侦听状态更新或更新应用程序的状态。
例如,我们可以将应用程序的“就绪”状态导出到一个文件中,以便 Kubernetes 的“Exec Probe”可以查看该文件:
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyReadinessStateExporter {
@EventListener
public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
switch (event.getState()) {
case ACCEPTING_TRAFFIC:
// create file /tmp/healthy
break;
case REFUSING_TRAFFIC:
// remove file /tmp/healthy
break;
}
}
}
当应用程序中断且无法恢复时,我们还可以更新应用程序的状态:
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class MyLocalCacheVerifier {
private final ApplicationEventPublisher eventPublisher;
public MyLocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void checkLocalCache() {
try {
// ...
}
catch (CacheCompletelyBrokenException ex) {
AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
}
}
}
Spring Boot 提供Kubernetes HTTP 探测与执行器健康端点的“活性”和“准备”。你可以获得更多关于deploying Spring Boot applications on Kubernetes in the dedicated section的指导。
# 1.7.应用程序事件和监听器
除了通常的 Spring 框架事件,如[ContextRefreshedEvent
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/event/contextrefreshedevent.html),aSpringApplication
发送一些额外的应用程序事件。
有些事件实际上是在ApplicationContext 被创建之前被触发的,所以你不能在这些事件上注册一个侦听器作为@Bean 。你可以用 SpringApplication.addListeners(…) 方法或SpringApplicationBuilder.listeners(…) 方法来注册它们,如果你想要自动注册这些侦听器,无论创建应用程序的方式如何,你都可以通过使用 org.springframework.context.ApplicationListener 键向项目添加META-INF/spring.factories 文件并引用侦听器,如以下示例所示:<br/>org.springframework.context.ApplicationListener=com.example.project.MyListener<br/> |
---|
在应用程序运行时,应用程序事件按以下顺序发送:
一个
ApplicationStartingEvent
在运行开始时但在任何处理之前发送,除了注册侦听器和初始化器。当要在上下文中使用的
Environment
是已知的但在创建上下文之前,则发送ApplicationEnvironmentPreparedEvent
。当
ApplicationContext
准备好并且在加载任何 Bean 定义之前调用了应用上下文初始化器时,发送ApplicationContextInitializedEvent
。一个
ApplicationPreparedEvent
在刷新开始之前但是在 Bean 定义加载之后被发送。在刷新上下文之后,但在调用任何应用程序和命令行运行器之前,将发送
ApplicationStartedEvent
。在使用
LivenessState.CORRECT
之后立即发送AvailabilityChangeEvent
,以表明该应用程序被认为是动态的。在调用任何应用程序和命令行运行器后发送
ApplicationReadyEvent
。在使用
ReadinessState.ACCEPTING_TRAFFIC
之后立即发送AvailabilityChangeEvent
,以表明应用程序已准备好为请求提供服务。如果启动时有异常,则发送
ApplicationFailedEvent
。
上面的列表只包含与SpringApplication
绑定的SpringApplicationEvent
。除了这些,以下事件还发布在ApplicationPreparedEvent
之后和ApplicationStartedEvent
之前:
在
WebServer
准备好之后发送WebServerInitializedEvent
。ServletWebServerInitializedEvent
和ReactiveWebServerInitializedEvent
分别是 Servlet 和活性变量。当刷新
ApplicationContext
时,将发送ContextRefreshedEvent
。
你通常不需要使用应用程序事件,但是知道它们的存在是很方便的。 在内部, Spring boot 使用事件来处理各种任务。 |
---|
默认情况下,事件侦听器在同一个线程中执行时,不应该运行可能很长的任务。 考虑使用应用程序和命令行运行器。 |
---|
应用程序事件通过使用 Spring Framework 的事件发布机制发送。该机制的一部分确保在子上下文中发布给侦听器的事件也在任何祖先上下文中发布给侦听器。因此,如果应用程序使用SpringApplication
实例的层次结构,则侦听器可能会接收同一类型的应用程序事件的多个实例。
为了允许侦听器区分其上下文的事件和子代上下文的事件,它应该请求注入其应用程序上下文,然后将注入的上下文与事件的上下文进行比较。上下文可以通过实现ApplicationContextAware
来注入,或者,如果侦听器是 Bean,则可以通过使用@Autowired
来注入。
# 1.8.网络环境
aSpringApplication
试图代表你创建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.9.访问应用程序参数
如果需要访问传递给SpringApplication.run(…)
的应用程序参数,则可以插入org.springframework.boot.ApplicationArguments
Bean。ApplicationArguments
接口提供对 RAWString[]
参数以及解析的option
和non-option
参数的访问,如以下示例所示:
import java.util.List;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
if (debug) {
System.out.println(files);
}
// if run with "--debug logfile.txt" prints ["logfile.txt"]
}
}
Spring Boot 还使用 Spring Environment 注册了CommandLinePropertySource 。这也允许你通过使用 @Value 注释来注入单个应用程序参数。 |
---|
# 1.10.使用 ApplicationRunner 或 CommandLineRunner
如果在SpringApplication
启动后需要运行某些特定的代码,则可以实现ApplicationRunner
或CommandLineRunner
接口。这两个接口以相同的方式工作,并提供一个run
方法,该方法在SpringApplication.run(…)
完成之前被调用。
此契约非常适合在应用程序启动后但在它开始接受流量之前运行的任务。 |
---|
CommandLineRunner
接口以字符串数组的形式提供对应用程序参数的访问,而ApplicationRunner
接口使用前面讨论的ApplicationArguments
接口。下面的示例显示了带有run
方法的CommandLineRunner
:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
// Do something...
}
}
如果定义了几个CommandLineRunner
或ApplicationRunner
bean,必须以特定的顺序进行调用,则可以另外实现org.springframework.core.Ordered
接口或使用org.springframework.core.annotation.Order
注释。
# 1.11.应用程序退出
每个SpringApplication
都向 JVM 注册一个关闭钩子,以确保ApplicationContext
在退出时优雅地关闭。可以使用所有标准的 Spring 生命周期回调(例如DisposableBean
接口或@PreDestroy
注释)。
此外,如果希望在调用SpringApplication.exit()
时返回特定的退出代码,则 bean 可以实现org.springframework.boot.ExitCodeGenerator
接口。然后可以将此退出代码传递给System.exit()
,以将其作为状态代码返回,如下例所示:
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MyApplication {
@Bean
public ExitCodeGenerator exitCodeGenerator() {
return () -> 42;
}
public static void main(String[] args) {
System.exit(SpringApplication.exit(SpringApplication.run(MyApplication.class, args)));
}
}
此外,ExitCodeGenerator
接口可以通过异常来实现。当遇到这样的异常时, Spring 引导返回由实现的getExitCode()
方法提供的退出代码。
# 1.12.管理功能
通过指定spring.application.admin.enabled
属性,可以为应用程序启用与管理相关的特性。这暴露了平台上的[SpringApplicationAdminMXBean
](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot/SRC/main/java/org/springframework/boot/admin/applicationadmxbean.java)MBeanServer
。你可以使用此功能远程管理你的 Spring 启动应用程序。这个特性对于任何服务包装器实现都是有用的。
如果你想知道应用程序在哪个 HTTP 端口上运行,请使用local.server.port 的键获取该属性。 |
---|
# 1.13.应用程序启动跟踪
在应用程序启动期间,SpringApplication
和ApplicationContext
执行许多与应用程序生命周期、bean 生命周期甚至处理应用程序事件有关的任务。通过[ApplicationStartup
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/metrics/applicationstartup.html), Spring Framework[允许你使用StartupStep
对象跟踪应用程序启动序列](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/ception/core-funicity.html#context-function-收集这些数据可以用于分析目的,或者仅仅是为了更好地了解应用程序启动过程。
在设置SpringApplication
实例时,可以选择ApplicationStartup
实现。例如,要使用BufferingApplicationStartup
,你可以写:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
}
}
第一个可用的实现方式,FlightRecorderApplicationStartup
是由 Spring 框架提供的。它将 Spring 特定的启动事件添加到 Java 飞行记录器会话中,用于分析应用程序,并将其 Spring 上下文生命周期与 JVM 事件(例如分配、GCS、类加载……)关联起来。一旦配置完成,你就可以通过启用飞行记录器来运行应用程序来记录数据:
$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar
Spring boot 附带BufferingApplicationStartup
变体;该实现用于缓冲启动步骤并将它们排入外部度量系统。应用程序可以在任何组件中请求类型BufferingApplicationStartup
的 Bean。
Spring 启动还可以被配置为公开一个[startup
端点](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/actuator-api/htmlsingle/#startup),它将此信息作为 JSON 文档提供。
# 2. 外部化配置
Spring 引导允许你外部化你的配置,以便你可以在不同的环境中使用相同的应用程序代码。你可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。
可以使用@Value
注释直接将属性值注入到 bean 中,该注释可通过 Spring 的Environment
抽象访问,也可以通过绑定到结构化对象通过@Configuration属性
访问。
Spring 引导使用非常特殊的PropertySource
顺序,该顺序被设计为允许合理地覆盖值。按以下顺序考虑属性(来自较低项的值覆盖了较早的项):
默认属性(通过设置
SpringApplication.setDefault属性
指定)。[
@PropertySource
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/propertysource.html)你的@Configuration
类上的注释。请注意,在刷新应用程序上下文之前,不会将此类属性源添加到Environment
中。现在配置某些属性(如logging.*
和spring.main.*
)已经太晚了,这些属性是在开始刷新之前读取的。配置数据(如
application.properties
文件)。a
RandomValuePropertySource
仅在random.*
中具有属性。OS 环境变量。
Java 系统属性(
System.get属性()
)。来自
java:comp/env
的 JNDI 属性。ServletContext
init 参数。ServletConfig
init 参数。来自
SPRING_APPLICATION_JSON
的属性(嵌入在环境变量或系统属性中的内联 JSON)。命令行参数。
properties
测试中的属性。 可在[@SpringBootTest
](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/test/context/springbootttest.html)和测试用于测试应用程序的特定部分的注释上查阅。[
@TestPropertySource
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/testpropertysource.html)测试上的注释。当 devtools 处于活动状态时,DevTools 全局设置属性在
$HOME/.config/spring-boot
目录中。
配置数据文件按以下顺序考虑:
应用程序属性封装在你的 jar 中(
application.properties
和 YAML 变体)。特定于配置文件的应用程序属性封装在你的 jar 中(
application-{profile}.properties
和 YAML 变体)。应用程序属性在你打包的 jar 之外(
application.properties
和 YAML 变体)。特定于配置文件的应用程序属性在你打包的 jar 之外(
application-{profile}.properties
和 YAML 变体)。
对于整个应用程序,建议使用一种格式。如果你的配置文件同时具有.properties 和.yml 两种格式,那么.properties 将优先使用。 |
---|
为了提供一个具体的示例,假设你开发了一个@Component
,它使用了name
属性,如以下示例所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
在你的应用程序 Classpath 上(例如,在你的 jar 内部),你可以拥有一个application.properties
文件,该文件为name
提供了一个合理的默认属性值。在新环境中运行时,可以在 jar 之外提供一个application.properties
文件,该文件覆盖name
。对于一次性测试,可以使用特定的命令行开关启动(例如,java -jar app.jar --name="Spring"
)。
env 和configprops 端点可以用于确定属性为什么具有特定值。你可以使用这两个端点来诊断意外的属性值。 有关详细信息,请参见“生产就绪功能一节。 |
---|
# 2.1.访问命令行属性
默认情况下,SpringApplication
将任何命令行选项参数(即以--
开头的参数,例如--server.port=9000
)转换为property
,并将其添加到 Spring Environment
。如前所述,命令行属性总是优先于基于文件的属性源。
如果不希望将命令行属性添加到Environment
,则可以使用SpringApplication.setAddCommandLine属性(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 当应用程序启动时,启动将自动从以下位置查找并加载application.properties
和application.yaml
文件:
来自 Classpath
Classpath 根
Classpath
/config
包
从当前目录
当前目录
当前目录中的
/config
子目录/config
子目录的直接子目录
该列表是按优先级排序的(来自较低项的值覆盖了较早的项)。来自加载的文件的文档以PropertySources
的形式添加到 Spring Environment
中。
如果不喜欢将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 很早就被用于确定必须加载哪些文件。它们必须被定义为一个环境属性(通常是一个 OS 环境变量、一个系统属性或一个命令行参数)。 |
---|
如果spring.config.location
包含目录(与文件相反),则它们应该以/
结尾。在运行时,在加载之前,将使用从spring.config.name
生成的名称对它们进行追加。在spring.config.location
中指定的文件是直接导入的。
还扩展了目录和文件位置值以检查特定于配置文件的文件。 例如,如果你有 spring.config.location 的classpath:myconfig.properties ,你还会发现适当的classpath:myconfig-<profile>.properties 文件已加载。 |
---|
在大多数情况下,你添加的每个spring.config.location
项都将引用一个文件或目录。位置是按照定义的顺序进行处理的,后面的位置可以覆盖前面的位置的值。
如果你有一个复杂的位置设置,并且你使用了特定于配置文件的配置文件,那么你可能需要提供进一步的提示,以便 Spring 引导知道应该如何对它们进行分组。位置组是所有位置都在同一级别上被考虑的位置的集合。例如,你可能希望对所有 Classpath 位置进行分组,然后对所有外部位置进行分组。位置组中的项应该用;
分隔。有关更多详细信息,请参见“配置文件特定的文件”小节中的示例。
使用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
被配置为值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/
这种搜索排序允许你在一个配置文件中指定默认值,然后有选择地重写另一个配置文件中的这些值。你可以在其中一个默认位置的application.properties
(或你选择的任何其他带有spring.config.name
的 basename)中为你的应用程序提供默认值。然后可以在运行时使用位于自定义位置之一中的不同文件重写这些默认值。
如果使用环境变量而不是系统属性,大多数操作系统都不允许使用周期分隔的键名,但可以使用下划线代替(例如,SPRING_CONFIG_NAME 而不是spring.config.name )。有关详细信息,请参见来自环境变量的绑定。 |
---|
如果你的应用程序运行在 Servlet 容器或应用程序服务器中,那么可以使用 JNDI 属性(在java:comp/env 中)或 Servlet 上下文初始化参数来代替环境变量或系统属性。 |
---|
# 2.3.1.可选位置
默认情况下,当指定的配置数据位置不存在时, Spring 启动将抛出ConfigDataLocationNotFoundException
,并且你的应用程序将不会启动。
如果你想指定一个位置,但并不介意它并不总是存在,那么可以使用optional:
前缀。你可以在spring.config.location
和spring.config.additional-location
属性以及[spring.config.import
](#features.external-config.files.importing)声明中使用此前缀。
例如,spring.config.import
值optional:file:./myconfig.properties
允许你的应用程序启动,即使缺少myconfig.properties
文件。
如果要忽略所有ConfigDataLocationNotFoundExceptions
并始终继续启动应用程序,则可以使用spring.config.on-not-found
属性。使用SpringApplication.setDefault属性(…)
或使用系统/环境变量将值设置为ignore
。
# 2.3.2.通配符位置
如果配置文件位置包含最后一个路径段的*
字符,则将其视为通配符位置。在加载配置时,通配符将被展开,以便也检查直接的子目录。当存在多个配置属性源时,通配符位置在 Kubernetes 等环境中特别有用。
例如,如果你有一些 Redis 配置和一些 MySQL 配置,那么你可能希望将这两个配置分开,同时要求这两个配置都存在于application.properties
文件中。这可能会导致两个单独的application.properties
文件安装在不同的位置,例如/config/redis/application.properties
和/config/mysql/application.properties
。在这种情况下,具有通配符位置config/*/
,将导致这两个文件都被处理。
默认情况下, Spring 引导在默认搜索位置中包括config/*/
。这意味着将搜索 jar 之外/config
目录的所有子目录。
你可以使用spring.config.location
和spring.config.additional-location
属性自己使用通配符位置。
通配符位置必须只包含一个* ,并且对于目录搜索位置以*/ 结尾,对于文件搜索位置以*/<filename> 结尾。通配符位置根据文件名的绝对路径按字母顺序排序。 |
---|
通配符位置仅与外部目录一起工作。 在 classpath: 位置中不能使用通配符。 |
---|
# 2.3.3.配置文件特定的文件
除了application
属性文件外, Spring 引导还将尝试使用命名约定application-{profile}
加载配置文件特定的文件。例如,如果应用程序激活名为prod
的配置文件并使用 YAML 文件,那么application.yml
和application-prod.yml
都将被考虑。
配置文件特定的属性是从与标准application.properties
相同的位置加载的,配置文件特定的文件总是覆盖非特定的文件。如果指定了多个配置文件,则应用“最后胜出”策略。例如,如果配置文件prod,live
由spring.profiles.active
属性指定,则application-prod.properties
中的值可以被application-live.properties
中的值覆盖。
Last-wins 策略适用于位置组级别。 a spring.config.location ofclasspath:/cfg/,classpath:/ext/ 将不会具有与classpath:/cfg/;classpath:/ext/ 相同的覆盖规则。例如,继续上面的 prod,live 示例,我们可能有以下文件:<br/>/cfg<br/> application-live.properties<br/>/ext<br/> application-live.properties<br/> application-prod.properties<br/> 当我们有 spring.config.location ofclasspath:/cfg/,classpath:/ext/ 时,我们在所有/ext 文件之前处理所有文件: 1. /cfg/application-live.properties 2。 /ext/application-prod.properties 3。 /ext/application-live.properties 当我们有 classpath:/cfg/;classpath:/ext/ 代替(用; 分隔符)时,我们处理/cfg 和/ext 在同一水平上:1。 /ext/application-prod.properties 2。 /cfg/application-live.properties 3。 /ext/application-live.properties |
---|
Environment
具有一组默认配置文件(默认情况下,[default]
),如果未设置活动配置文件,则使用这些配置文件。换句话说,如果没有配置文件被显式激活,那么application-default
中的属性将被考虑。
属性文件只加载一次。 如果你已经直接imported配置文件特定的属性文件,那么它将不会被导入第二次。 |
---|
# 2.3.4.导入附加数据
应用程序属性可以使用spring.config.import
属性从其他位置导入更多配置数据。导入在被发现时进行处理,并被视为在声明导入的文档下面插入的附加文档。
例如,你的 Classpath application.properties
文件中可能包含以下内容:
属性
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
Yaml
spring:
application:
name: "myapp"
config:
import: "optional:file:./dev.properties"
这将触发当前目录中dev.properties
文件的导入(如果存在这样的文件)。导入的dev.properties
中的值将优先于触发导入的文件。在上面的示例中,dev.properties
可以将spring.application.name
重新定义为不同的值。
一次进口,无论申报了多少次,都只能进口一次。在 属性/YAML 文件中的单个文档中定义的导入顺序并不重要。例如,下面的两个示例产生相同的结果:
属性
spring.config.import=my.properties
my.property=value
Yaml
spring:
config:
import: "my.properties"
my:
property: "value"
属性
my.property=value
spring.config.import=my.properties
Yaml
my:
property: "value"
spring:
config:
import: "my.properties"
在上述两个示例中,来自my.properties
文件的值将优先于触发其导入的文件。
可以在一个spring.config.import
键下指定多个位置。位置将按照定义的顺序进行处理,以后的导入将优先处理。
在适当的情况下,特定于配置文件的变体也被考虑用于导入。 上面的示例将导入 my.properties 以及任何my-<profile>.properties 变体。 |
---|
Spring 引导包括可插入的 API,允许支持各种不同的位置地址。 默认情况下可以导入 Java 属性、YAML 和“配置树”。 第三方 JAR 可以提供对额外技术的支持(不要求文件是本地的)。 例如,你可以想象配置数据来自外部商店,例如 Consul、Apache ZooKeeper 或 Netflix Archaius。 如果你想支持自己的位置,请参阅 ConfigDataLocationResolver 和ConfigDataLoader 包中的类。 |
---|
# 2.3.5.导入无扩展文件
一些云平台不能向卷安装的文件添加文件扩展名。要导入这些无扩展文件,你需要给 Spring 引导一个提示,以便它知道如何加载它们。你可以通过在方括号中添加扩展提示来实现此目的。
例如,假设你有一个/etc/config/myconfig
文件,你希望将其导入为 YAML。你可以使用以下方法从你的application.properties
导入它:
属性
spring.config.import=file:/etc/config/myconfig[.yaml]
Yaml
spring:
config:
import: "file:/etc/config/myconfig[.yaml]"
# 2.3.6.使用配置树
在云平台(如 Kubernetes)上运行应用程序时,通常需要读取平台提供的配置值。为此目的使用环境变量并不少见,但这可能有缺点,特别是如果值应该保密的话。
作为环境变量的一种替代方法,许多云平台现在允许你将配置映射到已安装的数据卷中。例如,Kubernetes 可以同时挂载[ConfigMaps
](https:/kubernetes.io/DOCS/tasks/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap)和[Secrets
(https://kubernetes.io/DOCS/concepts/configuration/sects/secret/#using-secrets-as-files-from-a-pod)。
可以使用两种常见的卷安装模式:
一个文件包含一组完整的属性(通常写为 YAML)。
多个文件被写入目录树,文件名成为“键”,内容成为“值”。
对于第一种情况,可以使用spring.config.import
直接导入 YAML 或属性文件,如above所述。对于第二种情况,你需要使用configtree:
前缀,以便 Spring 引导知道需要将所有文件作为属性公开。
举个例子,让我们假设 Kubernetes 已经安装了以下卷:
etc/
config/
myapp/
username
password
username
文件的内容将是一个配置值,而password
的内容将是一个秘密。
要导入这些属性,可以将以下内容添加到application.properties
或application.yaml
文件中:
属性
spring.config.import=optional:configtree:/etc/config/
Yaml
spring:
config:
import: "optional:configtree:/etc/config/"
然后,你可以以通常的方式从Environment
访问或注入myapp.username
和myapp.password
属性。
配置树下的文件夹形成属性名。 在上面的示例中,要访问属性为 username 和password ,你可以将spring.config.import 设置为optional:configtree:/etc/config/myapp 。 |
---|
例如,在上面的示例中,一个名为myapp.username 的文件在/etc/config 中将在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/*/
Yaml
spring:
config:
import: "optional:configtree:/etc/config/*/"
这将添加db.username
,db.password
,mq.username
和mq.password
属性。
使用通配符加载的目录是按字母顺序排序的。 如果需要不同的顺序,那么应该将每个位置作为单独的导入列表 |
---|
配置树也可以用于 Docker 秘密。当 Docker Swarm 服务被授予对秘密的访问权限时,该秘密将被装载到容器中。例如,如果一个名为db.password
的秘密被安装在位置/run/secrets/
上,则可以使用以下方法使db.password
环境对 Spring 环境可用:
属性
spring.config.import=optional:configtree:/run/secrets/
Yaml
spring:
config:
import: "optional:configtree:/run/secrets/"
# 2.3.7.财产占位符
application.properties
和application.yml
中的值在使用时会通过现有的Environment
进行过滤,因此你可以引用以前定义的值(例如,从系统属性)。标准的${name}
属性占位符语法可以在值的任何地方使用。
例如,下面的文件将app.description
设置为“MyApp 是一个 Spring 启动应用程序”:
属性
app.name=MyApp
app.description=${app.name} is a Spring Boot application
Yaml
app:
name: "MyApp"
description: "${app.name} is a Spring Boot application"
你也可以使用此技术来创建现有 Spring 引导属性的“短”变体。 有关详细信息,请参见 *howto.html*how-to。 |
---|
# 2.3.8.使用多文档文件
Spring 启动允许将单个物理文件拆分成多个独立添加的逻辑文档。文件的处理顺序是从上到下的。后面的文档可以覆盖前面的文档中定义的属性。
对于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 或@TestPropertySource 注释无法加载多文档属性文件。 |
---|
# 2.3.9.活化特性
有时,只有在满足某些条件时才激活给定的一组属性是有用的。例如,你可能拥有仅在特定配置文件处于活动状态时才相关的属性。
可以使用spring.config.activate.*
有条件地激活属性文档。
以下激活属性可用:
Property | 注 |
---|---|
on-profile | 必须匹配才能使文档处于活动状态的配置文件表达式。 |
on-cloud-platform | 要使文档处于活动状态,必须检测到的CloudPlatform 。 |
例如,下面指定了第二个文档只有在 Kubernetes 上运行时才处于活动状态,并且只有当“prod”或“staging”配置文件处于活动状态时才处于活动状态:
Properties
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
Yaml
myprop:
"always-set"
---
spring:
config:
activate:
on-cloud-platform: "kubernetes"
on-profile: "prod | staging"
myotherprop: "sometimes-set"
# 2.4.加密属性
Spring 引导不提供任何内置的对加密属性值的支持,但是,它确实提供了修改包含在 Spring Environment
中的值所必需的钩点。EnvironmentPostProcessor
接口允许你在应用程序启动之前操作Environment
。详见howto.html。
如果你需要一种安全的方式来存储凭据和密码,Spring Cloud Vault (opens new window)项目提供了在HashiCorp 保险库 (opens new window)中存储外部化配置的支持。
# 2.5.与 YAML 合作
YAML (opens new window)是 JSON 的超集,因此,是用于指定分层配置数据的一种方便的格式。只要在 Classpath 上有SnakeYAML (opens new window)库,SpringApplication
类就会自动支持 YAML 作为属性的替代。
如果使用“starter”,那么 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]
dereferencers 的属性键。例如,考虑以下 YAML:
my:
servers:
- "dev.example.com"
- "another.example.com"
前面的示例将转换为以下属性:
my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用[index] 表示法的属性可以使用 Spring boot 的类型安全配置属性对象绑定到 JavaSet 对象。Binder class.有关更多详细信息,请参见下面的“类型安全配置属性”一节。 |
---|
无法通过使用@PropertySource 或@TestPropertySource 注释来加载 YAML 文件。因此,在需要以这种方式加载值的情况下,需要使用属性文件。 |
---|
# 2.5.2.直接加载 YAML
Spring Framework 提供了两个方便的类,它们可以用来加载 YAML 文档。YamlPropertiesFactoryBean
将 YAML 加载为Properties
,YamlMapFactoryBean
将 YAML 加载为Map
。
如果希望以 Spring PropertySource
的形式加载 YAML,也可以使用YamlPropertySourceLoader
类。
# 2.6.配置随机值
RandomValuePropertySource
对于注入随机值(例如,注入到秘密或测试用例中)很有用。它可以产生整数、长、UUID 或字符串,如以下示例所示:
Properties
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]}
Yaml
my:
secret: "${random.value}"
number: "${random.int}"
bignumber: "${random.long}"
uuid: "${random.uuid}"
number-less-than-ten: "${random.int(10)}"
number-in-range: "${random.int[1024,65536]}"
random.int*
语法是OPEN value (,max) CLOSE
,其中OPEN,CLOSE
是任意字符,value,max
是整数。如果提供了max
,则value
是最小值,max
是最大值(不包含)。
# 2.7.配置系统环境属性
Spring 引导支持为环境属性设置前缀。如果系统环境由具有不同配置要求的多个 Spring 引导应用程序共享,则这是有用的。系统环境属性的前缀可以直接设置在SpringApplication
上。
例如,如果你将前缀设置为input
,那么在系统环境中,诸如remote.timeout
之类的属性也将解析为input.remote.timeout
。
# 2.8.类型安全配置属性
使用@Value("${property}")
注释来注入配置属性有时会很麻烦,尤其是在使用多个属性或数据本质上是分层的情况下。 Spring 引导提供了一种处理属性的替代方法,该方法允许强类型 bean 控制和验证应用程序的配置。
另请参见[@Value 和类型安全配置属性之间的区别]。 |
---|
# 2.8.1.JavaBean 属性绑定
可以绑定声明标准 JavaBean 属性的 Bean,如下例所示:
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
// getters / setters...
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
// getters / setters...
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
}
前面的 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 引导中可用的@ConfigurationProperties 类的属性(通过属性文件、YAML 文件、环境变量和其他机制进行配置)是公共 API,但是类本身的访问器(getters/setters)并不是直接使用的。 |
---|
这种安排依赖于缺省的空构造函数,getter 和 setter 通常是强制性的,因为绑定是通过标准的 Java bean 属性描述符进行的,就像在 Spring MVC 中一样。 在以下情况下,可以省略 setter: * 映射,只要它们被初始化,需要一个 getter,但不一定是 setter,因为它们可以通过绑定程序进行变异, * 集合和数组可以通过索引(通常使用 YAML)或使用一个逗号分隔的值(属性)进行访问, 在后一种情况下,setter 是强制性的。 我们建议总是为这样的类型添加一个 setter。 如果你初始化一个集合,请确保它不是不可变的(如前面的示例), * 如果初始化了嵌套的 POJO 属性(如前面示例中的 Security 字段),setter 不是必需的。如果你想让 binder 使用其默认构造函数动态地创建实例,你需要一个 setter。 有些人使用 Project Lombok 来自动添加 getter 和 setter。 确保 Lombok 不会为这样的类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。 最后,只考虑标准的 Java Bean 属性,不支持对静态属性的绑定。 |
---|
# 2.8.2.构造函数绑定
上一节中的示例可以以一种不可更改的方式重写,如下例所示:
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("my.service")
public class MyProperties {
// fields...
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
// getters...
public boolean isEnabled() {
return this.enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
// fields...
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;
}
// getters...
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public List<String> getRoles() {
return this.roles;
}
}
}
在此设置中,@ConstructorBinding
注释用于指示应该使用构造函数绑定。这意味着绑定器将期望找到一个具有你希望绑定的参数的构造函数。如果你使用的是 Java16 或更高版本,那么构造函数绑定可以用于记录。在这种情况下,除非你的记录有多个构造函数,否则不需要使用@ConstructorBinding
。
@ConstructorBinding
类的嵌套成员(例如上面示例中的Security
)也将通过其构造函数绑定。
可以使用@DefaultValue
指定默认值,并且将应用相同的转换服务来强制将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.启用 @configrationProperties-注释类型
Spring Boot 提供了用于绑定@ConfigurationProperties
类型并将它们注册为 bean 的基础设施。你可以逐个类地启用配置属性,也可以启用与组件扫描工作方式类似的配置属性扫描。
有时,用@ConfigurationProperties
注释的类可能不适合扫描,例如,如果你正在开发自己的自动配置,或者希望有条件地启用它们。在这些情况下,使用@EnableConfigurationProperties
注释指定要处理的类型列表。这可以在任何@Configuration
类上完成,如以下示例所示:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {
}
要使用配置属性扫描,请将@ConfigurationPropertiesScan
注释添加到应用程序中。通常,它被添加到用@SpringBootApplication
注释的主应用程序类中,但它可以添加到任何@Configuration
类中。默认情况下,将从声明注释的类的包中进行扫描。如果你想要定义要扫描的特定包,可以按照以下示例中所示的方式进行:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {
}
当使用配置属性扫描或通过@EnableConfigurationProperties 注册@ConfigurationProperties 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.使用 @configrationProperties 注释类型
这种配置风格在SpringApplication
外部 YAML 配置中尤其适用,如以下示例所示:
my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"
要使用@ConfigurationProperties
bean,你可以以与任何其他 Bean 相同的方式注入它们,如以下示例所示:
import org.springframework.stereotype.Service;
@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 可以使用这些文件为你自己的密钥提供自动补全功能。有关详细信息,请参见appendix。 |
---|
# 2.8.5.第三方配置
除了使用@ConfigurationProperties
对类进行注释外,还可以在 public@Bean
方法上使用它。当你希望将属性绑定到你无法控制的第三方组件时,这样做会特别有用。
要从Environment
属性配置 Bean,请将@ConfigurationProperties
添加到其 Bean 注册中,如以下示例所示:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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 属性名称之间进行精确匹配。这很有用的常见示例包括 dash 分隔的环境属性(例如,context-path
绑定到contextPath
)和大写的环境属性(例如,PORT
绑定到port
)。
例如,考虑以下@ConfigurationProperties
类:
import org.springframework.boot.context.properties.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;
}
}
使用前面的代码,可以使用以下属性名称:
Property | 注 |
---|---|
my.main-project.person.first-name | 烤肉串的情况下,这是建议在.properties 和.yml 文件中使用。 |
my.main-project.person.firstName | 标准的驼峰大小写语法。 |
my.main-project.person.first_name | 下划线符号,这是在.properties 和.yml 文件中使用的一种替代格式。 |
MY_MAINPROJECT_PERSON_FIRSTNAME | 大写格式,这是建议时,使用系统环境变量. |
注释prefix 的值必须以烤肉串为例(小写并用- 分隔,如my.main-project.person )。 |
---|
Property Source | 简单 | List |
---|---|---|
Properties Files | camel case、kebab case 或下划线符号 | Standard list syntax using [ ] or comma-separated values |
YAML Files | camel case、kebab case 或下划线符号 | Standard YAML list syntax or comma-separated values |
Environment Variables | 大写格式,下划线作为分隔符(参见来自环境变量的绑定)。 | Numeric values surrounded by underscores (see 来自环境变量的绑定) |
System properties | camel case、kebab case 或下划线符号 | Standard list syntax using [ ] or comma-separated values |
我们建议在可能的情况下,将属性存储在小写字母的烤肉串格式中,例如my.person.first-name=Rod 。 |
---|
# 绑定地图
当绑定到Map
属性时,你可能需要使用特殊的括号表示法,以便保留原来的key
值。如果键不被[]
包围,则删除所有不是 alpha-numeric,-
或.
的字符。
例如,考虑将以下属性绑定到Map<String,String>
:
Properties
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
Yaml
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
对于 YAML 文件,括号中需要用引号包围,以便正确解析这些键。 |
---|
上面的属性将绑定到Map
,其中/key1
、/key2
和key3
作为映射中的键。斜杠已从key3
中删除,因为它没有被方括号包围。
如果你的key
包含.
并且绑定到非标量值,那么你有时也可能需要使用括号表示法。例如,将a.b=c
绑定到Map<String, Object>
将返回一个带有条目{"a"={"b"="c"}}
的映射,而[a.b]=c
将返回一个带有条目{"a.b"="c"}
的映射。
# Binding from Environment Variables
大多数操作系统都对可用于环境变量的名称施加了严格的规则。例如,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
对象,其name
和description
属性默认为null
。下面的示例公开了来自MyProperties
的MyPojo
对象的列表:
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
考虑以下配置:
Properties
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
Yaml
my:
list:
- name: "my name"
description: "my description"
---
spring:
config:
activate:
on-profile: "dev"
my:
list:
- name: "my another name"
如果dev
配置文件不是活动的,则MyProperties.list
包含一个MyPojo
条目,如前面定义的那样。但是,如果启用了dev
配置文件,则list
Still只包含一个条目(名称为my another name
,描述为null
)。此配置不是将第二个MyPojo
实例添加到列表中,并且它不合并项。
当在多个配置文件中指定List
时,将使用优先级最高的那个配置文件。考虑以下示例:
Properties
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
Yaml
my:
list:
- name: "my name"
description: "my description"
- name: "another name"
description: "another description"
---
spring:
config:
activate:
on-profile: "dev"
my:
list:
- name: "my another name"
在前面的示例中,如果dev
配置文件是活动的,则MyProperties.list
包含OneMyPojo
条目(名称为my another name
,描述为null
)。对于 YAML,逗号分隔的列表和 YAML 列表都可以用于完全覆盖列表的内容。
对于Map
属性,你可以绑定来自多个源的属性值。但是,对于多个源中的相同属性,使用具有最高优先级的属性。下面的示例公开了来自MyProperties
的Map<String, MyPojo>
:
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
private final Map<String, MyPojo> map = new LinkedHashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
考虑以下配置:
Properties
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
Yaml
my:
map:
key1:
name: "my name 1"
description: "my description 1"
---
spring:
config:
activate:
on-profile: "dev"
my:
map:
key1:
name: "dev name 1"
key2:
name: "dev name 2"
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 当绑定到@ConfigurationProperties
bean 时,引导尝试强制将外部应用程序属性转换为正确的类型。如果需要自定义类型转换,可以提供ConversionService
Bean(带有 Bean 名为conversionService
)或自定义属性编辑器(通过CustomEditorConfigurer
Bean)或自定义Converters
(带有 Bean 注释为@ConfigurationPropertiesBinding
的定义)。
由于在应用程序生命周期的很早阶段就请求了这个 Bean,因此请确保限制你的ConversionService 正在使用的依赖关系。通常,你所需要的任何依赖项可能在创建时没有完全初始化。 如果配置键强制不需要它,并且仅依赖于用 @ConfigurationPropertiesBinding 限定的自定义转换器,那么你可能想要重命名自定义ConversionService 。 |
---|
# 转换持续时间
Spring Boot 具有用于表示持续时间的专用支持。如果你公开了java.time.Duration
属性,那么应用程序属性中的以下格式是可用的:
一个常规的
long
表示(使用毫秒作为默认单位,除非指定了@DurationUnit
)标准的 ISO-8601 格式[由
java.time.Duration
使用](https://DOCS.oracle.com/javase/8/DOCS/api/java/time/duration.html#parse-java.lang.charsequence-)一种更可读的格式,其中值和单位是耦合的(
10s
表示 10 秒)
考虑以下示例:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
public class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
// getters / setters...
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
重写,如上面的示例所示。
如果你更喜欢使用构造函数绑定,则可以公开相同的属性,如下例所示:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
// fields...
private final Duration sessionTimeout;
private final Duration readTimeout;
public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
@DefaultValue("1000ms") Duration readTimeout) {
this.sessionTimeout = sessionTimeout;
this.readTimeout = readTimeout;
}
// getters...
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
}
如果要升级Long 属性,请确保在不是毫秒的情况下定义该单元(使用@DurationUnit )。这样做可以提供透明的升级路径,同时支持更丰富的格式。 |
---|
# 转换周期
除了持续时间, Spring 启动还可以使用java.time.Period
类型。在应用程序属性中可以使用以下格式:
一个常规的
int
表示(使用天数作为默认单位,除非指定了@PeriodUnit
)标准的 ISO-8601 格式[由
java.time.Period
使用](https://DOCS.oracle.com/javase/8/DOCS/api/java/time/period.html#parse-java.lang.charsequence-)一种更简单的格式,其中值和单位对是耦合的(
1y3d
表示 1 年零 3 天)
以下单元以简单格式提供支持:
多年来
y
m
持续数月w
周天
d
java.time.Period 类型实际上不会存储周数,它是一种表示“7 天”的快捷方式。 |
---|
# 转换数据大小
Spring 框架具有DataSize
值类型,该类型表示以字节为单位的大小。如果你公开了DataSize
属性,那么应用程序属性中的以下格式是可用的:
一个常规的
long
表示(使用字节作为默认单位,除非指定了@DataSizeUnit
)一种更可读的格式,其中的值和单位是耦合的(
10MB
表示 10 兆)
考虑以下示例:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties("my")
public class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
// getters/setters...
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;
}
}
要指定 10MB 的缓冲区大小,10
和10MB
是等效的。256 字节的大小阈值可以指定为256
或256B
。
你也可以使用任何支持的单位。这些是:
B
表示字节KB
表示千字节MB
代表兆字节GB=“936”/>
TB
用于 TB
默认的单元是字节,可以使用@DataSizeUnit
重写,如上面的示例所示。
如果你更喜欢使用构造函数绑定,则可以公开相同的属性,如下例所示:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
// fields...
private final DataSize bufferSize;
private final DataSize sizeThreshold;
public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
@DefaultValue("512B") DataSize sizeThreshold) {
this.bufferSize = bufferSize;
this.sizeThreshold = sizeThreshold;
}
// getters...
public DataSize getBufferSize() {
return this.bufferSize;
}
public DataSize getSizeThreshold() {
return this.sizeThreshold;
}
}
如果你正在升级Long 属性,请确保定义不是字节的单元(使用@DataSizeUnit )。这样做可以提供一个透明的升级路径,同时支持更丰富的格式。 |
---|
# 2.8.9.@configrationProperties 验证
Spring 每当使用 Spring 的@Validated
注释对类进行注释时,引导都会尝试验证@ConfigurationProperties
类。你可以直接在你的配置类上使用 JSR-303javax.validation
约束注释。要做到这一点,请确保在你的 Classpath 上有一个兼容的 JSR-303 实现,然后将约束注释添加到你的字段中,如以下示例所示:
import java.net.InetAddress;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
// getters/setters...
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
}
你还可以通过注释@Bean 方法来触发验证,该方法使用@Validated 创建配置属性。 |
---|
要确保始终为嵌套属性触发验证,即使没有找到属性,也必须用@Valid
注释相关字段。下面的示例以前面的MyProperties
示例为基础:
import java.net.InetAddress;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
// getters/setters...
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
@NotEmpty
private String username;
// getters/setters...
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
}
}
还可以通过创建一个名为configurationPropertiesValidator
的 Bean 定义来添加自定义 Spring Validator
。应该声明@Bean
方法static
。配置属性验证器是在应用程序生命周期的早期创建的,将@Bean
方法声明为 static 可以使 Bean 在无需实例化@Configuration
类的情况下被创建。这样做可以避免早期实例化可能带来的任何问题。
spring-boot-actuator 模块包括一个公开所有@ConfigurationProperties bean 的端点。将你的 Web 浏览器指向 /actuator/configprops 或使用等效的 JMX 端点。有关详细信息,请参见“应用程序属性文件一节。 |
---|
# 2.8.10.@configrationProperties vs.@value
@Value
注释是一个核心容器特性,它不提供与类型安全配置属性相同的特性。下表总结了@ConfigurationProperties
和@Value
支持的功能:
Feature | @ConfigurationProperties | @Value |
---|---|---|
Relaxed binding | Yes | Limited(见note below) |
Meta-data support | Yes | 无 |
SpEL evaluation | No | 是的 |
如果你确实想使用@Value ,我们建议你使用它们的规范形式(kebab-case 只使用小写字母)来引用属性名称,这将允许 Spring 引导使用与放松绑定 @ConfigurationProperties 时相同的逻辑,例如,, @Value("{demo.item-price}") 将从application.properties 文件中获取demo.item-price 和demo.itemPrice 窗体,以及从系统环境中获取DEMO_ITEMPRICE 。如果使用 @Value("{demo.itemPrice}") 代替,则不会考虑demo.item-price 和DEMO_ITEMPRICE 。 |
---|
如果你为自己的组件定义了一组配置键,那么我们建议你将它们分组到一个用@ConfigurationProperties
注释的 POJO 中。这样做将为你提供结构化的、类型安全的对象,你可以将其注入到自己的 bean 中。
在解析这些文件和填充环境时,不会处理SpEL
中的应用程序属性文件表达式。但是,可以在@Value
中写入SpEL
表达式。如果来自应用程序属性文件的属性的值是SpEL
表达式,则在通过@Value
使用时将对其进行求值。
# 3. 配置文件
Spring 配置文件提供了一种方法来隔离应用程序配置的部分,并使其仅在某些环境中可用。任何@Component
、@Configuration
或@ConfigurationProperties
都可以用@Profile
标记,以在加载时进行限制,如以下示例所示:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
如果@Configuration属性 bean 是通过@EnableConfiguration属性 注册的,而不是通过自动扫描,则@Profile 注释需要在具有@Configuration 注释的类上指定。在 @Configuration属性 被扫描的情况下,@Profile 可以在@Configuration属性 类本身上指定。 |
---|
你可以使用spring.profiles.active``Environment
属性来指定哪些配置文件是活动的。你可以用本章前面描述的任何一种方式指定属性。例如,你可以在application.properties
中包含它,如下例所示:
属性
spring.profiles.active=dev,hsqldb
Yaml
spring:
profiles:
active: "dev,hsqldb"
你还可以通过使用以下开关在命令行中指定它:--spring.profiles.active=dev,hsqldb
。
如果没有配置文件处于活动状态,则启用默认配置文件。默认配置文件的名称是default
,可以使用spring.profiles.default``Environment
属性对其进行调优,如以下示例所示:
属性
spring.profiles.default=none
Yaml
spring:
profiles:
default: "none"
# 3.1.添加活动配置文件
PropertySource
属性遵循与其他属性相同的排序规则:最高的PropertySource
属性获胜。这意味着你可以使用命令行开关在application.properties
中指定活动配置文件,然后在替换中指定它们。
有时,将添加的属性用于活动配置文件而不是替换它们是很有用的。SpringApplication
入口点有一个用于设置附加配置文件的 Java API(即,在那些由spring.profiles.active
属性激活的配置文件之上)。参见SpringApplication (opens new window)中的setAdditionalProfiles()
方法。在下一节中描述的配置文件组也可以用于在给定的配置文件处于活动状态时添加活动配置文件。
# 3.2.配置文件组
有时,你在应用程序中定义和使用的配置文件粒度太细,使用起来很麻烦。例如,你可能有proddb
和prodmq
配置文件,用于独立启用数据库和消息传递功能。
为了帮助实现这一点, Spring Boot 允许你定义配置文件组。配置文件组允许你为相关的配置文件组定义逻辑名称。
例如,我们可以创建一个production
组,该组由我们的proddb
和prodmq
配置文件组成。
属性
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
Yaml
spring:
profiles:
group:
production:
- "proddb"
- "prodmq"
我们的应用程序现在可以使用--spring.profiles.active=production
来启动production
、proddb
和prodmq
配置文件。
# 3.3.以编程方式设置配置文件
你可以通过在应用程序运行之前调用SpringApplication.setAdditionalProfiles(…)
以编程方式设置活动配置文件。也可以通过使用 Spring 的ConfigurableEnvironment
接口激活配置文件。
# 3.4.特定于配置文件的配置文件
application.properties
(或application.yml
)和通过@Configuration属性
引用的文件的配置文件特定变体都被视为文件并加载。详见“配置文件特定的文件”。
# 4. 伐木
Spring 启动对所有内部日志使用共享日志记录 (opens new window),但对底层日志实现保持开放。为Java util 日志记录 (opens new window)、Log4J2 (opens new window)和@SpringBootApplication
提供了默认配置。在每种情况下,记录器都被预先配置为使用控制台输出,可选的文件输出也可用。
默认情况下,如果你使用“starters”,则会使用回录来进行日志记录。还包括适当的回登路由,以确保使用 Java util 日志、Commons 日志、log4j 或 SLF4j 的依赖库都能正确工作。
有很多日志框架可用于 Java。 如果上面的列表看起来令人困惑,请不要担心。 通常,你不需要更改日志依赖项,并且 Spring 引导默认值工作得很好。 |
---|
当你将应用程序部署到 Servlet 容器或应用程序服务器时,使用 Java util Logging API 执行的日志记录不会路由到应用程序的日志中。 这将防止由容器执行的日志或已部署到它的其他应用程序出现在应用程序的日志中。 |
---|
# 4.1.日志格式
Spring 引导的默认日志输出类似于以下示例:
2019-03-05 10:57:51.112 INFO 45469 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1358 ms
2019-03-05 10:57:51.698 INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2019-03-05 10:57:51.702 INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
以下项目是输出:
日期和时间:毫秒精度和容易排序.
日志级别:
ERROR
,WARN
,INFO
,下一节,或TRACE
。进程 ID。
一个
---
分隔符,用于区分实际日志消息的开始。线程名称:括在方括号中(对于控制台输出,可能会被截断)。
日志程序名称:这通常是源类名称(通常是缩写)。
日志消息。
注销没有FATAL 级别。它被映射到 ERROR 。 |
---|
# 4.2.控制台输出
默认的日志配置会在消息写入时将消息回传到控制台。默认情况下,将记录ERROR
-level、WARN
-level 和INFO
-level 消息。你还可以通过使用--debug
标志启动应用程序来启用“调试”模式。
$ java -jar myapp.jar --debug
你还可以在application.properties 中指定debug=true 。 |
---|
当调试模式被启用时,核心记录器(嵌入式容器、 Hibernate 和 Spring 引导)的选择被配置为输出更多信息。启用调试模式将不是配置你的应用程序以DEBUG
级别记录所有消息。
或者,你可以通过使用--trace
标志(或trace=true
中的application.properties
)启动应用程序来启用“跟踪”模式。这样做可以实现对核心记录器(嵌入式容器、 Hibernate 模式生成和整个 Spring 投资组合)的选择的跟踪日志记录。
# 4.2.1.颜色编码输出
如果你的终端支持 ANSI,则使用颜色输出来提高可读性。可以将spring.output.ansi.enabled
设置为支持的值 (opens new window)以覆盖自动检测。
颜色编码是通过使用%clr
转换字来配置的。在最简单的形式中,转换器根据日志级别对输出进行着色,如以下示例所示:
%clr(%5p)
下表描述了日志级别到颜色的映射:
电平 | Color |
---|---|
FATAL | Red |
ERROR | Red |
WARN | Yellow |
INFO | Green |
DEBUG | Green |
TRACE | Green |
或者,你可以通过将其作为转换的选项来指定应该使用的颜色或样式。例如,要使文本为黄色,请使用以下设置:
%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}
支持以下颜色和样式:
blue
cyan
faint
green
magenta
red
yellow
# 4.3.文件输出
默认情况下, Spring 只引导日志到控制台,不写日志文件。如果要在控制台输出之外写日志文件,则需要设置logging.file.name
或logging.file.path
属性(例如,在application.properties
中)。
下表显示了如何一起使用logging.*
属性:
logging.file.name | logging.file.path | Example | 说明 |
---|---|---|---|
(none) | (none) | 仅用于控制台日志记录。 | |
Specific file | (none) | my.log | 写入指定的日志文件。 名称可以是确切的位置或相对于当前目录的位置。 |
(none) | Specific directory | /var/log | 将spring.log 写入指定的目录。名称可以是确切的位置或相对于当前目录的位置。 |
当日志文件达到 10MB 时会旋转,并且与控制台输出一样,ERROR
-level,WARN
-level 和INFO
-level 消息在默认情况下会被记录。
日志记录属性独立于实际的日志基础设施。因此,特定的配置键(例如 用于回登)不受 Spring 引导的管理。 |
---|
# 4.4.文件旋转
如果你使用的是回录,那么可以使用application.properties
或application.yaml
文件来微调日志旋转设置。对于所有其他日志系统,你将需要直接自己配置旋转设置(例如,如果你使用 log4j2,那么你可以添加application.yaml
或log4j2-spring.xml
文件)。
支持以下旋转策略属性:
Name | 说明 |
---|---|
logging.logback.rollingpolicy.file-name-pattern | 用于创建日志归档的文件名模式。 |
logging.logback.rollingpolicy.clean-history-on-start | 如果日志归档清理应该在应用程序启动时进行。 |
logging.logback.rollingpolicy.max-file-size | 存档前日志文件的最大大小。 |
logging.logback.rollingpolicy.total-size-cap | 在被删除之前,日志档案可以占用的最大大小。 |
logging.logback.rollingpolicy.max-history | 保存的归档日志文件的最大数量(默认为 7)。 |
# 4.5.日志级别
通过使用logging.level.<logger-name>=<level>
,所有受支持的日志系统都可以在 Spring Environment
(例如,在application.properties
)中设置日志程序级别,其中application.properties
是跟踪、调试、信息、警告、错误、致命或关闭。root
记录器可以通过使用logging.level.root
进行配置。
下面的示例在application.properties
中显示了潜在的日志设置:
属性
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
Yaml
logging:
level:
root: "warn"
org.springframework.web: "debug"
org.hibernate: "error"
也可以使用环境变量设置日志记录级别。例如,LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG
将org.springframework.web
设置为DEBUG
。
上述方法仅对包级日志记录有效。 由于放松绑定总是将环境变量转换为小写,因此不可能以这种方式为单个类配置日志记录。 如果需要为一个类配置日志记录,可以使用[the SPRING_APPLICATION_JSON ]变量。 |
---|
# 4.6.日志组
能够将相关的记录器组合在一起,以便可以同时对它们进行配置,这通常是很有用的。例如,你通常可能会更改全部 Tomcat 相关日志记录器的日志记录级别,但是你不能很容易地记住顶级包。
为了解决这个问题, Spring Boot 允许你在 Spring Environment
中定义日志记录组。例如,下面是如何将“ Tomcat”组添加到application.properties
中来定义该组的方法:
属性
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
Yaml
logging:
group:
tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"
一旦定义好,你就可以用一行来改变组中所有记录器的级别:
Properties
logging.level.tomcat=trace
Yaml
logging:
level:
tomcat: "trace"
Spring 启动包括以下预定义的记录组,这些记录组可以开箱即用:
Name | 伐木工 |
---|---|
web | org.springframework.core.codec , org.springframework.http , org.springframework.web , org.springframework.boot.actuate.endpoint.web , org.springframework.boot.web.servlet.ServletContextInitializerBeans |
sql | org.springframework.jdbc.core , org.hibernate.SQL , org.jooq.tools.LoggerListener |
# 4.7.使用日志关闭钩子
为了在应用程序终止时释放日志资源,提供了一个关闭钩子,该钩子将在 JVM 退出时触发日志系统清理。除非你的应用程序被部署为 WAR 文件,否则此关闭钩子将自动注册。如果你的应用程序具有复杂的上下文层次结构,那么关闭钩子可能无法满足你的需求。如果没有,请禁用关闭钩子,并调查底层日志系统直接提供的选项。例如,Logback 提供上下文选择器 (opens new window),允许在自己的上下文中创建每个记录器。你可以使用logging.register-shutdown-hook
属性禁用关闭钩子。将其设置为false
将禁用注册。你可以在false
或application.yaml
文件中设置该属性:
Properties
logging.register-shutdown-hook=false
Yaml
logging:
register-shutdown-hook: false
# 4.8.自定义日志配置
各种日志记录系统可以通过在 Classpath 上包括适当的库来激活,并且可以通过在 Classpath 的根目录中或在由下面的 Spring 指定的位置中提供适当的配置文件来进一步定制属性:。
可以使用org.springframework.boot.logging.LoggingSystem
系统属性强制 Spring 引导使用特定的日志系统。该值应该是LoggingSystem
实现的完全限定类名称。还可以使用@EnableScheduling
的值来完全禁用 Spring Boot 的日志配置。
由于日志是初始化的在此之前ApplicationContext 已被创建,因此不可能在 Spring @PropertySources 文件中从@Configuration 文件中控制日志。在此之前改变日志系统或完全禁用它的唯一方法是通过系统属性。 |
---|
根据你的日志记录系统,将加载以下文件:
Logging System | 定制 |
---|---|
Logback | logback-spring.xml ,logback-spring.groovy ,logback.xml ,或logback.groovy |
Log4j2 | log4j2-spring.xml 或log4j2.xml |
JDK (Java Util Logging) | logging.properties |
如果可能,我们建议你在日志配置中使用-spring 变量(例如,logback-spring.xml 而不是logback.xml )。如果使用标准配置位置, Spring 不能完全控制日志初始化。 |
---|
在从“可执行文件 jar”运行时,Java util 日志记录中存在已知的类加载问题,这些问题会导致问题。 如果可能的话,我们建议你在从“可执行文件 jar”运行时避免此类问题。 |
---|
为了帮助进行定制,将一些其他属性从 Spring Environment
转移到系统属性,如下表所述:
Spring Environment | System Property | 评论 |
---|---|---|
logging.exception-conversion-word | LOG_EXCEPTION_CONVERSION_WORD | 记录异常时使用的转换词. |
logging.file.name | LOG_FILE | 如果定义了,它将在默认的日志配置中使用。 |
logging.file.path | LOG_PATH | 如果定义了,它将在默认的日志配置中使用。 |
logging.pattern.console | CONSOLE_LOG_PATTERN | 在控制台上使用的日志模式。 |
logging.pattern.dateformat | LOG_DATEFORMAT_PATTERN | 日志日期格式的附录模式。 |
logging.charset.console | CONSOLE_LOG_CHARSET | 用于控制台日志记录的字符集。 |
logging.pattern.file | FILE_LOG_PATTERN | 在文件中使用的日志模式(如果启用LOG_FILE )。 |
logging.charset.file | FILE_LOG_CHARSET | 用于文件日志记录的字符集(如果启用LOG_FILE )。 |
logging.pattern.level | LOG_LEVEL_PATTERN | 呈现日志级别时要使用的格式(默认%5p )。 |
PID | PID | 当前的进程 ID(如果可能的话,在尚未定义为 OS 环境变量时发现)。 |
如果使用注销,还会传输以下属性:
Spring Environment | System Property | 评论 |
---|---|---|
logging.logback.rollingpolicy.file-name-pattern | LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN | 滚转日志文件名的模式(默认${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz )。 |
logging.logback.rollingpolicy.clean-history-on-start | LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START | 是否在启动时清除归档日志文件。 |
logging.logback.rollingpolicy.max-file-size | LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE | 最大日志文件大小。 |
logging.logback.rollingpolicy.total-size-cap | LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP | 要保留的日志备份的总大小。 |
logging.logback.rollingpolicy.max-history | LOGBACK_ROLLINGPOLICY_MAX_HISTORY | 要保存的归档日志文件的最大数量。 |
所有受支持的日志记录系统在解析其配置文件时都可以参考系统属性。有关示例,请参见spring-boot.jar
中的默认配置:
如果你想在日志属性中使用占位符,那么你应该使用Spring Boot’s syntax,而不是底层框架的语法。 值得注意的是,如果你使用 logback,你应该使用 : 作为属性名与其默认值之间的分隔符,而不是使用:- 。 |
---|
你可以通过只重写LOG_LEVEL_PATTERN (或logging.pattern.level 带回日志),将 MDC 和其他临时内容添加到日志行中,例如,如果你使用 logging.pattern.level=user:%X{user} %5p ,那么默认的日志格式包含一个“user”的 MDC 条目,如果它存在的话,如下面的示例所示。<br/>2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0] demo.Controller<br/>Handling authenticated request<br/> |
---|
# 4.9.回录扩展
Spring 启动包括许多对注销的扩展,这些扩展可以帮助进行高级配置。你可以在logback-spring.xml
配置文件中使用这些扩展名。
因为标准的logback.xml 配置文件加载得太早,所以不能在其中使用扩展名。你需要使用 logback-spring.xml 或定义logging.config 属性。 |
---|
这些扩展名不能与 Logback 的配置扫描 (opens new window)一起使用。 如果你试图这样做,对配置文件进行更改将导致类似于记录以下错误之一的错误: |
---|
ERROR in [email protected]:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]]
ERROR in ch.qos.logback.core.joran.spi.Interpret[email protected]:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]
# 4.9.1.特定于配置文件的配置
<springProfile>
标记允许你基于活动 Spring 配置文件可选地包括或排除配置的部分。在<configuration>
元素中的任何地方都支持配置文件部分。使用name
属性指定哪个配置文件接受配置。<springProfile>
标记可以包含配置文件名(例如staging
)或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑,例如production & (eu-central | eu-west)
。查看参考指南 (opens new window)以获取更多详细信息。下面的清单显示了三个示例配置文件:
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
<springProfile name="dev | staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
# 4.9.2.环境属性
<springProperty>
标记允许你公开 Spring Environment
中的属性,以便在回传过程中使用。如果你希望在你的回登配置中访问application.properties
文件中的值,那么这样做会很有用。该标记的工作方式与 Logback 的标准<property>
标记类似。但是,不是直接指定value
,而是指定属性的source
(来自Environment
)。如果需要将属性存储在local
范围以外的其他地方,则可以使用scope
属性。如果你需要一个回退值(如果该属性未在Environment
中设置),则可以使用defaultValue
属性。下面的示例展示了如何公开属性以便在回登过程中使用:
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
<remoteHost>${fluentHost}</remoteHost>
...
</appender>
source 必须在烤肉串的情况下指定(例如my.property-name )。但是,可以通过使用放松的规则将属性添加到 Environment 中。 |
---|
# 5. 国际化
Spring 启动支持本地化消息,以便你的应用程序能够迎合具有不同语言偏好的用户。默认情况下, Spring 引导在 Classpath 的根位置查找messages
资源包的存在。
当配置的资源包的默认属性文件可用时(默认情况下messages.properties ),自动配置将应用,如果你的资源包只包含语言特定的属性文件,则需要添加默认的属性文件。 如果没有找到匹配任何配置的基名的属性文件,不会自动配置 messages 。 |
---|
可以使用messages
命名空间来配置资源包的 basename 以及其他几个属性,如下例所示:
Properties
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
Yaml
spring:
messages:
basename: "messages,config.i18n.messages"
fallback-to-system-locale: false
spring.messages.basename 支持以逗号分隔的位置列表,可以是包限定符,也可以是从 Classpath 根目录解析的资源。 |
---|
参见[MessageSourceProperties
](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/springframework/boot/autofigure/context/mentt/messagesourceproperties.java)以获得更多支持的选项。
# 6. JSON
Spring Boot 提供了与三个 JSON 映射库的集成:
格森
Jackson
JSON-B
Jackson 是首选的默认库。
# 6.1.Jackson
提供了用于 Jackson 的自动配置,并且 Jackson 是messages
的一部分。当 Jackson 在 Classpath 上时,ObjectMapper
Bean 被自动配置。为[自定义ObjectMapper
](howto.html#howto. Spring-mvc.customize-Jackson-objectmapper)的配置提供了几个配置属性。
# 6.2.格森
提供了 GSON 的自动配置。当 GSON 在 Classpath 上时,Gson
Bean 被自动配置。为定制配置提供了几个spring.gson.*
配置属性。要获得更多的控制,可以使用一个或多个GsonBuilderCustomizer
bean。
# 6.3.JSON-B
提供了 JSON-B 的自动配置。当 JSON-B API 和实现都在 Classpath 上时,Jsonb
Bean 将被自动配置。首选的 JSON-B 实现是 Apache Johnzon,它提供了依赖管理。
# 7. 任务执行和调度
在上下文中没有Executor
Bean 的情况下, Spring 引导自动配置具有合理默认值的ThreadPoolTaskExecutor
,该默认值可以自动关联到异步任务执行(@EnableAsync
)和 Spring MVC 异步请求处理。
如果你在上下文中定义了自定义的@EnableAsync ,则常规的任务执行(即@EnableAsync )将透明地使用它,但是 Spring MVC 支持将不会被配置,因为它需要AsyncTaskExecutor 实现(名为applicationTaskExecutor )。取决于你的目标安排,你可以将你的 Executor 更改为ThreadPoolTaskExecutor ,或者同时定义ThreadPoolTaskExecutor 和AsyncConfigurer 包装你的自定义Executor 。自动配置的 TaskExecutorBuilder 允许你轻松地创建实例,这些实例复制默认情况下自动配置的功能。 |
---|
线程池使用 8 个核心线程,这些线程可以根据负载的大小进行增长和收缩。可以使用Executor
名称空间对这些默认设置进行微调,如下例所示:
Properties
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
Yaml
spring:
task:
execution:
pool:
max-size: 16
queue-capacity: 100
keep-alive: "10s"
这将线程池更改为使用有界队列,以便当队列已满(100 个任务)时,线程池最多增加到 16 个线程。当线程空闲 10 秒(而不是默认的 60 秒)时,会回收线程,因此池的收缩会更剧烈。
如果需要与计划的任务执行相关联,ThreadPoolTaskScheduler
也可以自动配置(例如使用@EnableScheduling
)。线程池默认使用一个线程,其设置可以使用spring.task.scheduling
名称空间进行微调,如下例所示:
Properties
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
Yaml
spring:
task:
scheduling:
thread-name-prefix: "scheduling-"
pool:
size: 2
如果需要创建自定义执行器或调度程序,则在上下文中可以使用TaskExecutorBuilder
Bean 和TaskSchedulerBuilder
Bean。
# 8. 测试
Spring 引导提供了许多实用工具和注释,以在测试应用程序时提供帮助。测试支持由两个模块提供:spring-boot-test
包含核心项,spring-boot-test-autoconfigure
支持测试的自动配置。
大多数开发人员使用spring-boot-starter-test
“starter”,它既导入 Spring 引导测试模块,也导入 JUnit Jupiter、AssertJ、Hamcrest 和许多其他有用的库。
如果你有使用 JUnit4 的测试,可以使用 JUnit5 的 Vintage 引擎来运行它们。@EnableScheduling 要使用 Vintage 引擎,请在junit-vintage-engine 上添加一个依赖项,如以下示例所示: |
---|
spring-boot-starter-test
被排除在外,而org.hamcrest:hamcrest
是spring-boot-starter-test
的一部分。
# 8.1.测试范围依赖项
spring-boot-starter-test
“starter”(在test``scope
中)包含以下提供的库:
JUnit 5 (opens new window):用于单元测试 Java 应用程序的事实标准。
Spring Test (opens new window)& Spring 引导测试: Spring 引导应用程序的实用工具和集成测试支持。
AssertJ (opens new window):一个流畅的断言程序库。
Hamcrest (opens new window):Matcher 对象(也称为约束或谓词)的库。
Mockito (opens new window):一个 Java 模拟框架。
JSONassert (opens new window):JSON 的断言程序库。
JsonPath (opens new window):XPath for JSON。
在编写测试时,我们通常会发现这些公共库非常有用。如果这些库不适合你的需要,那么你可以添加自己的额外测试依赖项。
# 8.2.测试 Spring 应用程序
依赖注入的一个主要优点是,它应该使代码更容易进行单元测试。你可以使用new
运算符实例化对象,甚至不需要涉及 Spring。你也可以使用Java util 日志记录 (opens new window)来代替真正的依赖关系。
通常,你需要超越单元测试,开始集成测试(使用 Spring ApplicationContext
)。能够在不需要部署应用程序或不需要连接到其他基础设施的情况下执行集成测试是非常有用的。
Spring 框架包括用于这种集成测试的专用测试模块。你可以直接将依赖项声明为org.springframework:spring-test
,或者使用spring-boot-starter-test
“starter”来传递地将其拉入。
如果你以前没有使用spring-test
模块,那么你应该从阅读 Spring 框架参考文档的相关部分 (opens new window)开始。
# 8.3.测试 Spring 引导应用程序
Spring 引导应用程序是 Spring ApplicationContext
,因此,除了通常使用普通上下文 Spring 所做的事情外,无需做任何非常特殊的事情来测试它。
只有在使用SpringApplication 创建它的情况下, Spring 引导的外部属性、日志记录和其他功能才默认安装在上下文中。 |
---|
Spring Boot 提供了一个@SpringBootTest
注释,其可以作为标准spring-test``@ContextConfiguration
注释的替代项,当需要 Spring 启动功能时。该注释的工作原理是[通过SpringApplication
创建测试中使用的ApplicationContext
](#features.testing. Spring-boot-applications.detecting-configuration)。除了@SpringBootTest
之外,还为测试更具体的切片的应用程序提供了许多其他注释。
如果你正在使用 JUnit4,请不要忘记在你的测试中也添加@RunWith(SpringRunner.class) ,否则注释将被忽略。如果你正在使用 JUnit5,没有必要将等价的 @ExtendWith(SpringExtension.class) 添加为@SpringBootTest ,并且其他@…Test 注释已经用它注释了。 |
---|
默认情况下,@SpringBootTest
不会启动服务器。可以使用@SpringBootTest
的webEnvironment
属性来进一步细化测试的运行方式:
MOCK
(默认):加载 WebApplicationContext
并提供模拟 Web 环境。使用此注释时,嵌入式服务器不会启动。如果你的 Classpath 上没有可用的 Web 环境,则该模式可透明地返回到创建常规的非 WebApplicationContext
。它可以与[@AutoConfigureMockMvc
或@AutoConfigureWebTestClient
](#features.testing. Spring-boot-applications.with-mock-environment)结合使用,用于对 Web 应用程序进行基于模拟的测试。RANDOM_PORT
:加载WebServerApplicationContext
并提供一个真实的 Web 环境。嵌入式服务器在一个随机端口上启动和监听。DEFINED_PORT
:加载WebServerApplicationContext
,并提供一个真正的 Web 环境。嵌入式服务器在定义的端口(从application.properties
)或默认端口8080
上启动和侦听。NONE
:通过使用SpringApplication
加载ApplicationContext
,但不提供任何Web 环境(模拟或其他方式)。
如果你的测试是@Transactional ,那么默认情况下,它会在每个测试方法的末尾回滚事务。但是,由于使用这种安排, RANDOM_PORT 或RANDOM_PORT 隐式地提供了一个真正的 Servlet 环境,HTTP 客户机和服务器在单独的线程中运行,因此,在单独的事务中。在这种情况下,在服务器上发起的任何事务都不回滚。 |
---|
如果应用程序为管理服务器使用不同的端口,则@SpringBootTest withwebEnvironment = WebEnvironment.RANDOM_PORT 也将在单独的随机端口上启动管理服务器。 |
---|
# 8.3.1.检测 Web 应用程序类型
Spring 如果 MVC 是可用的,则配置一个常规的基于 MVC 的应用程序上下文。如果你只有 Spring 个 WebFlux,我们将检测到它并配置一个基于 WebFlux 的应用程序上下文。
如果两者都存在, Spring MVC 优先。如果要在此场景中测试一个反应式 Web 应用程序,则必须设置spring.main.web-application-type
属性:
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {
// ...
}
# 8.3.2.检测测试配置
如果你熟悉 Spring 测试框架,那么你可能习惯于使用@ContextConfiguration(classes=…)
来指定要加载哪个 Spring @Configuration
。或者,你可能经常在测试中使用嵌套的@Configuration
类。
在测试 Spring 引导应用程序时,这通常不是必需的。 Spring 只要不显式地定义一个配置,Boot 的@*Test
注释就会自动搜索你的主要配置。
搜索算法从包含测试的包开始工作,直到找到一个用@SpringBootApplication
或@SpringBootConfiguration
注释的类。只要以一种合理的方式
,通常就可以找到你的主配置。
如果使用测试注释以测试应用程序的一个更具体的部分,你应该避免在主方法的应用程序类上添加特定于特定区域的配置设置。@SpringBootApplication 的底层组件扫描配置定义了排除过滤器如果你在你的@SpringBootApplication -注释类上使用显式的@ComponentScan 指令,请注意这些过滤器将被禁用。如果你正在使用切片,则应再次定义它们。 |
---|
如果你想定制主配置,可以使用嵌套的@TestConfiguration
类。不同于嵌套的@Configuration
类,嵌套的@TestConfiguration
类将被用来代替应用程序的主配置,在应用程序的主配置之外还使用了嵌套的@TestConfiguration
类。
Spring 的测试框架在测试之间缓存应用程序上下文。 因此,只要你的测试共享相同的配置(无论如何发现),加载上下文的可能耗时的过程只会发生一次。 |
---|
# 8.3.3.不包括测试配置
如果你的应用程序使用组件扫描(例如,如果你使用@SpringBootApplication
或@ComponentScan
),你可能会发现只为特定测试创建的顶级配置类在任何地方都会意外地被选中。
正如我们看过更早的,@TestConfiguration
可以用于对内部类的测试进行自定义的主要配置。当放置在顶级类上时,@TestConfiguration
表示不应通过扫描来读取src/test/java
中的类。然后,你可以在需要的地方显式地导入该类,如下面的示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
@Test
void exampleTest() {
// ...
}
}
如果直接使用@ComponentScan (即不通过@SpringBootApplication ),则需要用它注册TypeExcludeFilter 。详见Javadoc (opens new window)。 |
---|
# 8.3.4.使用应用程序参数
如果应用程序期望@SpringBootTest
,则可以使用args
属性注入它们。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {
@Test
void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
assertThat(args.getOptionNames()).containsOnly("app.test");
assertThat(args.getOptionValues("app.test")).containsOnly("one");
}
}
# 8.3.5.在模拟环境中进行测试
默认情况下,@SpringBootTest
不会启动服务器,而是设置一个模拟环境来测试 Web 端点。
使用 Spring MVC,我们可以使用[MockMvc
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/testing.html# Spring-mvc-test-framework)或WebTestClient
查询我们的 Web 端点,如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
}
// If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
@Test
void testWithWebTestClient(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
如果你希望只关注 Web 层,而不是启动一个完整的ApplicationContext ,请考虑[使用@WebMvcTest 代替](#features.testing. Spring-boot-applications. Spring-mvc-tests)。 |
---|
使用 Spring WebFlux 端点,可以使用[WebTestClient
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/testing.html#WebTestClient-tests),如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
在模拟环境中进行测试通常比使用完整的 Servlet 容器运行更快。但是,由于模拟发生在 Spring MVC 层,因此依赖较低级别 Servlet 容器行为的代码不能直接使用 MockMVC 进行测试。例如,, Spring boot 的错误处理是基于 Servlet 容器提供的“错误页”支持。 这意味着,虽然你可以测试你的 MVC 层抛出和处理预期的异常,你无法直接测试呈现了特定的自定义错误页面。 如果你需要测试这些较低级别的问题,你可以启动一个完全运行的服务器,如下一节所述。 |
---|
# 8.3.6.使用正在运行的服务器进行测试
如果你需要启动一个完整的正在运行的服务器,我们建议你使用随机端口。如果使用@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
,则每次测试运行时都会随机选择一个可用的端口。
@LocalServerPort
注释可以用于注入实际使用的端口到你的测试中。为了方便起见,需要对启动的服务器进行 REST 调用的测试还可以另外@Autowire
a[WebTestClient
](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/testing.html#webtestclient-tests),它解析与运行中的服务器的相关链接,并附带用于验证响应的专用 API,如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
WebTestClient 可以同时用于实时服务器和模拟环境。 |
---|
此设置需要在 Classpath 上执行spring-webflux
。如果不能或不会添加 WebFlux, Spring Boot 还提供了TestRestTemplate
功能:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
void exampleTest(@Autowired TestRestTemplate restTemplate) {
String body = restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
# 8.3.7.自定义 WebTestClient
要定制WebTestClient
Bean,请配置WebTestClientBuilderCustomizer
Bean。使用WebTestClient.Builder
调用任何这样的 bean,该 bean 用于创建WebTestClient
。
# 8.3.8.使用 JMX
当测试上下文框架缓存上下文时,默认情况下禁用 JMX,以防止相同的组件在同一域上注册。如果这样的测试需要访问MBeanServer
,也可以考虑将其标记为 dirty:
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {
@Autowired
private MBeanServer mBeanServer;
@Test
void exampleTest() throws MalformedObjectNameException {
assertThat(this.mBeanServer.getDomains()).contains("java.lang");
// ...
}
}
# 8.3.9.使用度量
无论你的 Classpath 是什么,除了内存备份之外,仪表注册中心在使用@SpringBootTest
时不会自动配置。
如果作为集成测试的一部分,需要将指标导出到不同的后端,请用@AutoConfigureMetrics
对其进行注释。
# 8.3.10.嘲笑和窥探豆子
在运行测试时,有时需要模拟应用程序上下文中的某些组件。例如,你可能在某个远程服务上有一个 facade,而在开发过程中它是不可用的。当你想要模拟在真实环境中可能很难触发的故障时,模拟也很有用。
Spring 引导包括一个@MockBean
注释,该注释可用于在你的ApplicationContext
中为 Bean 定义一个 mockito 模拟。你可以使用该注释来添加新的 bean 或替换单个现有的 Bean 定义。注释可以直接用于测试类、测试中的字段或@Configuration
类和字段。当在字段上使用时,创建的模拟实例也会被注入。在每种测试方法之后,mock bean 都会自动重置。
如果你的测试使用 Spring boot 的测试注释之一(例如@SpringBootTest ),则自动启用此功能。要以不同的方式使用此功能,必须显式地添加监听器,如以下示例所示: <br/>import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;<br/>import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener;<br/>import org.springframework.test.context.ContextConfiguration;<br/>import org.springframework.test.context.TestExecutionListeners;<br/><br/>@ContextConfiguration(classes = MyConfig.class)<br/>@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })<br/>class MyTests {<br/><br/> // ...<br/><br/>}<br/><br/> |
---|
下面的示例用模拟实现替换现有的RemoteService
Bean:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@SpringBootTest
class MyTests {
@Autowired
private Reverser reverser;
@MockBean
private RemoteService remoteService;
@Test
void exampleTest() {
given(this.remoteService.getValue()).willReturn("spring");
String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService
assertThat(reverse).isEqualTo("gnirps");
}
}
@MockBean 不能用来模拟 Bean 在应用程序上下文刷新期间执行的行为。在执行测试时,应用程序上下文刷新已经完成,现在配置模拟行为已经太晚了。 在这种情况下,我们建议使用 @Bean 方法来创建和配置模拟行为。 |
---|
此外,你可以使用@SpyBean
用 mockitospy
包装任何现有的 Bean。有关详细信息,请参见Javadoc (opens new window)。
CGLIB 代理,例如为作用域 bean 创建的代理,将 proxied 方法声明为final 。这将阻止 Mockito 正常运行,因为它无法在其默认配置中模拟或监视 final 方法。,如果你想模拟或监视这样的 Bean,通过将 org.mockito:mockito-inline 添加到应用程序的测试依赖项,将 Mockito 配置为使用其内联 mock maker。这允许 Mockito 模拟和监视 final 方法。 |
---|
Spring 的测试框架在测试之间缓存应用程序上下文,并为共享相同配置的测试重用上下文,而@MockBean 或@SpyBean 的使用会影响缓存键,这很可能会增加上下文的数量。 |
---|
如果你使用@SpyBean 监视带有@Cacheable 方法的 Bean,该方法通过名称引用参数,则你的应用程序必须使用-parameters 进行编译。这将确保在监视了 Bean 之后,参数名称对缓存基础设施是可用的。 |
---|
当你使用@SpyBean 监视由 Spring 代理的 Bean 时,在某些情况下可能需要删除 Spring 的代理,例如在使用given 或when 设置期望时。使用 AopTestUtils.getTargetObject(yourProxiedSpy) 来这样做。 |
---|
# 8.3.11.自动配置的测试
Spring Boot 的自动配置系统在应用程序中运行良好,但有时在测试中可能会有点太多。通常情况下,只加载测试应用程序“部分”所需的配置部分是有帮助的。例如,你可能希望测试 Spring MVC 控制器是否正确映射 URL,并且你不希望在这些测试中涉及数据库调用,或者你可能希望测试 JPA 实体,并且你对这些测试运行时的 Web 层不感兴趣。
spring-boot-test-autoconfigure
模块包括许多注释,可用于自动配置此类“切片”。它们都以类似的方式工作,提供一个@…Test
注释,用于加载ApplicationContext
和一个或多个@AutoConfigure…
注释,这些注释可用于自定义自动配置设置。
每个切片将组件扫描限制为适当的组件,并加载一组非常受限制的自动配置类。 如果需要排除其中之一,大多数 @…Test 注释都提供了excludeAutoConfiguration 属性。或者,你可以使用 @ImportAutoConfiguration#exclude 。 |
---|
不支持在一个测试中使用多个@…Test 注释来包含多个“切片”。如果需要多个“切片”,请选择其中一个 @…Test 注释,并手动包含其他“切片”的@AutoConfigure… 注释。 |
---|
也可以使用带有标准@SpringBootTest 注释的@AutoConfigure… 注释。如果你对应用程序“切片”不感兴趣,但希望使用一些自动配置的测试 bean,则可以使用此组合。 |
---|
# 8.3.12.自动配置的 JSON 测试
要测试对象 JSON 序列化和反序列化是否如预期的那样工作,你可以使用@JsonTest
注释。@JsonTest
自动配置可用的受支持的 JSON 映射器,它可以是以下库之一:
Jackson
ObjectMapper
,任何@JsonComponent
bean 和任何 JacksonModule
sGson
Jsonb
由@JsonTest 启用的自动配置的列表可以是在附录中找到。 |
---|
如果需要配置自动配置的元素,可以使用@AutoConfigureJsonTesters
注释。
Spring 引导包括基于 AssertJ 的帮助器,这些帮助器与 JSONASSERT 和 JSONPATH 库一起工作,以检查 JSON 是否如预期的那样出现。JacksonTester
、GsonTester
、JsonbTester
和BasicJsonTester
类可以分别用于 Jackson、GSON、JSONB 和字符串。当使用@JsonTest
时,测试类上的任何 helper 字段都可以是@Autowired
。下面的示例展示了一个用于 Jackson 的测试类:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import static org.assertj.core.api.Assertions.assertThat;
@JsonTest
class MyJsonTests {
@Autowired
private JacksonTester<VehicleDetails> json;
@Test
void serialize() throws Exception {
VehicleDetails details = new VehicleDetails("Honda", "Civic");
// Assert against a `.json` file in the same package as the test
assertThat(this.json.write(details)).isEqualToJson("expected.json");
// Or use JSON path based assertions
assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
}
@Test
void deserialize() throws Exception {
String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
}
}
JSON helper 类也可以在标准单元测试中直接使用。 要这样做,如果不使用 @JsonTest 方法,请在你的initFields 方法中调用该 helper 的initFields 方法。 |
---|
如果使用 Spring boot 的基于 AssertJ 的助手在给定的 JSON 路径上断言一个数字值,则可能无法根据类型使用isEqualTo
。相反,你可以使用 AssertJ 的satisfies
来断言该值匹配给定的条件。例如,下面的示例断言,在0.01
的偏移量内,实际数字是一个接近0.15
的浮点值。
@Test
void someTest() throws Exception {
SomeObject value = new SomeObject(0.152f);
assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
.satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
# 8.3.13.自动配置的 Spring MVC 测试
要测试 Spring MVC 控制器是否如预期的那样工作,请使用@WebMvcTest
注释。@WebMvcTest
自动配置 Spring MVC 基础架构,并将扫描的 bean 限制为@Controller
,@ControllerAdvice
,Converter
,GenericConverter
,Filter
,HandlerInterceptor
,<1440"/>,,和”1442">。常规的@Component
和@ConfigurationProperties
bean 在使用@WebMvcTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。
@WebMvcTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
如果需要注册额外的组件,例如 JacksonModule ,则可以在测试中使用@Import 导入额外的配置类。 |
---|
通常,@WebMvcTest
仅限于单个控制器,并与@MockBean
组合使用,以为所需的协作者提供模拟实现。
@WebMvcTest
还自动配置MockMvc
。Mock MVC 提供了一种强大的方法,可以快速测试 MVC 控制器,而无需启动完整的 HTTP 服务器。
你还可以在非-@WebMvcTest (例如@SpringBootTest )中自动配置MockMvc (例如@SpringBootTest ),方法是用@AutoConfigureMockMvc 对其进行注释。下面的示例使用 MockMvc : |
---|
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Honda Civic"));
}
}
如果需要配置自动配置的元素(例如,当应用 Servlet 过滤器时),可以在@AutoConfigureMockMvc 注释中使用属性。 |
---|
如果使用 htmlUnit 和 Selenium,Auto-Configuration 还提供了一个 htmlUnit<gtr="1463"/> Bean 和/或一个 Selenium<gtr="1464"/> Bean。以下示例使用了 htmlUnit:
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {
@Autowired
private WebClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new VehicleDetails("Honda", "Civic"));
HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
}
}
默认情况下, Spring boot 将WebDriver bean 放在一个特殊的“范围”中,以确保每次测试后驱动程序退出,并且注入了一个新的实例。如果你不想要这种行为,可以将 @Scope("singleton") 添加到你的WebDriver 定义中。 |
---|
由 Spring 引导创建的webDriver 作用域将替换任何用户定义的同名作用域。如果你定义自己的 webDriver 作用域,则当你使用@WebMvcTest 时,你可能会发现它停止工作。 |
---|
如果在 Classpath 上具有 Spring 安全性,则@WebMvcTest
也将扫描WebSecurityConfigurer
bean。你可以使用 Spring Security 的测试支持,而不是完全禁用此类测试的安全性。关于如何使用 Spring Security 的MockMvc
支持的更多详细信息,可以在这个 *howto.html*how-to 部分中找到。
有时编写 Spring MVC 测试是不够的; Spring 引导可以帮助你运行使用实际服务器进行完整的端到端测试。 |
---|
# 8.3.14.自动配置的 Spring WebFlux 测试
要测试Spring WebFlux (opens new window)控制器是否按预期工作,可以使用@WebFluxTest
注释。@WebFluxTest
自动配置 Spring WebFlux 基础设施,并将扫描的 bean 限制为@Controller
,@ControllerAdvice
,Converter
,GenericConverter
,WebFilter
,以及WebFluxConfigurer
。常规的@Component
和@ConfigurationProperties
bean 在使用@WebFluxTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。
由@WebFluxTest 启用的自动配置的列表可以是在附录中找到。 |
---|
如果需要注册额外的组件,例如 JacksonModule ,则可以在测试中使用@Import 导入额外的配置类。 |
---|
通常,@WebFluxTest
仅限于单个控制器,并与@MockBean
注释结合使用,以为所需的协作者提供模拟实现。
@WebFluxTest
还自动配置[WebTestClient
](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/Reference/html/testing.html#WebTestClient),它提供了一种强大的方式,可以快速测试 WebFlux 控制器,而无需启动完整的 HTTP 服务器。
你还可以在非-@WebFluxTest (例如@SpringBootTest )中通过使用@AutoConfigureWebTestClient 对其进行注释来自动配置WebTestClient 。下面的示例显示了一个同时使用 @WebFluxTest 和WebTestClient 的类: |
---|
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.mockito.BDDMockito.given;
@WebFluxTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private WebTestClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() throws Exception {
given(this.userVehicleService.getVehicleDetails("sboot"))
.willReturn(new VehicleDetails("Honda", "Civic"));
this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Honda Civic");
}
}
此设置仅由 WebFlux 应用程序支持,因为在模拟 Web 应用程序中使用WebTestClient 目前仅与 WebFlux 一起工作。 |
---|
@WebFluxTest 无法检测通过 Functional Web Framework 注册的路由。对于在上下文中测试 RouterFunction bean,请考虑通过使用@Import 或通过使用@SpringBootTest 来导入你的RouterFunction 自己。 |
---|
@WebFluxTest 无法检测到注册为@Bean 类型的SecurityWebFilterChain 的自定义安全配置。要在测试中包含该配置,你将需要通过使用 @Import 或通过使用@SpringBootTest 导入注册 Bean 的配置。 |
---|
有时编写 Spring WebFlux 测试是不够的; Spring Boot 可以帮助你运行使用实际服务器进行完整的端到端测试。 |
---|
# 8.3.15.自动配置的数据 Cassandra 测试
你可以使用@DataCassandraTest
来测试 Cassandra 应用程序。默认情况下,它配置CassandraTemplate
,扫描@Table
类,并配置 Spring 数据 Cassandra 存储库。常规的@Component
和@ConfigurationProperties
bean 在使用@DataCassandraTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。(关于使用 Spring boot 的 Cassandra 的更多信息,请参见本章前面的“data.html”。
@DataCassandraTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的示例展示了在 Spring Boot 中使用 Cassandra 测试的典型设置:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
@DataCassandraTest
class MyDataCassandraTests {
@Autowired
private SomeRepository repository;
}
# 8.3.16.自动配置的数据 JPA 测试
你可以使用@DataJpaTest
注释来测试 JPA 应用程序。默认情况下,它扫描@Entity
类并配置 Spring 数据 JPA 存储库。如果嵌入式数据库在 Classpath 上可用,那么它也会配置一个。默认情况下,通过将spring.jpa.show-sql
属性设置为true
来记录 SQL 查询。可以使用注释的showSql()
属性禁用此功能。
常规的@Component
和@ConfigurationProperties
bean 在使用@DataJpaTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。
@DataJpaTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
默认情况下,数据 JPA 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,你可以禁用测试或整个类的事务管理,如下所示:
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {
// ...
}
数据 JPA 测试还可能注入一个[TestEntityManager
](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-test-autofigure/SRC/main/java/org/springframework/boot/test/autoform/ JPA/testentitymanager.java) Bean,它为专门为测试设计的标准 JPA <gtr="1548"/>提供了一种替代方案。
TestEntityManager 还可以通过添加@AutoConfigureTestEntityManager 自动配置到你的任何基于 Spring 的测试类。这样做时,请确保你的测试是在事务中运行的,例如,在你的测试类或方法上添加 @Transactional 。 |
---|
如果需要的话,也可以使用JdbcTemplate
。下面的示例显示了使用中的@DataJpaTest
注释:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class MyRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository repository;
@Test
void testExample() throws Exception {
this.entityManager.persist(new User("sboot", "1234"));
User user = this.repository.findByUsername("sboot");
assertThat(user.getUsername()).isEqualTo("sboot");
assertThat(user.getEmployeeNumber()).isEqualTo("1234");
}
}
内存中的嵌入式数据库通常可以很好地用于测试,因为它们速度快且不需要任何安装。但是,如果你更喜欢对真实的数据库运行测试,则可以使用@AutoConfigureTestDatabase
注释,如下例所示:
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {
// ...
}
# 8.3.17.自动配置的 JDBC 测试
@JdbcTest
类似于@DataJpaTest
,但用于仅需要DataSource
且不使用 Spring 数据 JDBC 的测试。默认情况下,它配置内存中的嵌入式数据库和JdbcTemplate
。当使用@JdbcTest
注释时,常规的@Component
和@ConfigurationProperties
bean 不会被扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。
@JdbcTest 启用的自动配置列表可以是在附录中找到。 |
---|
默认情况下,JDBC 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,那么你可以禁用测试或整个类的事务管理,如下所示:
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests {
}
如果你希望你的测试在真实的数据库中运行,那么你可以使用@AutoConfigureTestDatabase
注释,就像使用DataJpaTest
注释一样。(见“Auto-configured Data JPA Tests”。
# 8.3.18.自动配置的数据 JDBC 测试
@DataJdbcTest
类似于@JdbcTest
,但用于使用 Spring 数据 JDBC 存储库的测试。默认情况下,它配置内存中的嵌入式数据库、JdbcTemplate
和 Spring 数据 JDBC 存储库。常规的@Component
和@ConfigurationProperties
bean 在使用@DataJdbcTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。
由@DataJdbcTest 启用的自动配置的列表可以是在附录中找到。 |
---|
默认情况下,数据 JDBC 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,那么你可以禁用测试或整个测试类的事务管理,如在 JDBC 示例中显示。
如果你希望你的测试在真实的数据库中运行,那么你可以使用@AutoConfigureTestDatabase
注释,就像使用DataJpaTest
注释一样。(见“Auto-configured Data JPA Tests”。
# 8.3.19.自动配置的 Jooq 测试
你可以以与@JdbcTest
类似的方式使用@JooqTest
,但用于与 Jooq 相关的测试。由于 Jooq 在很大程度上依赖于与数据库模式相对应的基于 Java 的模式,因此使用了现有的DataSource
。如果要用内存数据库替换它,可以使用@AutoConfigureTestDatabase
来覆盖这些设置。(有关使用 Spring boot 使用 Jooq 的更多信息,请参见本章前面的“data.html”。)常规@Component
和@ConfigurationProperties
bean 在使用@JooqTest
注释时不会被扫描。@EnableConfigurationProperties
bean 可用于包括@ConfigurationProperties
bean。
由@JooqTest 启用的自动配置的列表可以是在附录中找到。 |
---|
@JooqTest
配置DSLContext
。下面的示例显示了正在使用的@JooqTest
注释:
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jooq.JooqTest;
@JooqTest
class MyJooqTests {
@Autowired
private DSLContext dslContext;
// ...
}
在默认情况下,Jooq 测试是事务性的,并在每个测试结束时回滚。如果这不是你想要的,那么你可以禁用测试或整个测试类的事务管理,如在 JDBC 示例中显示。
# 8.3.20.自动配置的数据 MongoDB 测试
你可以使用@DataMongoTest
来测试 MongoDB 应用程序。默认情况下,它配置内存中的嵌入式 MongoDB(如果可用),配置MongoTemplate
,扫描@Document
类,并配置 Spring 数据 MongoDB 存储库。常规的@Component
和@ConfigurationProperties
bean 在使用@DataMongoTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。(有关使用 Spring Boot 的 MongoDB 的更多信息,请参见本章前面的“data.html”。
@DataMongoTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的类显示了使用中的@DataMongoTest
注释:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoTemplate;
@DataMongoTest
class MyDataMongoDbTests {
@Autowired
private MongoTemplate mongoTemplate;
// ...
}
内嵌入内存的 MongoDB 通常在测试中运行良好,因为它速度快,并且不需要任何开发人员安装。但是,如果你更喜欢在真正的 MongoDB 服务器上运行测试,那么你应该排除嵌入的 MongoDB 自动配置,如下例所示:
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
class MyDataMongoDbTests {
// ...
}
# 8.3.21.自动配置的数据 NEO4J 测试
你可以使用@DataNeo4jTest
来测试 NEO4J 应用程序。默认情况下,它扫描@Node
类,并配置 Spring 数据 NEO4j 存储库。常规的@Component
和@ConfigurationProperties
bean 在使用@DataNeo4jTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。(有关在 Spring 引导下使用 NEO4j 的更多信息,请参见本章前面的“data.html”。
@DataNeo4jTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的示例显示了在 Spring 引导中使用 NEO4J 测试的典型设置:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
@DataNeo4jTest
class MyDataNeo4jTests {
@Autowired
private SomeRepository repository;
// ...
}
默认情况下,数据 NEO4J 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,那么你可以禁用测试或整个类的事务管理,如下所示:
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests {
}
反应性访问不支持事务性测试。 如果使用这种样式,则必须如上所述配置 @DataNeo4jTest 测试。 |
---|
# 8.3.22.自动配置的数据 Redis 测试
你可以使用@DataRedisTest
来测试 Redis 应用程序。默认情况下,它扫描@RedisHash
类并配置 Spring 数据 Redis 存储库。常规的@Component
和@ConfigurationProperties
bean 在使用@DataRedisTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。(有关在 Spring 引导下使用 Redis 的更多信息,请参见本章前面的“data.html”。
@DataRedisTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的示例显示了使用中的@DataRedisTest
注释:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
@DataRedisTest
class MyDataRedisTests {
@Autowired
private SomeRepository repository;
// ...
}
# 8.3.23.自动配置的数据 LDAP 测试
你可以使用@DataLdapTest
来测试 LDAP 应用程序。默认情况下,它配置内存中的嵌入式 LDAP(如果可用),配置LdapTemplate
,扫描@Entry
类,并配置 Spring 数据 LDAP 存储库。常规的@Component
和@ConfigurationProperties
bean 在使用@DataLdapTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。(有关在 Spring boot 中使用 LDAP 的更多信息,请参见本章前面的“data.html”。
@DataLdapTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的示例显示了使用中的@DataLdapTest
注释:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.ldap.core.LdapTemplate;
@DataLdapTest
class MyDataLdapTests {
@Autowired
private LdapTemplate ldapTemplate;
// ...
}
内嵌入内存的 LDAP 通常在测试中运行良好,因为它速度快且不需要任何开发人员安装。但是,如果你更喜欢在真实的 LDAP 服务器上运行测试,则应该排除嵌入的 LDAP 自动配置,如下例所示:
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
class MyDataLdapTests {
// ...
}
# 8.3.24.自动配置的 REST 客户机
你可以使用@RestClientTest
注释来测试 REST 客户机。默认情况下,它会自动配置 Jackson、GSON 和 JSONB 支持,配置RestTemplateBuilder
,并添加对MockRestServiceServer
的支持。常规的@Component
和@ConfigurationProperties
bean 在使用@RestClientTest
注释时不进行扫描。@EnableConfigurationProperties
可以用于包括@ConfigurationProperties
bean。
@RestClientTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
要测试的特定 bean 应该使用value
或components
的components
属性来指定,如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {
@Autowired
private RemoteVehicleDetailsService service;
@Autowired
private MockRestServiceServer server;
@Test
void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() throws Exception {
this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
# 8.3.25.自动配置的 Spring REST DOCS 测试
在使用模拟 MVC、Rest Assured 或 WebTestClient 的测试中,可以使用@AutoConfigureRestDocs
注释来使用Spring REST Docs (opens new window)。它消除了对 Spring REST DOCS 中的 JUnit 扩展的需要。
@AutoConfigureRestDocs
可以用来覆盖默认的输出目录(target/generated-snippets
如果你正在使用 Maven 或build/generated-snippets
如果你正在使用 Gradle)。它还可以用于配置出现在任何有文档的 URI 中的主机、方案和端口。
# Spring 使用模拟 MVC 的 REST DOCS 测试的自动配置
@AutoConfigureRestDocs
在测试基于 Servlet 的 Web 应用程序时定制MockMvc
Bean 以使用 Spring REST DOCS。你可以通过使用@Autowired
注入它,并在测试中使用它,就像在使用模拟 MVC 和 Spring REST DOCS 时通常使用的那样,如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class MyUserDocumentationTests {
@Autowired
private MockMvc mvc;
@Test
void listUsers() throws Exception {
this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andDo(document("list-users"));
}
}
如果需要对 Spring REST DOCS 配置的控制比@AutoConfigureRestDocs
的属性提供的更多,则可以使用RestDocsMockMvcConfigurationCustomizer
Bean,如以下示例所示:
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {
@Override
public void customize(MockMvcRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
如果希望利用 Spring REST DOCS 对参数化输出目录的支持,则可以创建RestDocumentationResultHandler
Bean。自动配置使用此结果处理程序调用alwaysDo
,从而使每个MockMvc
调用自动生成缺省片段。下面的示例显示了正在定义的RestDocumentationResultHandler
:
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {
@Bean
public RestDocumentationResultHandler restDocumentation() {
return MockMvcRestDocumentation.document("{method-name}");
}
}
# 使用 WebTestClient 自动配置的 Spring REST DOCS 测试
@AutoConfigureRestDocs
在测试反应性 Web 应用程序时也可以与WebTestClient
一起使用。你可以通过使用@Autowired
注入它,并在测试中使用它,就像在使用@WebFluxTest
和 Spring REST DOCS 时通常使用它一样,如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {
@Autowired
private WebTestClient webTestClient;
@Test
void listUsers() {
this.webTestClient
.get().uri("/")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("list-users"));
}
}
如果你需要对 Spring REST DOCS 配置的控制超过由@AutoConfigureRestDocs
的属性提供的控制,则可以使用RestDocsWebTestClientConfigurationCustomizer
Bean,如以下示例所示:
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer;
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer {
@Override
public void customize(WebTestClientRestDocumentationConfigurer configurer) {
configurer.snippets().withEncoding("UTF-8");
}
}
如果希望利用 Spring REST DOCS 对参数化输出目录的支持,则可以使用WebTestClientBuilderCustomizer
来为每个实体交换结果配置消费者。下面的示例显示了正在定义的这样的WebTestClientBuilderCustomizer
:
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {
@Bean
public WebTestClientBuilderCustomizer restDocumentation() {
return (builder) -> builder.entityExchangeResultConsumer(document("{method-name}"));
}
}
# Spring 具有 REST 保证的 REST DOCS 测试的自动配置
@AutoConfigureRestDocs
生成一个RequestSpecification
Bean,预先配置为使用 Spring REST DOCS,可用于你的测试。你可以通过使用@Autowired
注入它,并在测试中使用它,就像在使用 REST ASSURED 和 Spring REST DOCS 时通常使用它一样,如以下示例所示:
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {
@Test
void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) {
given(documentationSpec)
.filter(document("list-users"))
.when()
.port(port)
.get("/")
.then().assertThat()
.statusCode(is(200));
}
}
如果需要对 Spring REST DOCS 配置的控制超过由@AutoConfigureRestDocs
的属性提供的控制,则可以使用RestDocsRestAssuredConfigurationCustomizer
Bean,如以下示例所示:
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.restassured3.RestAssuredRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer {
@Override
public void customize(RestAssuredRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
# 8.3.26.自动配置的 Spring Web 服务测试
# 自动配置的 Spring Web 服务客户端测试
你可以使用@WebServiceClientTest
来测试使用 Spring Web 服务项目调用 Web 服务的应用程序。默认情况下,它配置一个模拟WebServiceServer
Bean 并自动定制你的WebServiceTemplateBuilder
。(有关使用 Spring boot 的 Web 服务的更多信息,请参见本章前面的“io.html”。
@WebServiceClientTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的示例显示了使用中的@WebServiceClientTest
注释:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest;
import org.springframework.ws.test.client.MockWebServiceServer;
import org.springframework.xml.transform.StringSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.ws.test.client.RequestMatchers.payload;
import static org.springframework.ws.test.client.ResponseCreators.withPayload;
@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {
@Autowired
private MockWebServiceServer server;
@Autowired
private SomeWebService someWebService;
@Test
void mockServerCall() {
this.server
.expect(payload(new StringSource("<request/>")))
.andRespond(withPayload(new StringSource("<response><status>200</status></response>")));
assertThat(this.someWebService.test())
.extracting(Response::getStatus)
.isEqualTo(200);
}
}
# 自动配置的 Spring Web 服务服务器测试
你可以使用@WebServiceServerTest
来测试使用 Spring Web 服务项目实现 Web 服务的应用程序。默认情况下,它配置了一个MockWebServiceClient
Bean,可用于调用你的 Web 服务端点。(有关使用 Spring 引导的 Web 服务的更多信息,请参见本章前面的“io.html”。
@WebServiceServerTest 启用的自动配置设置列表可以是在附录中找到。 |
---|
下面的示例显示了使用中的@WebServiceServerTest
注释:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest;
import org.springframework.ws.test.server.MockWebServiceClient;
import org.springframework.ws.test.server.RequestCreators;
import org.springframework.ws.test.server.ResponseMatchers;
import org.springframework.xml.transform.StringSource;
@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {
@Autowired
private MockWebServiceClient client;
@Test
void mockServerCall() {
this.client
.sendRequest(RequestCreators.withPayload(new StringSource("<ExampleRequest/>")))
.andExpect(ResponseMatchers.payload(new StringSource("<ExampleResponse>42</ExampleResponse>")));
}
}
# 8.3.27.附加的自动配置和切片
每个切片提供一个或多个@AutoConfigure…
注释,即定义了作为切片的一部分应该包含的自动配置。通过创建自定义@AutoConfigure…
注释或在测试中添加@ImportAutoConfiguration
,可以在逐个测试的基础上添加额外的自动配置,如下例所示:
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests {
}
确保不要使用常规的@Import 注释来导入自动配置,因为它们是由 Spring 引导以特定方式处理的。 |
---|
或者,可以通过在META-INF/spring.factories
中注册它们,为任何使用切片注释的情况添加额外的自动配置,如以下示例所示:
org.springframework.boot.test.autoconfigure.jdbc.JdbcTest=com.example.IntegrationAutoConfiguration
切片或@AutoConfigure… 注释可以通过这种方式进行定制,只要它是用@ImportAutoConfiguration 进行元注释的。 |
---|
# 8.3.28.用户配置和切片
如果以一种合理的方式构造你的代码,你的@SpringBootApplication
类是默认使用作为测试的配置。
因此,重要的是,不要在应用程序的主类中浪费特定于其特定功能领域的配置设置。
假设你正在使用 Spring 批处理,并且你依赖于它的自动配置。你可以将你的@SpringBootApplication
定义如下:
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableBatchProcessing
public class MyApplication {
// ...
}
因为这个类是测试的源配置,所以任何切片测试实际上都会尝试启动 Spring 批处理,而这绝对不是你想要做的。推荐的方法是将该区域特定的配置移动到与应用程序处于同一级别的单独的@Configuration
类,如下例所示:
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableBatchProcessing
public class MyBatchConfiguration {
// ...
}
根据应用程序的复杂性,你可以有一个用于自定义的@Configuration 类,或者每个域区域有一个类。后一种方法允许你在你的一个测试中启用它,如果需要的话,使用 @Import 注释。 |
---|
测试片从扫描中排除@Configuration
类。例如,对于@WebMvcTest
,以下配置将不包括在测试切片加载的应用程序上下文中给定的WebMvcConfigurer
Bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {
@Bean
public WebMvcConfigurer testConfigurer() {
return new WebMvcConfigurer() {
// ...
};
}
}
但是,下面的配置将导致测试切片加载自定义WebMvcConfigurer
。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
// ...
}
另一个令人困惑的原因是扫描。假设,当你以一种合理的方式构造代码时,你需要扫描一个额外的包。你的应用程序可能类似于以下代码:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {
// ...
}
这样做有效地覆盖了默认的组件扫描指令,其副作用是无论你选择的是哪一片,都要扫描这两个包。例如,@DataJpaTest
似乎会突然扫描应用程序的组件和用户配置。同样,将自定义指令移动到一个单独的类是解决此问题的一个好方法。
如果这不是你的一个选项,那么你可以在测试的层次结构中的某个地方创建一个@SpringBootConfiguration ,以便使用它。或者,你可以为你的测试指定一个源,这将禁用查找缺省源的行为。 |
---|
# 8.3.29.使用 Spock 测试 Spring 启动应用程序
Spock2.x 可用于测试 Spring 引导应用程序。为此,在应用程序的构建中添加对 Spock 的spock-spring
模块的依赖关系。spock-spring
将 Spring 的测试框架集成到 Spock 中。有关更多详细信息,请参见the documentation for Spock’s Spring module (opens new window)。
# 8.4.测试实用程序
在测试应用程序时通常有用的一些测试实用程序类被打包为spring-boot
的一部分。
# 8.4.1.ConfigDataApplicationContextInitializer
ConfigDataApplicationContextInitializer
是一个ApplicationContextInitializer
,你可以将其应用到测试中以加载 Spring bootapplication.properties
文件。当你不需要@SpringBootTest
提供的全套功能时,可以使用它,如下例所示:
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {
// ...
}
单独使用ConfigDataApplicationContextInitializer 并不提供对@Value("${…}") 注入的支持。其唯一的工作是确保 application.properties 文件被加载到 Spring 的Environment 中,对于 @Value 的支持,你需要另外配置一个PropertySourcesPlaceholderConfigurer ,或者使用@SpringBootTest ,它为你自动配置了一个。 |
---|
# 8.4.2.测试 PropertyValues
TestPropertyValues
可以让你快速地将属性添加到ConfigurableEnvironment
或ConfigurableApplicationContext
。你可以使用key=value
字符串调用它,如下所示:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
class MyEnvironmentTests {
@Test
void testPropertySources() {
MockEnvironment environment = new MockEnvironment();
TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
assertThat(environment.getProperty("name")).isEqualTo("Boot");
}
}
# 8.4.3.输出捕获
OutputCapture
是一个 JUnitExtension
,你可以使用它来捕获System.out
和System.err
输出。要使用添加@ExtendWith(OutputCaptureExtension.class)
并将CapturedOutput
作为参数注入到你的测试类构造函数或测试方法中,方法如下:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {
@Test
void testName(CapturedOutput output) {
System.out.println("Hello World!");
assertThat(output).contains("World");
}
}
# 8.4.4.测试模板
TestRestTemplate
是 Spring 的RestTemplate
的一种方便的替代方法,在集成测试中很有用。你可以获得一个普通模板或一个发送基本 HTTP 身份验证的模板(带有用户名和密码)。在这两种情况下,模板都是容错的。这意味着它以一种测试友好的方式运行,不会在 4xx 和 5xx 错误上抛出异常。相反,可以通过返回的ResponseEntity
及其状态代码来检测此类错误。
Spring Framework5.0 提供了一个新的WebTestClient ,它对WebFlux 集成测试和WebFlux 和 MVC 端到端测试都有效。它为断言提供了一个流畅的 API,而不像 TestRestTemplate 。 |
---|
建议使用 Apache HTTP 客户机(版本 4.3.2 或更好),但不是强制的。如果你的 Classpath 上有这样的配置,则TestRestTemplate
通过适当地配置客户机来进行响应。如果你确实使用了 Apache 的 HTTP 客户机,则启用了一些额外的测试友好特性:
重定向不会被跟踪(因此你可以断言响应位置)。
cookies 被忽略(因此模板是无状态的)。
TestRestTemplate
可以在集成测试中直接实例化,如以下示例所示:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
class MyTests {
private TestRestTemplate template = new TestRestTemplate();
@Test
void testRequest() throws Exception {
ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
}
}
或者,如果使用@SpringBootTest
注释和WebEnvironment.RANDOM_PORT
或WebEnvironment.DEFINED_PORT
,则可以插入一个完全配置的TestRestTemplate
并开始使用它。如果需要,可以通过RestTemplateBuilder
Bean 应用额外的自定义。任何未指定主机和端口的 URL 都会自动连接到嵌入式服务器,如以下示例所示:
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests {
@Autowired
private TestRestTemplate template;
@Test
void testRequest() {
HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
assertThat(headers.getLocation()).hasHost("other.example.com");
}
@TestConfiguration(proxyBeanMethods = false)
static class RestTemplateBuilderConfiguration {
@Bean
RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
.setReadTimeout(Duration.ofSeconds(1));
}
}
}
# 9. 创建自己的自动配置
如果你在一家开发共享库的公司工作,或者在一家开放源代码或商业库工作,那么你可能希望开发自己的自动配置。自动配置类可以捆绑在外部 JAR 中,并且仍然可以在 Spring 启动时被拾取。
自动配置可以与一个“启动器”相关联,该启动器提供自动配置代码以及你将与之一起使用的典型库。我们首先介绍构建自己的自动配置所需的知识,然后继续讨论创建自定义启动器所需的典型步骤。
可以使用演示项目 (opens new window)来展示如何一步一步地创建一个启动程序。 |
---|
# 9.1.理解自动配置的 bean
在引擎盖下,自动配置是通过标准的@Configuration
类实现的。附加的@Conditional
注释用于在应用自动配置时进行约束。通常,自动配置类使用@ConditionalOnClass
和@ConditionalOnMissingBean
注释。这确保了自动配置仅在找到相关类以及尚未声明自己的@Configuration
时才适用。
你可以浏览[spring-boot-autoconfigure
](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/org/springframework/boot/autofigure/autofigure)的源代码,查看 Spring 提供的@Configuration
类(参见[META-INF/spring.factories
](https://github.com/ Spring-projects/[[[[tree]-tree/6.6]-
# 9.2.定位自动配置候选项
Spring 引导检查在你发布的 jar 中是否存在META-INF/spring.factories
文件。该文件应该在EnableAutoConfiguration
键下列出你的配置类,如下例所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
自动配置必须以只有的方式加载。 确保它们是在特定的包空间中定义的,并且它们永远不是组件扫描的目标。 此外,自动配置类不应使组件扫描能够找到其他组件。应该使用 特定的 @Import s。 |
---|
你可以使用[@AutoConfigureAfter
](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autconfigure/SRC/main/java/org/SpringFramework/boot/autoConfigure/autoConfigure/autoafter.java.java)或[@AutoConfigureBefore
](https:///github.com/ Spring-projects/[[ Spring-boot/tree/tree/v2.6.4/例如,如果你提供了特定于 Web 的配置,那么你的类可能需要在WebMvcAutoConfiguration
之后应用。
如果你希望订购某些不应相互有任何直接了解的自动配置,也可以使用@AutoConfigureOrder
。该注释与常规的@Order
注释具有相同的语义,但为自动配置类提供了专用的顺序。
与标准的@Configuration
类一样,应用自动配置类的顺序只会影响其 bean 的定义顺序。随后创建这些 bean 的顺序不受影响,并且由每个 Bean 的依赖关系和任何@DependsOn
关系决定。
# 9.3.条件注释
你几乎总是希望在自动配置类中包含一个或多个@Conditional
注释。@ConditionalOnMissingBean
注释是一个常见的示例,如果开发人员对你的默认值不满意,它将允许他们覆盖自动配置。
Spring 引导包括许多@Conditional
注释,你可以通过注释@Configuration
类或单独的@Bean
方法在自己的代码中重用这些注释。这些注释包括:
# 9.3.1.类条件
@ConditionalOnClass
和@ConditionalOnMissingClass
注释让@Configuration
类基于特定类的存在或不存在而被包括。由于批注元数据是通过使用ASM (opens new window)进行解析的,因此你可以使用value
属性来引用真正的类,即使该类实际上可能不会出现在正在运行的应用程序上 Classpath。如果你希望通过使用String
值来指定类名,也可以使用name
属性。
此机制不以同样的方式应用于@Bean
方法,其中返回类型通常是条件的目标:在方法的条件应用之前,JVM 将装载类和可能处理的方法引用,如果类不存在,这些引用将失败。
要处理此场景,可以使用一个单独的@Configuration
类来隔离该条件,如以下示例所示:
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
// Some conditions ...
public class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService.class)
public static class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
}
如果使用@ConditionalOnClass 或@ConditionalOnMissingClass 作为元注释的一部分来编写自己的合成注释,则必须使用name 作为在这种情况下未处理的类的引用。 |
---|
# 9.3.2. Bean 条件
@ConditionalOnBean
和@ConditionalOnMissingBean
注释允许基于特定 bean 的存在或不存在来包含 Bean。可以使用value
属性按类型指定 bean,也可以使用name
属性按名称指定 bean。search
属性允许你限制在搜索 bean 时应该考虑的ApplicationContext
层次结构。
当放置在@Bean
方法上时,目标类型默认为该方法的返回类型,如以下示例所示:
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
在前面的示例中,如果SomeService
类型的 Bean 已经包含在ApplicationContext
中,则将创建someService
Bean。
你需要非常小心添加 Bean 定义的顺序,因为这些条件是基于到目前为止已处理的内容进行评估的。,由于这个原因,<gt r=“1826”/,我们建议在自动配置类上只使用@ConditionalOnBean 和@ConditionalOnMissingBean 注释(因为这些注释保证在添加了任何用户定义的 Bean 定义之后加载)。 |
---|
@ConditionalOnBean 和@ConditionalOnMissingBean 并不阻止创建@Configuration 类。在类级别上使用这些条件与用注释标记每个包含的 @Bean 方法之间的唯一区别是如果条件不匹配,则前者阻止将@Configuration 类注册为 Bean。 |
---|
在声明@Bean 方法时,在方法的返回类型中提供尽可能多的类型信息,例如,,如果 Bean 的 Concrete 类实现了一个接口,那么 Bean 方法的返回类型应该是 Concrete 类而不是接口。在方法中提供尽可能多的类型信息在使用 Bean 条件时尤其重要,因为它们的评估只能依赖于在方法签名中可用的类型信息。 |
---|
# 9.3.3.财产条件
@ConditionalOnProperty
注释允许基于 Spring Environment 属性包含配置。使用prefix
和name
属性来指定应该检查的属性。默认情况下,任何存在且不等于false
的属性都是匹配的。还可以使用havingValue
和matchIfMissing
属性创建更高级的检查。
# 9.3.4.资源条件
@ConditionalOnResource
注释仅允许在存在特定资源时包含配置。可以通过使用通常的 Spring 约定来指定资源,如以下示例所示:file:/home/user/test.dat
。
# 9.3.5.Web 应用程序条件
@ConditionalOnWebApplication
和@ConditionalOnNotWebApplication
注释允许根据应用程序是否为“Web 应用程序”而包括配置。基于 Servlet 的 Web 应用程序是任何使用 Spring WebApplicationContext
、定义session
作用域或具有ConfigurableWebEnvironment
的应用程序。反应性 Web 应用程序是任何使用ReactiveWebApplicationContext
或具有ConfigurableReactiveWebEnvironment
的应用程序。
@ConditionalOnWarDeployment
注释允许根据应用程序是否是部署到容器中的传统 WAR 应用程序来包含配置。对于使用嵌入式服务器运行的应用程序,此条件将不匹配。
# 9.3.6.SPEL 表达条件
@ConditionalOnExpression
注释允许基于Spel 表达式 (opens new window)的结果包含配置。
在表达式中引用 Bean 将导致 Bean 在上下文刷新处理中很早就被初始化。 因此, Bean 将不符合后处理的条件(例如配置属性绑定),并且其状态可能不完整。 |
---|
# 9.4.测试你的自动配置
自动配置可能受到许多因素的影响:用户配置(@Bean
定义和Environment
定制)、条件评估(存在特定库),以及其他因素。具体地说,每个测试都应该创建一个定义良好的ApplicationContext
,它代表了这些定制的组合。ApplicationContextRunner
提供了一种很好的实现方法。
ApplicationContextRunner
通常被定义为用于收集基础、公共配置的测试类的字段。下面的示例确保始终调用MyServiceAutoConfiguration
:
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
如果必须定义多个自动配置,则不需要对它们的声明进行排序,因为它们的调用顺序与运行应用程序时的顺序完全相同。 |
---|
每个测试都可以使用 Runner 来表示特定的用例。例如,下面的示例调用一个用户配置(UserConfiguration
),并检查自动配置是否正确备份。调用run
提供了一个可以与AssertJ
一起使用的回调上下文。
@Test
void defaultServiceBacksOff() {
this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
});
}
@Configuration(proxyBeanMethods = false)
static class UserConfiguration {
@Bean
MyService myCustomService() {
return new MyService("mine");
}
}
也可以方便地定制Environment
,如以下示例所示:
@Test
void serviceNameCanBeConfigured() {
this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
});
}
运行程序还可以用来显示ConditionEvaluationReport
。报告可以在INFO
或DEBUG
级别打印。下面的示例展示了如何使用ConditionEvaluationReportLoggingListener
在自动配置测试中打印报告。
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class MyConditionEvaluationReportingTests {
@Test
void autoConfigTest() {
new ApplicationContextRunner()
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
# 9.4.1.模拟 Web 上下文
如果需要测试仅在 Servlet 或反应性 Web 应用程序上下文中操作的自动配置,则分别使用WebApplicationContextRunner
或ReactiveWebApplicationContextRunner
。
# 9.4.2.超越 Classpath
也可以测试在运行时不存在特定类和/或包时会发生什么。 Spring 具有FilteredClassLoader
的引导船,该引导船可以很容易地被跑步者使用。在下面的示例中,我们断言,如果MyService
不存在,则自动配置将被正确禁用:
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
# 9.5.创建自己的启动器
典型的引导启动器包含自动配置和自定义给定技术基础设施的代码,我们将其称为“Acme”。为了使其易于扩展,可以将专用名称空间中的许多配置键公开到环境中。最后,提供了一个单一的“启动”依赖项,以帮助用户尽可能轻松地启动。
具体地说,自定义启动器可以包含以下内容:
autoconfigure
模块,其中包含“acme”的自动配置代码。starter
模块提供了对autoconfigure
模块的依赖关系,以及“acme”和任何通常有用的附加依赖关系。简而言之,添加 starter 应该提供开始使用该库所需的一切。
这种在两个模块中的分离是绝对没有必要的。如果“Acme”有几种口味、选项或可选功能,那么最好分离自动配置,因为你可以清楚地表达这样一个事实,即某些功能是可选的。此外,你还可以创建一个启动器,提供有关这些可选依赖项的意见。同时,其他人只能依赖autoconfigure
模块,并根据不同的意见制作自己的启动器。
如果自动配置相对简单,并且没有可选功能,那么合并启动器中的两个模块肯定是一个选项。
# 9.5.1.命名
你应该确保为你的启动器提供一个正确的名称空间。即使使用不同的 Maven groupId
,也不要以spring-boot
开始模块名称。我们可能会在将来为你的自动配置提供官方支持。
根据经验,你应该以 starter 命名一个组合模块。例如,假设你正在为“acme”创建一个启动器,并且将自动配置模块命名为acme-spring-boot
,并将启动器命名为acme-spring-boot-starter
。如果只有一个模块合并了这两个模块,请将其命名为acme-spring-boot-starter
。
# 9.5.2.配置键
如果你的启动器提供了配置键,请为它们使用唯一的命名空间。特别是,不要在 Spring 引导使用的名称空间中包含你的键(例如server
,management
,spring
,等等)。如果使用相同的名称空间,将来我们可能会以破坏模块的方式修改这些名称空间。作为一条经验法则,在所有的键前加上一个你拥有的名称空间(例如acme
)。
通过为每个属性添加字段 Javadoc,确保配置键是有文档记录的,如下例所示:
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("acme")
public class AcmeProperties {
/**
* Whether to check the location of acme resources.
*/
private boolean checkLocation = true;
/**
* Timeout for establishing a connection to the acme server.
*/
private Duration loginTimeout = Duration.ofSeconds(3);
// getters/setters ...
public boolean isCheckLocation() {
return this.checkLocation;
}
public void setCheckLocation(boolean checkLocation) {
this.checkLocation = checkLocation;
}
public Duration getLoginTimeout() {
return this.loginTimeout;
}
public void setLoginTimeout(Duration loginTimeout) {
this.loginTimeout = loginTimeout;
}
}
你应该只在@ConfigurationProperties 字段 Javadoc 中使用纯文本,因为它们在被添加到 JSON 之前不会被处理。 |
---|
以下是我们内部遵循的一些规则,以确保描述一致:
不要以“the”或“a”开头描述。
对于
boolean
类型,以“是否”或“启用”开始描述。对于基于集合的类型,以“逗号分隔的列表”开始描述。
使用
java.time.Duration
而不是long
,并描述缺省单位,如果它与毫秒不同,例如“如果没有指定持续时间后缀,将使用秒”。不要在描述中提供默认值,除非它必须在运行时确定。
请确保触发元数据生成,以便你的密钥也可以使用 IDE 辅助。你可能想要查看生成的元数据(META-INF/spring-configuration-metadata.json
),以确保你的密钥被正确地记录。在兼容的 IDE 中使用自己的启动器也是验证元数据质量的一个好主意。
# 9.5.3.“自动配置”模块
autoconfigure
模块包含了启动该库所需的所有内容。它还可能包含配置键定义(例如@ConfigurationProperties
)和任何回调接口,可用于进一步定制组件的初始化方式。
你应该将库的依赖关系标记为可选的,这样你就可以更容易地在项目中包含autoconfigure 模块。如果你这样做,那么库就不会被提供,并且在默认情况下, Spring back off。 |
---|
Spring 引导使用注释处理器来收集元数据文件(META-INF/spring-autoconfigure-metadata.properties
)中关于自动配置的条件。如果该文件存在,它将被用来急切地过滤不匹配的自动配置,这将提高启动时间。建议在包含自动配置的模块中添加以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
如果直接在应用程序中定义了自动配置,请确保配置spring-boot-maven-plugin
,以防止repackage
目标将依赖项添加到 fat jar 中:
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
对于 Gradle 4.5 或更早的版本,依赖关系应该在compileOnly
配置中声明,如以下示例所示:
dependencies {
compileOnly "org.springframework.boot:spring-boot-autoconfigure-processor"
}
对于 Gradle 4.6 及更高版本,依赖关系应该在annotationProcessor
配置中声明,如以下示例所示:
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
# 9.5.4.启动器模块
起动器实际上是一个空的 jar。它的唯一目的是提供使用该库所需的依赖项。你可以把它看作是对开始工作需要什么的一种固执己见的看法。
不要对添加了启动器的项目进行假设。如果你正在自动配置的库通常需要其他启动器,那么也应该提及它们。如果可选依赖关系的数量很高,那么提供一组适当的默认值依赖关系可能会很困难,因为你应该避免包含对于库的典型使用来说不必要的依赖关系。换句话说,你不应该包括可选的依赖关系。
无论哪种方式,你的启动程序都必须直接或间接地引用核心 Spring 启动程序(spring-boot-starter )(如果你的启动程序依赖于另一个启动程序,则无需添加它),如果仅使用你的自定义启动程序创建项目,则, Spring Boot 的核心功能将因核心启动器的出现而受到尊重。 |
---|
# 10. Kotlin 支持
Kotlin (opens new window)是一种针对 JVM(和其他平台)的静态类型语言,它允许编写简洁而优雅的代码,同时提供互操作性 (opens new window)用 Java 编写的现有库。
Spring Boot 通过利用其他 Spring 项目中的支持来提供 Kotlin 支持,例如 Spring 框架、 Spring 数据和反应堆。有关更多信息,请参见Spring Framework Kotlin support documentation (opens new window)。
启动 Spring boot 和 Kotlin 的最简单方法是遵循这个全面的教程 (opens new window)。你可以使用start.spring.io (opens new window)创建新的 Kotlin 项目。如果你需要支持,可以随时加入Kotlin Slack (opens new window)的 # Spring 频道,或者使用spring
和kotlin
标签在堆栈溢出 (opens new window)上提问。
# 10.1.所需经费
Spring 启动需要至少 Kotlin 1.3.x 并且通过依赖管理管理管理管理合适的 Kotlin 版本。要使用 Kotlin,org.jetbrains.kotlin:kotlin-stdlib
和org.jetbrains.kotlin:kotlin-reflect
必须存在于 Classpath 上。也可以使用kotlin-stdlib
变种kotlin-stdlib-jdk7
和kotlin-stdlib-jdk8
。
由于Kotlin classes are final by default (opens new window),你可能想要配置kotlin-spring (opens new window)插件,以便自动打开 Spring-注释的类,以便可以代理它们。
在 Kotlin 中,序列化/反序列化 JSON 数据需要Jackson’s Kotlin module (opens new window)。当在 Classpath 上找到它时,它会自动注册。如果 Jackson 和 Kotlin 存在但 Jackson Kotlin 模块不存在,则记录警告消息。
默认情况下,如果在start.spring.io (opens new window)上引导一个 Kotlin 项目,就会提供这些依赖项和插件。 |
---|
# 10.2.零安全
Kotlin 的关键特征之一是零安全 (opens new window)。它在编译时处理null
值,而不是将问题推迟到运行时并遇到NullPointerException
。这有助于消除常见的错误源,而无需支付Optional
之类包装器的费用。 Kotlin 还允许使用如在此comprehensive guide to null-safety in Kotlin (opens new window)中所描述的具有可空的值的函数构造。
虽然 Java 不允许在其类型系统中表示空安全,但 Spring Framework、 Spring Data 和 Reactor 现在通过对工具友好的注释为其 API 提供了空安全。默认情况下,来自 Kotlin 中使用的 Java API 的类型被识别为平台类型 (opens new window),对其放松了空检查。Kotlin’s support for JSR 305 annotations (opens new window)与空性注释相结合,为 Kotlin 中相关的 Spring API 提供了空安全性。
可以通过添加带有以下选项的-Xjsr305
编译器标志来配置 JSR305 检查:-Xjsr305={strict|warn|ignore}
。默认行为与-Xjsr305=warn
相同。在从 Spring API 推断出的 Kotlin 类型中,strict
值必须考虑到空安全性,但在使用时应了解 Spring API 的无效性声明即使在较小的版本之间也可以发展,并且将来可能会添加更多的检查)。
还不支持泛型类型参数、varargs 和数组元素的可空性。 有关最新信息,请参见SPR-15942 (opens new window)。 还请注意 Spring Boot 自己的 API 是尚未注释 (opens new window)。 |
---|
# 10.3. Kotlin 空气污染指数
# 10.3.1.运行应用程序
Spring Boot 提供了一种惯用的方式来运行带有runApplication<MyApplication>(*args)
的应用程序,如以下示例所示:
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
这是SpringApplication.run(MyApplication::class.java, *args)
的直接替换。它还允许定制应用程序,如以下示例所示:
runApplication<MyApplication>(*args) {
setBannerMode(OFF)
}
# 10.3.2.扩展
Kotlin extensions (opens new window)提供了扩展具有附加功能的现有类的能力。 Spring 引导 Kotlin API 利用这些扩展为现有的 API 添加新的 Kotlin 特定的便利。
TestRestTemplate
扩展,类似于 Spring 框架中为RestOperations
提供的扩展,在 Spring 框架中。在其他事情中,这些扩展使得有可能利用 Kotlin 具体化的类型参数。
# 10.4.依赖管理
为了避免混合 Kotlin 依赖于 Classpath 的不同版本, Spring 引导导入 Kotlin BOM。
有了 Maven, Kotlin 版本可以通过设置kotlin.version
属性来定制,并且为kotlin-maven-plugin
提供插件管理。在 Gradle 中, Spring 引导插件自动将kotlin.version
与 Kotlin 插件的版本对齐。
Spring Boot 还通过导入 Kotlin 协程 BOM 来管理协程依赖的版本。可以通过设置kotlin-coroutines.version
属性来定制版本。
如果一个 Kotlin 项目至少对start.spring.io (opens new window)具有一个反应性依赖项,则默认情况下提供org.jetbrains.kotlinx:kotlinx-coroutines-reactor 依赖项。 |
---|
# 10.5.@configrationProperties
@ConfigurationProperties
与[@ConstructorBinding
](#features.external-config.typesafe-configuration-properties.constructor-binding)结合使用时,支持具有不可变val
属性的类,如以下示例所示:
@ConstructorBinding
@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
val name: String,
val description: String,
val myService: MyService) {
data class MyService(
val apiToken: String,
val uri: URI
)
}
要使用注释处理器生成你自己的元数据,[kapt 应该配置](https://kotlinlang.org/DOCS/reference/kapt.html)与spring-boot-configuration-processor 依赖关系。注意,由于 Kapt 提供的模型中的限制,一些功能(例如检测默认值或不推荐项)不起作用。 |
---|
# 10.6.测试
虽然可以使用 JUnit4 来测试 Kotlin 代码,但 JUnit5 是默认提供的,并且是推荐的。JUnit5 允许一个测试类被实例化一次,并在类的所有测试中重用。这使得在非静态方法上使用@BeforeAll
和@AfterAll
注释成为可能,这非常适合 Kotlin。
要模拟 Kotlin 类,建议使用MockK (opens new window)。如果你需要Mockk
特定于 mockito 的[@MockBean
和@SpyBean
注释](#features.testing. Spring-boot-applications.mocking-beans)的等价物,则可以使用斯普林莫克 (opens new window),它提供类似的@MockkBean
和@SpykBean
注释。
# 10.7.资源
# 10.7.1.进一步阅读
Kotlin Slack (opens new window)(带专用 # Spring 频道)
[Stackoverflow with
spring
andkotlin
tags](https://stackoverflow.com/questions/tagged/ Spring + Kotlin)Tutorial: building web applications with Spring Boot and Kotlin (opens new window)
Developing Spring Boot applications with Kotlin (opens new window)
A Geospatial Messenger with Kotlin, Spring Boot and PostgreSQL (opens new window)
Introducing Kotlin support in Spring Framework 5.0 (opens new window)
Spring Framework 5 Kotlin APIs, the functional way (opens new window)
# 10.7.2.例子
spring-boot-kotlin-demo (opens new window):常规 Spring 引导 + Spring 数据 JPA 项目
mixit (opens new window): Spring Boot2+WebFlux+Reactive Spring Data MongoDB
spring-kotlin-fullstack (opens new window):使用 Kotlin2js 代替 JavaScript 或 TypeScript 作为前端的 webflux Kotlin fullstack 示例
spring-petclinic-kotlin (opens new window): Spring PetClinic 样本应用程序的 Kotlin 版本
spring-kotlin-deepdive (opens new window):将 Boot1.0+Java 逐步迁移到 Boot2.0+ Kotlin
# 11. 接下来要读什么?
如果你想了解更多关于本节中讨论的任何类的信息,请参见Spring Boot API documentation (opens new window),或者你也可以浏览源代码直接 (opens new window)。如果你有具体的问题,请参阅how-to部分。
如果你对 Spring Boot 的核心功能感到满意,那么你可以继续阅读可投入生产的功能。
← 用 Spring boot 开发 万维网 →