本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
本节深入介绍了Spring Boot的细节。在这里,你可以了解到你可能想要使用和定制的关键功能。如果你还没有这样做,你可能想阅读 "入门" 和 "使用Spring Boot开发" 部分,这样你就有了良好的基础知识。
1. SpringApplication
通过 SpringApplication
类,你可以从 main()
方法中启动Spring应用程序。
在许多情况下,你可以直接调动 SpringApplication.run
静态方法,如以下例子所示。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
当你的应用程序启动时,你应该看到类似于以下的输出。
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.2.0-SNAPSHOT) 2023-09-10T13:33:54.881+08:00 INFO 14060 --- [ main] o.s.b.d.f.s.MyApplication : Starting MyApplication using Java 17 with PID 14060 (/opt/apps/myapp.jar started by myuser in /opt/apps/) 2023-09-10T13:33:54.887+08:00 INFO 14060 --- [ main] o.s.b.d.f.s.MyApplication : No active profile set, falling back to 1 default profile: "default" 2023-09-10T13:33:56.187+08:00 INFO 14060 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2023-09-10T13:33:56.202+08:00 INFO 14060 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-09-10T13:33:56.202+08:00 INFO 14060 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.10] 2023-09-10T13:33:56.344+08:00 INFO 14060 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2023-09-10T13:33:56.347+08:00 INFO 14060 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1389 ms 2023-09-10T13:33:56.821+08:00 INFO 14060 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2023-09-10T13:33:56.831+08:00 INFO 14060 --- [ main] o.s.b.d.f.s.MyApplication : Started MyApplication in 2.379 seconds (process running for 2.715)
默认情况下,会显示 INFO
级别的日志信息,包括一些相关的启动细节,比如启动应用程序的用户。
如果你需要 INFO
以外级别的日志,你可以设置它,如"日志级别"中所述。
应用程序的版本是使用main方法所在类的包的实现版本来确定的。
启动信息的记录可以通过设置 spring.main.log-startup-info
为 false
来关闭。
这也将关闭应用程序的激活的profiles的日志记录。
为了在启动过程中增加额外的日志记录,你可以在 SpringApplication 的子类中覆写 logStartupInfo(boolean) 。
|
1.1. 启用失败
如果你的应用程序启动失败,注册的 FailureAnalyzers
会尝试提供一个专门的错误信息提示和具体的解决办法。
例如,如果你在端口 8080
上启动一个网络应用,而该端口已经被使用,你应该看到类似于下面的信息。
*************************** 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 的实现,你也可以 添加自己的实现。
|
如果 failure analyzer 不能够处理异常,你仍然可以显示完整的条件报告以更好地了解出错的原因。要实现这个,你需要为 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
启用 debug
属性或启用 DEBUG
日志 。
例如,如果你通过使用 java -jar
来运行你的应用程序,你可以按以下方式启用 debug
属性。
$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug
1.2. 懒初始化(Lazy Initialization)
SpringApplication
允许应用程序被懒初始化。
当启用懒初始化时,Bean在需要时被创建,而不是在应用程序启动时。
因此,懒初始化可以减少应用程序的启动时间。
在一个Web应用程序中,启用懒初始化后将导致许多与Web相关的Bean在收到HTTP请求之后才会进行初始化。
懒初始化的一个缺点是它会延迟发现应用程序的问题。 如果一个配置错误的Bean被懒初始化了,那么在启动过程中就不会再出现故障,问题只有在Bean被初始化时才会显现出来。 还必须注意确保JVM有足够的内存来容纳应用程序的所有Bean,而不仅仅是那些在启动期间被初始化的Bean。 由于这些原因,默认情况下不启用懒初始化,建议在启用懒初始化之前,对JVM的堆大小进行微调。
可以使用 SpringApplicationBuilder
的 lazyInitialization
方法或 SpringApplication
的 setLazyInitialization
方法以编程方式启用懒初始化。
另外,也可以使用 spring.main.lazy-initialization
属性来启用,如下例所示。
spring.main.lazy-initialization=true
spring:
main:
lazy-initialization: true
如果你想禁用某些Bean的懒初始化,同时对应用程序的其他部分使用懒初始化,你可以使用 @Lazy(false) 注解将其`Lazy` 属性显式地设置为 false。
|
1.3. 自定义 Banner
启动时打印的Banner可以通过在 classpath 中添加 banner.txt
文件或通过将 spring.banner.location
属性设置为该文件的位置来自定义。
如果该文件的编码不是UTF-8,你可以通过 spring.banner.charset
属性设置其字符编码。
在你的 banner.txt
文件中,你可以使用 Environment
中的任何key,以及以下任何占位符。
变量 | 介绍 |
---|---|
|
你的应用程序的版本号,也就是 |
|
你的应用程序的版本号,如在`MANIFEST.MF`中声明的那样,并以格式化显示(用括号包围,以 |
|
你所使用的Spring Boot版本。
例如 |
|
你正在使用的Spring Boot版本,格式化显示(用大括号包围并以 |
|
其中 |
|
你的应用程序的标题,正如在 |
如果你想以编程方式生成一个Banner,可以使用 SpringApplication.setBanner(..) 方法。
实现 org.springframework.boot.Banner 接口并实现你自己的 printBanner() 方法。
|
你也可以使用 spring.main.banner-mode
属性来决定Beann打印模式。例如打印到 System.out
(console
)上,发送到配置的logger(log
),或者根本不打印(off
)。
打印的Banner被注册为一个单例Bean,名字是:springBootBanner
。
这就是为什么我们建议你总是使用 |
1.4. 自定义 SpringApplication
如果 SpringApplication
的默认值不符合你的需求,你可以创建一个实例并对其进行自定义。
例如,要关闭Banner,你可以这样写。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
}
}
传递给 SpringApplication 的构造参数是 Spring Bean 的配置源。
在大多数情况下,这些是对 @Configuration 类的引用,但它们也可能是对 @Component 类的直接引用。
|
也可以通过使用 application.properties
文件来配置 SpringApplication
。
参见 "外部配置" 以了解详情。
关于配置选项的完整列表,请参阅 SpringApplication
Javadoc。
1.5. Builder API
如果你需要建立一个包含层次结构的 ApplicationContext
(具有父/子关系的多个context),或者你喜欢使用"`fluent`" Builder API,你可以使用 SpringApplicationBuilder
。
SpringApplicationBuilder
允许你链式调用多个方法,包括调用 parent
和 child
方法,创建一个层次结构,如下例所示。
new SpringApplicationBuilder().sources(Parent.class)
.child(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
SpringApplicationBuilder()
.sources(Parent::class.java)
.child(Application::class.java)
.bannerMode(Banner.Mode.OFF)
.run(*args)
在创建多层次的 ApplicationContext 时,有一些限制。
例如,Web组件 必须 包含在子context中,并且父context和子context都使用相同的 Environment 。
参见 SpringApplicationBuilder Javadoc 以了解全部细节。
|
1.6. Application 可用性
在平台上部署时,应用程序可以使用 Kubernetes Probes 等基础设施向平台提供有关其可用性的信息。Spring Boot对常用的 “liveness” 和 “readiness” 可用性状态提供了开箱即用的支持。如果你使用Spring Boot的 “actuator” ,那么这些状态将作为健康端点组(health endpoint groups)暴露出来。
此外,你也可以通过将 ApplicationAvailability
接口注入到你自己的Bean中来获得可用性状态。
1.6.1. Liveness State
一个应用程序的 “Liveness” 状态告诉我们它的内部状态是否允许它正常工作,或者在当前失败的情况下自行恢复。 一个broken状态的 “Liveness” 状态意味着应用程序处于一个无法恢复的状态,基础设施应该重新启动应用程序。
一般来说,"Liveness" 状态不应该基于外部检查,比如健康检查。 如果是这样,外部系统如果发生异常(数据库、Web API、缓存)将引发大规模的重启和整个平台的级联故障。 |
Spring Boot应用程序的内部状态大多由Spring ApplicationContext
表示。如果 application context 已成功启动,Spring Boot就认为应用程序处于有效状态。一旦context被刷新,应用程序就被认为是活的,见Spring Boot应用程序生命周期和相关Application Event
1.6.2. Readiness State
应用程序的 “Readiness” 状态告诉平台,该应用程序是否准备好处理流量。
failing状态的 “Readiness” 告诉平台,它暂时不应该将流量发送到该应用程序。
这通常发生在启动期间,当 CommandLineRunner
和 ApplicationRunner
组件还在被处理的时候,或者是应用程序觉得目前负载已经到了极限,不能再处理额外的请求的时候。
一旦 ApplicationRunner
和 CommandLineRunner
被调用完成,就认为应用程序已经准备好了,见Spring Boot应用程序生命周期和相关的Application Event。
预计在启动期间运行的任务应该交由 CommandLineRunner 和 ApplicationRunner 组件执行,而不是使用Spring组件的生命周期回调,如 @PostConstruct 。
|
1.6.3. 管理应用程序的可用性状态
应用组件可以在任何时候通过注入 ApplicationAvailability
接口并调用其上的方法来检索当前的可用性状态。
更多时候,应用程序会想要监听状态更新或更新应用程序的状态。
例如,我们可以将应用程序的 "Readiness" 状态导出到一个文件,这样Kubernetes的 "exec Probe" 就可以查看这个文件了。
@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;
}
}
}
@Component
class MyReadinessStateExporter {
@EventListener
fun onStateChange(event: AvailabilityChangeEvent<ReadinessState?>) {
when (event.state) {
ReadinessState.ACCEPTING_TRAFFIC -> {
// create file /tmp/healthy
}
ReadinessState.REFUSING_TRAFFIC -> {
// remove file /tmp/healthy
}
else -> {
// ...
}
}
}
}
当应用程序中断而无法恢复时,我们还可以更新应用程序的状态。
@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);
}
}
}
@Component
class MyLocalCacheVerifier(private val eventPublisher: ApplicationEventPublisher) {
fun checkLocalCache() {
try {
// ...
} catch (ex: CacheCompletelyBrokenException) {
AvailabilityChangeEvent.publish(eventPublisher, ex, LivenessState.BROKEN)
}
}
}
Spring Boot提供了Kubernetes HTTP探针,通过Actuator Health Endpoints探测 "Liveness "和 "Readiness"。 你可以得到更多关于在Kubernetes上部署Spring Boot应用程序的指导,在专门的章节里。
1.7. Application 事件和监听器
除了常见的 Spring 框架事件,如 ContextRefreshedEvent
外,SpringApplication 还会发布一些额外的应用事件。
有些事件实际上是在 如果你想让这些监听器自动注册,不管应用程序是如何创建的,你可以在你的项目中添加一个 org.springframework.context.ApplicationListener=com.example.project.MyListener |
当应用程序运行时,Application event按以下顺序发布。
-
一个
ApplicationStartingEvent
在运行开始时被发布,但在任何处理之前,除了注册监听器和初始化器之外。 -
当在上下文中使用的
Environment
已知,但在创建上下文之前,将发布ApplicationEnvironmentPreparedEvent
。 -
当
ApplicationContext
已准备好并且ApplicationContextInitializers
被调用,但在任何Bean定义被加载之前,ApplicationContextInitializedEvent
被发布。 -
一个
ApplicationPreparedEvent
将在刷新开始前但在Bean定义加载后被发布。 -
在上下文被刷新之后,但在任何应用程序和命令行运行程序被调用之前,将发布一个
ApplicationStartedEvent
。 -
紧接着发布
LivenessState.CORRECT
状态的AvailabilityChangeEvent
,表明应用程序被认为是存活的。 -
在任何ApplicationRunner 和 CommandLineRunner被调用后,将发布一个
ApplicationReadyEvent
。 -
紧接着发布
ReadinessState.ACCEPTING_TRAFFIC
状态的AvailabilityChangeEvent
,表明应用程序已经准备好为请求提供服务。 -
如果启动时出现异常,将发布一个
ApplicationFailedEvent
。
以上列表仅包括与 SpringApplication
相关的 SpringApplicationEvent
。
除此以外,以下事件也会在 ApplicationPreparedEvent
之后和 ApplicationStartedEvent
之前发布。
-
在
WebServer
准备好后发布WebServerInitializedEvent
。ServletWebServerInitializedEvent
和ReactiveWebServerInitializedEvent
分别对应Servlet和reactive的实现。 -
当
ApplicationContext
被刷新时,将发布一个ContextRefreshedEvent
。
你通常不需要使用application event,但知道它们的存在会很方便。 在内部,Spring Boot使用事件来处理各种任务。 |
事件监听器不应该运行潜在耗时的任务,因为它们默认是在同一个线程中执行。 考虑使用 ApplicationRunner 和 CommandLineRunner 代替。 |
Application event 是通过使用Spring框架的事件发布机制来发布的。
该机制的一部分确保了发布给子context中的listener的事件也会发布给任何祖先context的listener。
因此,如果你的应用程序使用了多层级的 SpringApplication
,一个监听器可能会收到同一类型应用程序事件的多个实例(重复收到事件通知)。
为了让你的listener能够区分事件是由哪个context(子、父)发送的,可以注入其application context,然后将注入的context与事件的context进行比较。
context可以通过实现 ApplicationContextAware
来注入,如果监听器是一个Bean,则可以通过使用 @Autowired
来注入。
1.8. WEB 环境(Environment)
SpringApplication
会试图帮你创建正确类型的 ApplicationContext
。
确定为 WebApplicationType
的算法如下。
-
如果Spring MVC存在,就会使用
AnnotationConfigServletWebServerApplicationContext
。 -
如果Spring MVC不存在而Spring WebFlux存在,则使用
AnnotationConfigReactiveWebServerApplicationContext
。 -
否则,将使用
AnnotationConfigApplicationContext
。
这意味着,如果你在同一个应用程序中使用Spring MVC和新的 WebClient
(来自于Spring WebFlux),Spring MVC将被默认使用。
你可以通过调用 setWebApplicationType(WebApplicationType)
来轻松覆盖。
也可以通过调用 setApplicationContextFactory(…)
来完全控制使用的 ApplicationContext
类型。
当在JUnit测试中使用 SpringApplication 时,通常需要调用 setWebApplicationType(WebApplicationType.NONE) 。
|
1.9. 访问应用参数
如果你需要访问传递给 SpringApplication.run(..)
的命令行参数,你可以注入一个 org.springframework.boot.ApplicationArguments
bean。
通过 ApplicationArguments
接口,你可以访问原始的 String[]
参数以及经过解析的 option
和 non-option
参数。如以下例子所示。
@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"]
}
}
@Component
class MyBean(args: ApplicationArguments) {
init {
val debug = args.containsOption("debug")
val files = args.nonOptionArgs
if (debug) {
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(…)
执行完毕之前被调用。
这很适合用于执行那些需要在处理HTTP请求之前执行的任务。 |
CommandLineRunner
接口以字符串数组形式提供了对应用程序参数(启动参数)的访问。而 ApplicationRunner
使用前面讨论的 ApplicationArguments
接口。
下面的例子显示了一个带有 run
方法的 CommandLineRunner
。
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
// Do something...
}
}
@Component
class MyCommandLineRunner : CommandLineRunner {
override fun run(vararg args: String) {
// Do something...
}
}
如果定义了多个 CommandLineRunner
或 ApplicationRunner
Bean,并且需要它们按照特定的顺序先后执行。那么可以实现 org.springframework.core.Ordered
接口或使用 org.springframework.core.annotation.Order
注解来指定顺序。
1.11. 程序退出
每个 SpringApplication
都向JVM注册了一个shutdown hook,以确保 ApplicationContext
在退出时优雅地关闭。
所有标准的Spring生命周期回调(如 DisposableBean
接口或 @PreDestroy
注解)都可以使用。
此外,如果Bean希望在调用 SpringApplication.exit()
时返回特定的退出代码,可以实现 org.springframework.boot.ExitCodeGenerator
接口。
然后,这个退出代码可以被传递给 System.exit()
,将其作为状态代码返回,如下面的例子所示。
@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)));
}
}
@SpringBootApplication
class MyApplication {
@Bean
fun exitCodeGenerator() = ExitCodeGenerator { 42 }
}
fun main(args: Array<String>) {
exitProcess(SpringApplication.exit(
runApplication<MyApplication>(*args)))
}
另外,ExitCodeGenerator
接口可以由异常(Exception)实现。
当遇到这种异常时,Spring Boot会返回由实现的 getExitCode()
方法提供的退出代码。
如果有多个 ExitCodeGenerator
,则使用第一个生成的非零退出代码。
要控制生成器(Generator)的调用顺序,你可以实现 org.springframework.core.Ordered
接口或使用 org.springframework.core.annotation.Order
注解。
1.12. 管理功能
通过指定 spring.application.admin.enabled
属性,可以启用应用程序的管理相关功能。
这在 MBeanServer
平台上暴露了 SpringApplicationAdminMXBean
。
你可以使用这个功能来远程管理你的Spring Boot应用程序。
这个功能对任何服务包装器的实现也很有用。
如果你想知道应用程序是在哪个HTTP端口上运行的,可以通过 local.server.port 这个KEY来获取。
|
1.13. 应用程序启动追踪
在应用程序启动期间,SpringApplication
和 ApplicationContext
执行许多与应用程序生命周期相关的任务。
beans的生命周期,甚至是处理应用事件。
通过 ApplicationStartup
, ,Spring框架 允许你用 StartupStep
对象来跟踪应用程序的启动顺序。
这些数据可以为分析目的而收集,或者只是为了更好地了解应用程序的启动过程。
你可以在设置 SpringApplication
实例时选择一个 ApplicationStartup
实现。
例如,要使用 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);
}
}
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
applicationStartup = BufferingApplicationStartup(2048)
}
}
第一个可用的实现,FlightRecorderApplicationStartup
是由Spring框架提供的。
它将Spring特有的启动事件添加到Java Flight Recorder会话中,旨在对应用程序进行分析,并将其Spring context生命周期与JVM事件(如分配、GC、类加载……)联系起来。
一旦配置好,你就可以通过启用Flight Recorder运行应用程序来记录数据。
$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar
Spring Boot提供了 BufferingApplicationStartup
实现;该实现旨在缓存启动步骤并将其排入(发送)到外部度量系统。
应用程序可以在任何组件中要求(通过注入)获得 BufferingApplicationStartup
类型的bean。
Spring Boot也可以通过配置暴露一个 startup
endpoint,以JSON形式提供这些信息。
2. 外部化的配置
Spring Boot可以让你将配置外部化,这样你就可以在不同的环境中使用相同的应用程序代码。 你可以使用各种外部配置源,包括Java properties 文件、YAML文件、环境变量和命令行参数。
属性值可以通过使用 @Value
注解直接注入你的Bean,也可以通过Spring 的 Environment
访问,或者通过 @ConfigurationProperties
绑定到对象。
Spring Boot 使用一个非常特别的 PropertySource
顺序,旨在允许合理地重写值。
后面的 property source 可以覆盖前面属性源中定义的值。
按以下顺序考虑。
-
默认属性(通过
SpringApplication.setDefaultProperties
指定)。 -
@Configuration 类上的
@PropertySource
注解。请注意,这样的属性源直到application context被刷新时才会被添加到环境中。这对于配置某些属性来说已经太晚了,比如logging.*
和spring.main.*
,它们在刷新开始前就已经被读取了。 -
配置数据(如
application.properties
文件)。 -
RandomValuePropertySource
,它只有random.*
属性。 -
操作系统环境变量
-
Java System properties (
System.getProperties()
). -
java:comp/env
中的 JNDI 属性。 -
ServletContext
init parameters. -
ServletConfig
init parameters. -
来自
SPRING_APPLICATION_JSON
的属性(嵌入环境变量或系统属性中的内联JSON)。 -
命令行参数
-
你在测试中的
properties
属性。在@SpringBootTest
和测试注解中可用,用于测试你的应用程序的一个特定片断。 -
@DynamicPropertySource
注解在你的测试中。 -
你测试中的https://docs.spring.io/spring-framework/docs/6.1.0-M1/javadoc-api/org/springframework/test/context/TestPropertySource.html[
@TestPropertySource
] 注解. -
当devtools处于活动状态时,
$HOME/.config/spring-boot
目录下的Devtools全局设置属性。
配置数据文件按以下顺序考虑。
-
在你的jar中打包的Application properties(application.properties 和 YAML)。
-
在你的jar中打包的 特定的 Profile application properties(
application-{profile}.properties
和 YAML)。 -
在你打包的jar之外的Application properties性(application.properties和YAML)。
-
在你打包的jar之外的特定的 Profile application properties(
application-{profile}.properties
和YAML)。
建议你在整个应用程序中坚持使用一种格式。如果你同时有 .properties 和YAML格式的配置文件,那么 .properties 优先。
|
如果你使用环境变量而不是系统属性,大多数操作系统不允许使用句点分隔的键名,但你可以使用下划线代替(例如, SPRING_CONFIG_NAME 代替 spring.config.name )。详见 从环境变量绑定 。
|
如果你的应用程序在servlet容器或应用服务器中运行,那么JNDI属性(在 java:comp/env 中)或servlet上下文初始化参数可以代替环境变量或系统属性,或者与之一样。
|
为了提供一个具体的例子,假设你开发了一个 @Component
,使用了一个 name
属性,如下面的例子所示。
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
@Component
class MyBean {
@Value("\${name}")
private val name: String? = null
// ...
}
在你的应用程序的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.setAddCommandLineProperties(false)
禁用它们。
2.2. JSON Application Properties
环境变量和系统属性往往有限制,这意味着有些属性名称不能使用。 为了帮助解决这个问题,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. 外部的 Application Properties
当你的应用程序启动时,Spring Boot会自动从以下位置找到并加载 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
环境属性来引用一个明确的位置。
该属性接受一个逗号分隔的列表,其中包含一个或多个要检查的位置。
下面的例子显示了如何指定两个不同的文件。
$ java -jar myproject.jar --spring.config.location=\
optional:classpath:/default.properties,\
optional:classpath:/override.properties
如果 配置文件是可选的,并且可以是不存在的,那么请使用 optional: 前缀。
|
spring.config.name , spring.config.location , 和 spring.config.extra-location 很早就用来确定哪些文件必须被加载。
它们必须被定义为环境属性(通常是操作系统环境变量,系统属性,或命令行参数)。
|
如果 spring.config.location
包含目录(而不是文件),它们应该以 /
结尾。
在运行时,它们将被附加上由 spring.config.name
生成的名称,然后被加载。
在 spring.config.location
中指定的文件被直接导入。
目录和文件位置值也被扩展,以检查特定的配置文件。例如,如果你的 spring.config.location 是 classpath:myconfig.properties ,你也会发现适当的 classpath:myconfig-<profile>.properties 文件被加载。
|
在大多数情况下,你添加的每个 spring.config.location
项将引用一个文件或目录。
位置是按照它们被定义的顺序来处理的,后面的位置可以覆盖前面的位置的值。
如果你有一个复杂的位置设置,而且你使用特定的配置文件,你可能需要提供进一步的提示,以便Spring Boot知道它们应该如何分组。一个位置组是一个位置的集合,这些位置都被认为是在同一级别。例如,你可能想把所有classpath位置分组,然后是所有外部位置。一个位置组内的项目应该用 ;
分隔。更多细节见 “指定 profile” 部分的例子。
通过使用 spring.config.location
配置的位置取代默认位置。
例如,如果 spring.config.location
被配置为 optional:classpath:/custom-config/,optional:file:./custom-config/
,考虑的完整位置集如下。
-
optional:classpath:custom-config/
-
optional:file:./custom-config/
如果你喜欢添加额外的位置,而不是替换它们,你可以使用 spring.config.extra-location
。
从附加位置加载的属性可以覆盖默认位置的属性。
例如,如果 spring.config.extra-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)中为你的应用程序提供默认值。
然后,这些默认值可以在运行时被位于其中一个自定义位置的不同文件覆盖。
2.3.1. 可选的位置(Optional Locations)
默认情况下,当指定的配置数据位置不存在时,Spring Boot将抛出一个 ConfigDataLocationNotFoundException
,你的应用程序将无法启动。
如果你想指定一个位置,但你不介意它并不总是存在,你可以使用 optional:
前缀。你可以在 spring.config.location和spring.config.extra-location
属性中使用这个前缀,也可以在 spring.config.import
声明中使用。
例如,spring.config.import
值为 optional:file:./myconfig.properties
允许你的应用程序启动,即使 myconfig.properties
文件丢失。
如果你想忽略所有的 ConfigDataLocationNotFoundExceptions
并始终继续启动你的应用程序,你可以使用 spring.config.on-not-found
属性。
使用 SpringApplication.setDefaultProperties(..)
或使用系统/环境变量将其值设置为 ignore
。
2.3.2. 通配符地址
如果一个配置文件的位置在最后一个路径段中包含 *
字符,它就被认为是一个通配符位置。
通配符在加载配置时被扩展,因此,直接的子目录也被检查。
通配符位置在Kubernetes这种有多个配置属性的来源的环境中特别有用。
例如,如果你有一些Redis配置和一些MySQL配置,你可能想把这两部分配置分开,同时要求这两部分都存在于一个 application.properties
文件中。
这可能会导致两个独立的 application.properties
文件挂载在不同的位置,如 /config/redis/application.properties
和 /config/mysql/application.properties
。
在这种情况下,有一个通配符位置 config/*/
,将导致两个文件被处理。
默认情况下,Spring Boot将 config/*/
列入默认搜索位置。
这意味着你的jar之外的 /config
目录的所有子目录都会被搜索到。
你可以在 spring.config.location
和 spring.config.extra-location
属性中使用通配符位置。
通配符位置必须只包含一个 * 并以 */ 结尾,用于搜索属于目录的位置,或 */<filename> 用于搜索属于文件的位置。
带有通配符的位置将根据文件名的绝对路径按字母顺序排序。
|
通配符位置只对外部目录起作用。
你不能在 classpath: 位置中使用通配符。
|
2.3.3. 特定文件(Profile Specific Files)
除了 application
属性文件,Spring Boot还将尝试使用 application-{profile}
的命名惯例加载profile特定的文件。
例如,如果你的应用程序激活了名为 prod
的配置文件(spring.profiles.active=prod
)并使用YAML文件,那么 application.yaml
和 application-prod.yaml
都将被考虑。
特定文件(profiles
)的属性与标准的 application.properties
的位置相同,特定文件总是优先于非特定文件。
如果指定了几个配置文件,则采用最后胜出的策略。
例如,如果配置文件 prod,live
是由 spring.profiles.active
属性指定的,application-prod.properties
中的值可以被 application-live.properties
中的值所覆盖。
最后胜出的策略适用于location group级别。
例如,拿我们上面的 /cfg application-live.properties /ext application-live.properties application-prod.properties 当我们有一个
当我们用
|
Environment
有一组默认的配置文件(默认为 [default]
),如果没有设置活动的配置文件,就会使用这些配置文件。
换句话说,如果没有明确激活的配置文件,那么就会考虑来自 application-default
的属性。
属性文件只被加载一次。 如果你已经直接导入了一个配置文件的特定属性文件,那么它将不会被第二次导入。 |
2.3.4. 导入额外的数据
application properties 中可以使用 spring.config.import
属性从其他地方导入更多的配置数据。
导入在被发现时被处理,并被视为紧接着声明导入的文件下面插入的额外文件。
例如,你可能在你的 classpath application.properties
文件中有以下内容。
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
spring:
application:
name: "myapp"
config:
import: "optional:file:./dev.properties"
这将触发导入当前目录下的 dev.properties
文件(如果存在这样的文件)。
导入的 dev.properties
中的值将优先于触发导入的文件。
在上面的例子中,dev.properties
可以将 spring.application.name
重新定义为一个不同的值。
一个导入只会被导入一次,无论它被声明多少次。 一个导入在properties/yaml文件内的单个文件中被定义的顺序并不重要。 例如,下面的两个例子产生相同的结果。
spring.config.import=my.properties
my.property=value
spring:
config:
import: "my.properties"
my:
property: "value"
my.property=value
spring.config.import=my.properties
my:
property: "value"
spring:
config:
import: "my.properties"
在上述两个例子中,my.properties
文件的值将优先于触发其导入的文件。
在一个单一的 spring.config.import
属性下可以指定多个位置。
位置将按照它们被定义的顺序被处理,后来的导入将被优先处理。
在适当的时候,特定配置文件的变体也被考虑导入。
上面的例子将导入 my.properties 以及任何 my-<profile>.properties 变体。
|
Spring Boot 提供了可插拔的API(插件),允许支持各种不同的位置地址。 默认情况下,你可以导入Java Properties、YAML和 “配置树” 。 第三方jar可以提供对其他技术的支持(不要求必须是本地文件)。 例如,你可以想象配置数据来自外部存储,如Consul、Apache ZooKeeper或Netflix Archaius(包括Nacos)。 如果你想支持你自己的位置(实现自己定义的配置加载),请参阅 |
2.3.5. 导入无扩展名的文件
有些云平台不能为卷装文件(volume mounted files)添加文件扩展名。 要导入这些无扩展名的文件,你需要给Spring Boot一个提示,以便它知道如何加载它们。 你可以通过把扩展名提示放在方括号里来做到这一点。
例如,假设你有一个 /etc/config/myconfig
文件,你希望以yaml形式导入。
你可以用下面的方法从你的 application.properties
中导入它。
spring.config.import=file:/etc/config/myconfig[.yaml]
spring:
config:
import: "file:/etc/config/myconfig[.yaml]"
2.3.6. 使用配置树(Configuration Trees)
当在云平台(如Kubernetes)上运行应用程序时,你经常需要读取平台提供的配置值。 将环境变量用于此类目的并不少见,但这可能有缺点,特别是如果该值是 secret 的。
作为环境变量的替代方案,许多云平台现在允许你将配置映射到挂载的数据卷。 例如,Kubernetes 可以卷挂载 ConfigMaps
和 Secrets
。
可以使用两种常见的 volume 挂载模式:
-
一个文件包含一套完整的属性(通常写成YAML)。
-
多个文件被写入一个目录树中,文件名成为 ‘key’,内容成为 ‘value’。
对于第一种情况,你可以使用 spring.config.import
直接导入YAML或属性文件,如上所述。
对于第二种情况,你需要使用 configtree:
前缀,以便Spring Boot知道它需要将所有文件作为属性公开。
举个例子,让我们想象一下,Kubernetes已经挂载了以下volume。
etc/ config/ myapp/ username password
username
文件的内容将是一个配置值,而 password
的内容将是一个 secret。
要导入这些属性,你可以在你的 application.properties
或 application.yaml
文件中添加以下内容。
spring.config.import=optional:configtree:/etc/config/
spring:
config:
import: "optional:configtree:/etc/config/"
然后你可以从 Environment
中以常规方式访问或注入 myapp.username
和 myapp.password
属性。
配置树下的文件夹构成了属性名称。
在上面的例子中,为了访问属性为 username 和 password ,你可以将 spring.config.import 设置为 optional:configtree:/etc/config/myapp 。
|
带有点符号的文件名也会被正确映射。
例如,在上面的例子中,/etc/config 中名为 myapp.username 的文件在 Environment 中的属性名将会是 myapp.username 。
|
配置树的值可以被绑定到字符串 String 和 byte[] 类型,这取决于预期的内容。
|
如果你有多个配置树要从同一个父文件夹导入,你可以使用通配符快捷方式。
任何以 /*/
结尾的 configtree:
位置将导入所有直接的子文件夹作为配置树。
例如,给定以下volume:
etc/ config/ dbconfig/ db/ username password mqconfig/ mq/ username password
你可以使用 configtree:/etc/config/*/
作为导入位置。
spring.config.import=optional:configtree:/etc/config/*/
spring:
config:
import: "optional:configtree:/etc/config/*/"
这将添加 db.username
、db.password
、mq.username
和 mq.password
属性。
使用通配符加载的目录是按字母顺序排列的。 如果你需要一个不同的顺序,那么你应该把每个位置作为一个单独的导入列出。 |
配置树也可用于Docker secret。
当Docker swarm服务被授予对secret的访问权时,该secret会被装载到容器中。
例如,如果一个名为 db.password
的secret。被挂载在 /run/secrets/
的位置,你可以用以下方法让 db.password
对Spring环境可用。
spring.config.import=optional:configtree:/run/secrets/
spring:
config:
import: "optional:configtree:/run/secrets/"
2.3.7. 属性占位符
application.properties
和 application.yaml
中的值在使用时通过现有的 Environment
过滤,所以你可以参考以前定义的值(例如,来自系统属性或环境变量)。
标准的 ${name}
属性占位符语法可以用在一个值的任何地方。
属性占位符也可以指定一个默认值,使用 :
来分隔默认值和属性名称,例如 ${name:default}
。
下面的例子显示了带默认值和不带默认值的占位符的使用情况。
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
app:
name: "MyApp"
description: "${app.name} is a Spring Boot application written by ${username:Unknown}"
假设 username
属性没有在其他地方设置,app.description
的值将是 MyApp is a Spring Boot application written by Unknown
。
你应该始终使用占位符中的属性名称的规范形式(仅使用小写字母的kebab-case)来引用它们。
这将允许Spring Boot使用与宽松绑定 例如, |
你也可以使用这种技术来创建现有Spring Boot属性的 “short” 变体。 详情请参见_howto.html_的方法。 |
2.3.8. 使用多文档文件(Working with Multi-Document Files)
Spring Boot允许你将一个物理文件分成多个逻辑文件,每个文件都是独立添加的。 文件是按顺序处理的,从上到下。 后面的文件可以覆盖前面文件中定义的属性。
对于 application.yaml
文件,使用标准的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
properties 文件的分隔符不能有任何前导空白,并且必须正好有三个连字符。 分隔符的前后两行不能是相同的注释前缀。 |
多文档属性文件通常与激活属性一起使用,如 spring.config.activated.on-profile 。
详见下一节。
|
多文档属性文件不能通过使用 @PropertySource 或 @TestPropertySource 注解加载。
|
2.3.9. 激活属性(Activation Properties)
有时,只在满足某些条件时激活一组特定的属性是很有用的。 例如,你可能有一些属性只有在特定的配置文件被激活时才相关。
你可以使用 spring.config.activation.*
有条件地激活一个属性文件。
激活属性有如下。
属性 | 说明 |
---|---|
|
一个必须与之匹配的配置文件表达式,以使文件处于活动状态(激活指定的配置文件时有效)。 |
|
必须检测到的 |
例如,下面指定第二个文件只有在Kubernetes上运行时才有效,并且只有在 “prod” 或 “staging” 配置文件处于活动状态时才有效。
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
myprop:
"always-set"
---
spring:
config:
activate:
on-cloud-platform: "kubernetes"
on-profile: "prod | staging"
myotherprop: "sometimes-set"
2.4. 加密配置属性(Encrypting Properties)
Spring Boot没有为加密属性值提供任何内置支持,但它提供了Hookm,可以用来修改Spring Environment
中包含的值。
EnvironmentPostProcessor
接口允许你在应用程序启动前操作 Environment
。
参见howto.html以了解详情。
如果你需要一种安全的方式来存储凭证和密码, Spring Cloud Vault 项目提供了对在 HashiCorp Vault中存储外部化配置的支持。
2.5. 使用 YAML
YAML 是JSON的超集,因此是指定分层配置数据的方便格式。
只要你的classpath上有 SnakeYAML 库,SpringApplication
类就会自动支持YAML作为properties的替代品。
如果你使用 “Starter”,SnakeYAML将由 spring-boot-starter 自动提供。
|
2.5.1. 将YAML映射到Properties
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]
索引的key。
例如,考虑下面的YAML。
my:
servers:
- "dev.example.com"
- "another.example.com"
前面的例子将被转化为如下属性。
my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用 [index] 符号的属性可以使用Spring Boot的 Binder 类绑定到Java List 或 Set 对象。
更多细节见下面的 “类型安全的配置属性” 部分。
|
YAML文件不能通过使用 @PropertySource 或 @TestPropertySource 注解来加载。
所以,在你需要以这种方式加载值的情况下,你需要使用一个 properties 文件。
|
2.6. 配置随机值
The RandomValuePropertySource
is useful for injecting random values (for example, into secrets or test cases).
It can produce integers, longs, uuids, or strings, as shown in the following example:
RandomValuePropertySource
对于注入随机值很有用(例如,注入密码或测试案例)。
它可以产生Integer、Long、UUID,或String,如下面的例子所示。
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]}
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 Boot支持为环境属性设置一个前缀。
如果系统环境被多个具有不同配置要求的Spring Boot应用程序共享,这就很有用。
系统环境属性的前缀可以直接在 SpringApplication
上设置。
例如,如果你将前缀设置为 input
,诸如 remote.timeout
这样的属性在系统环境中也将被解析为 input.remote.timeout
。
2.8. 类型安全的配置属性
使用 @Value("${property}")
注解来注入配置属性有时会很麻烦,特别是当你要处理多个属性或你的数据是分层的。
Spring Boot提供了一种处理属性的替代方法,让强类型的Bean管理和验证你的应用程序的配置。
2.8.1. JavaBean 属性绑定
如下面的例子所示,可以绑定一个声明了标准JavaBean属性的bean。
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
}
}
@ConfigurationProperties("my.service")
class MyProperties {
var isEnabled = false
var remoteAddress: InetAddress? = null
val security = Security()
class Security {
var username: String? = null
var password: String? = null
var roles: List<String> = ArrayList(setOf("USER"))
}
}
前面的POJO定义了以下属性。
-
my.service.enabled
,默认值为`false`。 -
my.service.remote-address
,其类型可由`String`强制提供。 -
my.service.security.username
,有一个嵌套的security
对象,其名称由该属性的名称决定。 特别是,那里完全没有使用类型,可以是SecurityProperties
。 -
my.service.security.password
. -
my.service.security.role
,有一个String
的集合,默认为USER
。
映射到Spring Boot中可用的 @ConfigurationProperties 类的属性,通过properties文件、YAML文件、环境变量和其他机制进行配置,这些属性是公共API,但类本身的 getters/setters 并不意味着可以直接使用(一句话,Spring也是通过getter/setter这些public方法进行设置值的,你别用)。
|
这样的设计依赖于一个默认的无参构造函数,getter和setter通常是必须的,因为绑定是通过标准的Java Beans property descriptor(Java内省)实现的,就像在Spring MVC中一样。 在以下情况下,可以省略setter。
有些人使用Project Lombok来自动添加getter和setter。 请确保Lombok不会为这样的类型生成任何特定的构造函数,因为它被容器自动用来实例化对象。 最后,只考虑标准的Java Bean属性,不支持对静态属性的绑定。 |
2.8.2. 构造函数绑定
上一节的例子可以用不可变的方式重写,如下例所示。
@ConfigurationProperties("my.service")
public class MyProperties {
public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
public static class Security {
public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
}
}
@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
val security: Security) {
class Security(val username: String, val password: String,
@param:DefaultValue("USER") val roles: List<String>)
}
在这种设置中,唯一的“带参数构造函数”的存在意味着应该使用该构造函数进行绑定。
这意味着绑定器会找到一个带有你希望绑定的参数的构造函数。
如果你的类有多个构造函数,可以使用 @ConstructorBinding
注解来指定使用哪个构造函数进行构造函数绑定。
如果要为一个只有一个“带参数构造函数”的类选择不绑定构造函数,该构造函数必须用 @Autowired
来注解。
构造函数绑定可以与 Record
一起使用。
除非你的记录有多个构造函数,否则没有必要使用 @ConstructorBinding
。
构造函数绑定类的嵌套成员(如上面例子中的 Security
)也将通过其构造函数被绑定。
默认值可以在构造函数参数和Record组件上使用 @DefaultValue
来指定。
转换服务将被应用于将注解的 String
值强制转换为缺失属性的目标类型。
参考前面的例子,如果没有属性绑定到 Security
, MyProperties
实例将包含一个 security
类型的 null
值。
为了使它包含一个非 null 的 Security
实例,即使没有属性与之绑定(当使用Kotlin时,这将要求 Security
的 username
和 password
参数被声明为 nullable,因为它们没有默认值),使用一个空的 @DefaultValue
注解。
public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
@DefaultValue val security: Security) {
class Security(val username: String?, val password: String?,
@param:DefaultValue("USER") val roles: List<String>)
}
要使用构造函数绑定,该类必须使用 @EnableConfigurationProperties 或配置属性扫描来启用。
你不能对通过常规Spring机制创建的Bean使用构造函数绑定(例如 @Component Bean,通过使用 @Bean 方法创建的Bean或通过使用 @Import 加载的Bean)。
|
要在原生镜像中使用构造函数绑定,必须用 -parameters 参数编译该类。如果你使用 Spring Boot 的 Gradle 插件或使用 Maven 和 spring-boot-starter-parent ,这将自动配置。
|
不建议将 java.util.Optional 与 @ConfigurationProperties 一起使用,因为它主要是作为一个返回类型使用。
因此,它并不适合配置属性注入。
为了与其他类型的属性保持一致,如果你确实声明了一个 Optional 属性,但它没有值,null 而不是一个空的 Optional 将被绑定。
|
2.8.3. 启用 @ConfigurationProperties 类
Spring Boot提供了绑定 @ConfigurationProperties
类型并将其注册为Bean的基础设施。
你可以在逐个类的基础上启用配置属性,或者启用配置属性扫描,其工作方式与组件扫描类似。
有时,用 @ConfigurationProperties
注解的类可能不适合扫描,例如,如果你正在开发你自己的自动配置或者你想有条件地启用它们。
在这些情况下,使用 @EnableConfigurationProperties
注解指定要处理的类型列表,
它可以注解在任何 @Configuration
类上,如下面的例子所示。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
@ConfigurationProperties("some.properties")
public class SomeProperties {
}
@ConfigurationProperties("some.properties")
class SomeProperties
要使用配置属性扫描,请向你的application添加 @ConfigurationPropertiesScan
注解。
通常,它被添加到用 @SpringBootApplication
注解的main类中,但它也可以被添加到任何 @Configuration
类上。
默认情况下,扫描会从注解所在的包开始,你如果想自定义扫描其他包,可以参考如下。
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {
}
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication
当 假设它在 |
我们建议 @ConfigurationProperties
只处理 environment,特别是不从上下文注入其他Bean。
对于边角案例(特殊情况),可以使用 setter 注入或框架提供的任何 *Aware
接口(如 EnvironmentAware
,如果你需要访问 Environment
)。
如果你仍然想使用构造器注入其他Bean,配置属性Bean必须用 @Component
来注解,并使用基于JavaBean的属性绑定。
2.8.4. 使用 @ConfigurationProperties 类
这种配置方式与 SpringApplication
外部YAML配置配合得特别好,如以下例子所示。
my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"
要使用 @ConfigurationProperties
Bean,你可以用与其他Bean相同的方式注入它们,如下例所示。
@Service
public class MyService {
private final MyProperties properties;
public MyService(MyProperties properties) {
this.properties = properties;
}
public void openConnection() {
Server server = new Server(this.properties.getRemoteAddress());
server.start();
// ...
}
// ...
}
@Service
class MyService(val properties: MyProperties) {
fun openConnection() {
val server = Server(properties.remoteAddress)
server.start()
// ...
}
// ...
}
使用 @ConfigurationProperties 还可以让你生成元数据文件,这些文件可以被IDE用来配置属性的“自动补全”功能。
详情见附录。
|
2.8.5. 第三方配置
除了使用 @ConfigurationProperties
来注解一个类之外,你还可以在公共的 @Bean
方法上使用它。
当你想把属性绑定到你控制之外的第三方组件时,这样做特别有用。
要从 Environment
属性中配置一个Bean,请在其Bean注册中添加 @ConfigurationProperties
,如下例所示。
@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {
@Bean
@ConfigurationProperties(prefix = "another")
public AnotherComponent anotherComponent() {
return new AnotherComponent();
}
}
@Configuration(proxyBeanMethods = false)
class ThirdPartyConfiguration {
@Bean
@ConfigurationProperties(prefix = "another")
fun anotherComponent(): AnotherComponent = AnotherComponent()
}
任何用 another
前缀定义的JavaBean属性都会被映射到 AnotherComponent
Bean上,其方式类似于前面的 SomeProperties
例子。
2.8.6. 宽松的绑定
Spring Boot在将 Environment
属性绑定到 @ConfigurationProperties
bean时使用了一些宽松的规则,因此 Environment
属性名称和bean属性名称之间不需要完全匹配。
这很有用,常见的例子包括破折号分隔的属性名称(例如, context-path
绑定到 contextPath
),和大写的属性名称(例如,PORT
绑定到 port
)。
演示一个例子,考虑以下 @ConfigurationProperties
类。
@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {
var firstName: String? = null
}
对以上的代码来说,以下的属性名称都可以使用。
Property | Note |
---|---|
|
Kebab 风格(短横线隔开),建议在 |
|
标准的驼峰语法。 |
|
下划线,这是一种用于 |
|
大写格式,在使用系统环境变量时建议使用大写格式。 |
注解的 prefix 值 必须 是kebab风格(小写并以 - 分隔,如 my.main-project.person )。
|
属性源 | 简单的 | 列表 |
---|---|---|
Properties 文件 |
驼峰, kebab , 下划线 |
使用 |
YAML 文件 |
驼峰, kebab , 下划线 |
标准YAML列表语法或逗号分隔的值 |
环境变量 |
大写,下划线为分隔符(见 从环境变量绑定). |
Numeric values surrounded by underscores (see 从环境变量绑定) |
系统属性(System properties) |
驼峰, kebab , 下划线 |
使用 |
我们建议,在可能的情况下,属性应以小写的kebab格式存储,例如 my.person.first-name=Rod 。
|
绑定Map
当绑定到 Map
属性时,你可能需要使用一个特殊的括号符号,以便保留原始的 key
值。
如果key没有被 [ ]
包裹,任何非字母数字、-
或 .
的字符将被删除。
例如,考虑将以下属性绑定到一个 Map<String,String>
。
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
my:
map:
"[/key1]": "value1"
"[/key2]": "value2"
"/key3": "value3"
对于YAML文件,括号需要用引号包裹,以使key被正确解析。 |
上面的属性将绑定到一个 Map
,/key1
,/key2
和 key3
作为map的key。
斜线已经从 key3
中删除,因为它没有被方括号包裹。
当绑定到标量值时,带有 .
的键不需要用 []
包裹。
标量值包括枚举和所有 java.lang
包中的类型,除了 Object
。
将 a.b=c
绑定到 Map<String, String>
将保留键中的 .
,并返回一个带有 {"a.b"="c"}
Entry的Map。
对于任何其他类型,如果你的 key
包含 .
,你需要使用括号符号。
例如,将 a.b=c
绑定到 Map<String, Object>
将返回一个带有 {"a"={"b"="c"}
entry的Map,而 [a.b]=c
将返回一个带有 {"a.b"="c"}
entry 的Map。
从环境变量绑定
例如,Linux shell变量只能包含字母(a
到 z
或 A
到 Z
)、数字( 0
到 9
)或下划线字符( _
)。
按照惯例,Unix shell变量的名称也将采用大写字母。
Spring Boot宽松的绑定规则被设计为尽可能地与这些命名限制兼容。
要将规范形式的属性名称转换为环境变量名称,你可以遵循这些规则。
-
用下划线(
_
)替换点(.
)。 -
删除任何破折号(
-
)。 -
转换为大写字母。
例如,配置属性 spring.main.log-startup-info
将是一个名为 SPRING_MAIN_LOGSTARTUPINFO
的环境变量。
环境变量也可以在绑定到对象列表(List)时使用。
要绑定到一个 List
,在变量名称中,元素编号(索引)应该用下划线包裹。
例如,配置属性 my.service[0].other
将使用一个名为 MY_SERVICE_0_OTHER
的环境变量。
2.8.7. 合并复杂的类型
当List被配置在多个地方时,覆盖的作用是替换整个list。
例如,假设一个 MyPojo
对象的 name
和 description
属性默认为 null
。
下面的例子从 MyProperties
中暴露了一个 MyPojo
对象的列表。
@ConfigurationProperties("my")
public class MyProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
@ConfigurationProperties("my")
class MyProperties {
val list: List<MyPojo> = ArrayList()
}
考虑以下配置。
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
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
仍然只包含一个条目(name 为 my another name
,description为 null
)。
这种配置不会在列表中添加第二个 MyPojo
实例,也不会合并项目。
当一个 List
在多个配置文件中被指定时,将使用具有最高优先级的那个(并且只有那个)。
考虑下面的例子。
my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
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
包含 一个 MyPojo
条目(name 是 my another name
,description是 null
)。
对于YAML,逗号分隔的列表和YAML列表都可以用来完全覆盖列表的内容。
对于 Map
属性,你可以用从多个来源获取的属性值进行绑定。
然而,对于多个来源中的同一属性,使用具有最高优先级的那个。
下面的例子从 MyProperties
暴露了一个 Map<String, MyPojo>
。
@ConfigurationProperties("my")
public class MyProperties {
private final Map<String, MyPojo> map = new LinkedHashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
@ConfigurationProperties("my")
class MyProperties {
val map: Map<String, MyPojo> = LinkedHashMap()
}
考虑以下配置。
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
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
包含一个key为 key1
的条目(name为 my name 1
,description为 my description 1
)。
然而,如果 dev
配置文件被激活,map
包含两个条目,key为 key1
(name为 dev name 1
,description为 my description 1
)和 key2
(name为 dev name 2
,description为 dev description 2
)。
前面的合并规则适用于所有属性源的属性,而不仅仅是文件。 |
2.8.8. 属性(Properties)转换
当Spring Boot与 @ConfigurationProperties
Bean绑定时,它试图将外部application properties强制改为正确的类型。
如果你需要自定义类型转换,你可以提供一个 ConversionService
bean(Bean的名称为 conversionService
)或自定义属性编辑器(通过 CustomEditorConfigurer
bean)或自定义 Converters
Bean(使用 @ConfigurationPropertiesBinding
注解)。
由于这个Bean是在应用程序生命周期的早期被请求的,请确保限制你的 ConversionService 所使用的依赖关系。
通常情况下,你所需要的任何依赖关系在创建时可能没有完全初始化。
如果你的自定义 ConversionService 不需要配置keys coercion,你可能想重命名它,并且只依赖用 @ConfigurationPropertiesBinding 限定的自定义转换器。
|
转换为 Duration
Spring Boot对表达持续时间有专门的支持。
如果你公开了一个 java.time.Duration
属性,application properties中的以下格式就可用。
-
普通的
long
(使用毫秒作为默认单位,除非指定了@DurationUnit
)。 -
标准的ISO-8601格式 由
java.time.Duration
使用。 -
一个更易读的格式,其中值和单位是耦合的(
10s
表示10秒)。
请考虑以下例子。
@ConfigurationProperties("my")
public class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
}
@ConfigurationProperties("my")
class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
var sessionTimeout = Duration.ofSeconds(30)
var readTimeout = Duration.ofMillis(1000)
}
要指定一个30秒的会话超时, 30
、 PT30S
和 30s
都是等价的。
读取超时为500ms,可以用以下任何一种形式指定。 500
, PT0.5S
和 500ms
.
你也可以使用如下支持的时间单位。
-
ns
纳秒 -
us
微秒 -
ms
毫秒 -
s
秒 -
m
分 -
h
小时 -
d
天
默认单位是毫秒,可以使用 @DurationUnit
来重写,如上面的例子所示。
如果你喜欢使用构造函数绑定,同样的属性可以被暴露出来,如下面的例子所示。
@ConfigurationProperties("my")
public class MyProperties {
public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
@DefaultValue("1000ms") Duration readTimeout) {
this.sessionTimeout = sessionTimeout;
this.readTimeout = readTimeout;
}
}
@ConfigurationProperties("my")
class MyProperties(@param:DurationUnit(ChronoUnit.SECONDS) @param:DefaultValue("30s") val sessionTimeout: Duration,
@param:DefaultValue("1000ms") val readTimeout: Duration)
如果你要升级一个 Long 的属性,如果它不是毫秒,请确保定义单位(使用 @DurationUnit )。
这样做提供了一个透明的升级路径,同时支持更丰富的格式
|
转换为期间(Period)
除了duration,Spring Boot还可以使用 java.time.Period
类型。
以下格式可以在application properties中使用。
-
一个常规的
int
表示法(使用天作为默认单位,除非指定了@PeriodUnit
)。 -
标准的ISO-8601格式 由
java.time.Period
使用。 -
一个更简单的格式,其中值和单位对是耦合的(
1y3d
表示1年3天)。
支持下列简单的单位格式。
-
y
年 -
m
月 -
w
周 -
d
日
java.time.Period 类型实际上从未存储过周数,它是一个快捷方式,意味着 “7天”。
|
转换为数据大小(Data Sizes)
Spring Framework有一个 DataSize
值类型,以字节为单位表达大小。
如果你公开了一个 DataSize
属性,application properties中的以下格式就可用。
-
一个常规的
long
表示(使用字节作为默认单位,除非指定了@DataSizeUnit
)。 -
一个更易读的格式,其中值和单位是耦合的(
10MB
意味着10兆字节)。
考虑以下例子。
@ConfigurationProperties("my")
public class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
}
@ConfigurationProperties("my")
class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
var bufferSize = DataSize.ofMegabytes(2)
var sizeThreshold = DataSize.ofBytes(512)
}
要指定一个10兆字节(Mb)的缓冲区大小, 10
和 10MB
是等价的。
256字节的大小阈值可以指定为 256
或 256B
。
你也可以使用如下这些支持的单位。
-
B
字节 -
KB
KB -
MB
MB -
GB
GB -
TB
TB
默认单位是字节,可以使用 @DataSizeUnit
来重写,如上面的例子所示。
如果你喜欢使用构造函数绑定,同样的属性可以被暴露出来,如下面的例子所示。
@ConfigurationProperties("my")
public class MyProperties {
public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
@DefaultValue("512B") DataSize sizeThreshold) {
this.bufferSize = bufferSize;
this.sizeThreshold = sizeThreshold;
}
}
@ConfigurationProperties("my")
class MyProperties(@param:DataSizeUnit(DataUnit.MEGABYTES) @param:DefaultValue("2MB") val bufferSize: DataSize,
@param:DefaultValue("512B") val sizeThreshold: DataSize)
如果你正在升级一个 Long 属性,确保定义单位(使用 @DataSizeUnit ),如果它不是字节。
这样做提供了一个透明的升级路径,同时支持更丰富的格式。
|
2.8.9. @ConfigurationProperties 校验
只要使用Spring的 @Validated
注解,Spring Boot就会尝试验证 @ConfigurationProperties
类。
你可以直接在你的配置类上使用JSR-303的 jakarta.validation
约束注解。
要做到这一点,请确保你的classpath上有一个兼容的JSR-303实现,然后将约束注解添加到你的字段中,如下面的例子所示。
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
}
@ConfigurationProperties("my.service")
@Validated
class MyProperties {
var remoteAddress: @NotNull InetAddress? = null
}
你也可以通过在 configuration properties 的 @Bean 方法上注解 @Validated 来触发验证。
|
为了确保总是为嵌套的属性触发验证,即使没有找到属性,相关的字段必须用 @Valid
来注释。
下面的例子建立在前面的 MyProperties
的基础上。
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
public static class Security {
@NotEmpty
private String username;
}
}
@ConfigurationProperties("my.service")
@Validated
class MyProperties {
var remoteAddress: @NotNull InetAddress? = null
@Valid
val security = Security()
class Security {
@NotEmpty
var username: String? = null
}
}
你也可以通过创建一个名为 configurationPropertiesValidator
的bean定义来添加一个自定义的Spring Validator
。
@Bean
方法应该被声明为 static
。
配置属性验证器是在应用程序生命周期的早期创建的,将 @Bean
方法声明为静态,可以让Bean的创建不需要实例化 @Configuration
类。
这样做可以避免过早实例化可能引起的任何问题。
spring-boot-actuator 模块包括一个暴露所有 @ConfigurationProperties Bean 的端点。
你可以通过浏览器访问 /actuator/configprops 或使用相应的JMX端点。
详情见"生产就绪"部分。
|
2.8.10. @ConfigurationProperties vs. @Value
@Value
注解是一个核心的容器功能,它不提供与类型安全的配置属性相同的功能。
下表总结了 @ConfigurationProperties
和 @Value
所支持的功能。
功能 | @ConfigurationProperties |
@Value |
---|---|---|
Yes |
有限制 (见 下文注释) |
|
Yes |
No |
|
|
No |
Yes |
如果你确实想使用 例如, |
如果你为你自己的组件定义了一组配置键,我们建议你将它们分组在一个用 @ConfigurationProperties
注解的POJO中。
这样做将为你提供结构化的、类型安全的对象,你可以将其注入到你自己的bean中。
来自应用application property 文件的 SpEL
表达式在解析这些文件和填充environment时不会被处理。
然而,可以在 @Value
中写一个 SpEL
表达式。
如果来自应用程序属性文件的属性值是一个 SpEL
表达式,它将在被 @Value
消费时被解析。
3. Profiles
Spring Profiles提供了一种方法来隔离你的应用程序配置的一部分,并使其仅在某些环境中可用。
任何 @Component
、@Configuration
或 @ConfigurationProperties
都可以用 @Profile
标记,以限制它的加载时机,如下面的例子所示。
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
@Configuration(proxyBeanMethods = false)
@Profile("production")
class ProductionConfiguration {
// ...
}
如果 @ConfigurationProperties Bean是通过 @EnableConfigurationProperties 注册的,而不是自动扫描,则需要在具有 @EnableConfigurationProperties 注解的 @Configuration 类上指定 @Profile 注解。
在 @ConfigurationProperties 被扫描的情况下,@Profile 可以在 @ConfigurationProperties 类本身指定。
|
你可以使用 spring.profiles.active
Environment
属性来指定哪些配置文件是活动的(active)。
你可以通过本章前面描述的任何方式来指定该属性。
例如,你可以在你的 application.properties
中包含它,如下面的例子所示。
spring.profiles.active=dev,hsqldb
spring:
profiles:
active: "dev,hsqldb"
你也可以通过使用以下开关在命令行中指定它:--spring.profiles.active=dev,hsqldb
。
如果没有激活配置文件,则启用一个默认的配置文件。
默认配置文件的名称是 default
,可以使用 spring.profiles.default
Environment
属性对其进行调整,如下面的例子所示。
spring.profiles.default=none
spring:
profiles:
default: "none"
spring.profiles.active
和 spring.profiles.default
只能在非配置文件的文件中使用。
这意味着它们不能包含在配置文件的特定文件或由 spring.config.activated.on-profile
激活的文件中。
例如,第二个文件配置是无效的。
# this document is valid
spring.profiles.active=prod
#---
# this document is invalid
spring.config.activate.on-profile=prod
spring.profiles.active=metrics
# this document is valid
spring:
profiles:
active: "prod"
---
# this document is invalid
spring:
config:
activate:
on-profile: "prod"
profiles:
active: "metrics"
3.1. 添加活动的配置文件
spring.profiles.active
属性遵循与其他属性相同的排序规则。最高的 PropertySource
获胜。
这意味着你可以在 application.properties
中指定active profile,然后通过命令行开关(参数) 替换 它们。
有时,有一些属性可以 添加 到活动的配置文件中,而不是替换它们,这很有用。
spring.profiles.include
属性可用于在由 spring.profiles.active
属性激活的配置文件之上添加活动配置文件。
SpringApplication
入口点也有一个Java API用于设置额外的配置文件。
参见 SpringApplication 中的 setAdditionalProfiles()
方法。
例如,当一个具有以下属性的应用程序被运行时,即使它使用 --spring.profiles.active
命令行参数指定了激活的配置文件,common和local profile 也会被激活。
spring.profiles.include[0]=common
spring.profiles.include[1]=local
spring:
profiles:
include:
- "common"
- "local"
与 spring.profiles.active 类似,spring.profiles.include 只能用于非配置文件的文件。
这意味着它不能包含在由 spring.config.activated.on-profile 激活的文件中。
|
如果一个给定的配置文件是活动的,配置文件组,在下一节中描述,也可以用来添加活动的配置文件。
3.2. 配置文件组(Profile Groups)
偶尔,你在你的应用程序中定义和使用的配置文件过于精细,使用起来就会很麻烦。
例如,你可能有 proddb
和 prodmq
配置文件,用来独立启用数据库和消息传递功能。
为了帮助解决这个问题,Spring Boot允许你定义配置文件组。 配置文件组允许你为相关的配置文件组定义一个逻辑名称。
例如,我们可以创建一个 production
组,由 proddb
和 prodmq
配置文件组成。
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
spring:
profiles:
group:
production:
- "proddb"
- "prodmq"
现在可以使用 --spring.profiles.active=production
来启动我们的应用程序,一次性激活 production
、proddb
和 prodmq
配置文件。
3.3. 以编程方式设置配置文件(Profile)
你可以在应用运行前通过调用 SpringApplication.setAdditionalProfiles(…)
以编程方式设置激活的配置文件。
也可以通过使用Spring的 ConfigurableEnvironment
接口来激活配置文件。
3.4. 特定的配置文件
application.properties
(或 application.yaml
)和通过 @ConfigurationProperties
引用的文件的特定配置文件变体都被认为是文件并被加载。
参见"特定文件(Profile Specific Files)"以了解详情。
4. 日志
Spring Boot在所有内部日志中使用 Commons Logging ,但对底层日志的实现保持开放。 为 Java Util Logging 、 Log4j2 、 Logback 提供了默认配置。 在每一种情况下,记录器(logger)都被预设为使用控制台输出,也可以选择输出到文件。
默认情况下,如果你使用 “Starter”,则默认使用Logback。 适当的Logback路由也包括在内,以确保使用Java Util Logging、Commons Logging、Log4J或SLF4J的依赖库都能正确工作。
有很多适用于Java的日志框架。 如果上面的列表看起来很混乱,请不要担心。 一般来说,你不需要改变你的日志依赖,Spring Boot的默认值就很好用。 |
当你把你的应用程序部署到一个servlet容器或应用服务器时,用Java Util Logging API执行的日志不会被传送到你的应用程序的日志中。 这可以防止由容器或其他已经部署到它的应用程序执行的日志出现在你的应用程序的日志中。 |
4.1. 日志格式
Spring Boot的默认的日志输出格式类似于下面的例子。
2023-09-10T13:33:51.625+08:00 INFO 22180 --- [myapp] [ main] o.s.b.d.f.s.MyApplication : Starting MyApplication using Java 17 with PID 22180 (/opt/apps/myapp.jar started by myuser in /opt/apps/) 2023-09-10T13:33:51.640+08:00 INFO 22180 --- [myapp] [ main] o.s.b.d.f.s.MyApplication : No active profile set, falling back to 1 default profile: "default" 2023-09-10T13:33:53.432+08:00 INFO 22180 --- [myapp] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2023-09-10T13:33:53.444+08:00 INFO 22180 --- [myapp] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-09-10T13:33:53.444+08:00 INFO 22180 --- [myapp] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.10] 2023-09-10T13:33:53.571+08:00 INFO 22180 --- [myapp] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2023-09-10T13:33:53.574+08:00 INFO 22180 --- [myapp] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1803 ms 2023-09-10T13:33:54.043+08:00 INFO 22180 --- [myapp] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2023-09-10T13:33:54.052+08:00 INFO 22180 --- [myapp] [ main] o.s.b.d.f.s.MyApplication : Started MyApplication in 3.689 seconds (process running for 4.141)
输出的项目如下。
-
Date和时Time:精确到毫秒,易于排序。
-
日志级别:
ERROR
,WARN
,INFO
,DEBUG
, 或TRACE
. -
进程ID。
-
一个
---
分隔符,以区分实际日志信息的开始。 -
Application name: 包含在方括号中(仅当
spring.application.name
被设置时才默认记录)。 -
线程名称:包含在方括号中(对于控制台输出可能会被截断)。
-
相关ID:如果启用了trace功能(上面的例子中没有显示)。
-
记录器名称:这通常是源类的名称(通常是缩写)。
-
日志消息。
Logback没有 FATAL 级别。
它被映射到 ERROR 。
|
如果你有一个 spring.application.name 属性,但不希望它被记录,你可以将 logging.include-application-name 设置为 false 。
|
4.2. 控制台输出
默认情况下,日志会输出 ERROR
、WARN
和 INFO
级别的消息到控制台。
你也可以通过用 --debug
标志启动你的应用程序,来启用 debug
模式。
$ java -jar myapp.jar --debug
你也可以在你的 application.properties 中指定 debug=true 。
|
当debug模式被启用时,一些核心记录器(嵌入式容器、Hibernate和Spring Boot)被配置为输出更多信息。
启用debug模式并不意味着将你的应用程序配置为以 DEBUG
级别记录所有信息。
另外,你可以通过在启动应用程序时使用 --trace
标志(或在 application.properties
中使用 trace=true
)来启用 “trace” 模式。
这样做可以对一些核心记录器(嵌入式容器、Hibernate schema生成和整个Spring组合)进行跟踪记录。
4.2.1. 彩色编码的输出
如果你的终端支持ANSI,就会使用彩色输出来帮助阅读。
你可以将 spring.output.ansi.enabled
设置为 支持的值,以覆盖自动检测。
颜色编码是通过使用 %clr
转换关键字来配置的。
在其最简单的形式中,转换器根据日志级别对输出进行着色,如下面的例子中所示。
%clr(%5p)
下表描述了日志级别与颜色的映射关系。
日志级别 | 颜色 |
---|---|
|
红 |
|
红 |
|
黄 |
|
绿 |
|
绿 |
|
绿 |
另外,你也可以通过为转换提供一个选项来指定应该使用的颜色或样式。 例如,要使文本为黄色,请使用以下设置。
%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow}
支持以下颜色和样式。
-
blue
-
cyan
-
faint
-
green
-
magenta
-
red
-
yellow
4.3. 输出到文件
默认情况下,Spring Boot只向控制台记录日志,不写日志文件。
如果你想在控制台输出之外写日志文件,你需要设置 logging.file.name
或 logging.file.path
属性(例如,在你的 application.properties
中)。
下表显示了 logging.*
属性如何被一起使用。
logging.file.name |
logging.file.path |
Example | Description |
---|---|---|---|
(none) |
(none) |
只在控制台进行记录。 |
|
指定文件 |
(none) |
|
写入指定的日志文件。 名称可以是一个确切的位置,也可以是与当前目录的相对位置。 |
(none) |
指定目录 |
|
将 |
日志文件在达到10MB时就会轮换,与控制台输出一样,默认情况下会记录 ERROR
、WARN
级和 INFO
级别的信息。
日志属性独立于实际的日志基础设施。
因此,特定的配置属性(如Logback的 logback.configurationFile )不由spring Boot管理。
|
4.4. 文件轮换(滚动日志)
如果你使用Logback,可以使用你的 application.properties
或 application.yaml
文件来微调日志轮换设置。
对于所有其他的日志系统,你将需要自己直接配置轮换设置(例如,如果你使用Log4J2,那么你可以添加一个 log4j2.xml
或 log4j2-spring.xml
文件)。
支持以下轮换策略属性。
属性 | 说明 |
---|---|
|
用于创建日志归档的文件名模式。 |
|
应用程序启动时,是否行日志归档清理。 |
|
日志文件归档前的最大尺寸(文件最大体积,达到这个体积就会归档)。 |
|
日志档案在被删除前的最大尺寸(归档文件最大占用大小,超过这个大小后会被删除)。 |
|
要保留的归档日志文件的最大数量(默认为7)。 |
4.5. 日志级别
所有支持的日志系统都可以通过使用 logging.level.<logger-name>=<level>
在Spring的 Environment
(例如,在 application.properties
)中设置日志级别,其中 level
是 TRACE
, DEBUG
, INFO
, WARN
, ERROR
, FATAL
, 或 OFF
之一。
root
记录器(logger)的级别可以通过 logging.level.root
来配置。
下面的例子显示了 application.properties
中潜在的日志设置。
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
logging:
level:
root: "warn"
org.springframework.web: "debug"
org.hibernate: "error"
也可以使用环境变量来设置日志级别。
例如,LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG
将设置 org.springframework.web
为 DEBUG
。
上述方法只适用于包级日志。
由于宽松绑定总是将环境变量转换为小写字母,所以不可能用这种方式为单个类配置日志。
如果你需要为一个类配置日志,你可以使用SPRING_APPLICATION_JSON 变量。
|
4.6. 日志组(Log Groups)
能够将相关的日志记录器分组,以便同时对它们进行配置,这通常很有用。 例如,你可能经常改变 所有 与Tomcat相关的记录器的记录级别,但你不容易记住最高级别的包。
为了帮助解决这个问题,Spring Boot允许你在Spring Environment
中定义日志组。
例如,你可以通过在 application.properties
中加入 “tomcat” group 来定义它。
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
logging:
group:
tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"
一旦定义好后,就可以用一行代码来改变组中所有logger的级别。
logging.level.tomcat=trace
logging:
level:
tomcat: "trace"
Spring Boot包括以下预定义的日志组,可以开箱即用。
组名 | 组中的logger |
---|---|
web |
|
sql |
|
4.7. 使用日志 Shutdown Hook
为了在你的应用程序终止时释放日志资源,我们提供了一个Shutdown Hook,它将在JVM退出时触发日志系统清理。
除非你的应用程序是以war文件的形式部署的,否则这个Shutdown Hook会自动注册。
如果你的应用程序有复杂的上下文层次结构,Shutdown Hook可能无法满足你的需求。
如果不能,请禁用关机钩子,并研究底层日志系统直接提供的选项。
例如,Logback提供了 context selectors,允许每个记录器在它自己的上下文中被创建。
你可以使用 logging.register-shutdown-hook
属性来禁用Shutdown Hook。
将其设置为 false
将禁用注册。
你可以在你的 application.properties
或 application.yaml
文件中设置该属性。
logging.register-shutdown-hook=false
logging:
register-shutdown-hook: false
4.8. 自定义日志配置
各种日志系统可以通过在classpath上包含适当的库来激活,并且可以通过在classpath的根目录下或在 Spring Environment
属性指定的位置提供一个合适的配置文件来进一步定制: logging.config
。
你可以通过使用 org.springframework.boot.logging.LoggingSystem
系统属性,强制Spring Boot使用特定的日志系统。
该值应该是 LoggingSystem
实现的全类名。
你也可以通过使用 none
的值来完全禁用Spring Boot的日志配置。
S由于日志是在创建 ApplicationContext 之前初始化的,所以不可能从Spring @Configuration 文件中的 @PropertySources 控制日志。
改变日志系统或完全停用它的唯一方法是通过System properties。
|
根据你的日志系统,会加载以下文件。
日志系统 | 配置文件 |
---|---|
Logback |
|
Log4j2 |
|
JDK (Java Util Logging) |
|
在可能的情况下,我们建议你使用 -spring 变体来进行日志配置(例如, logback-spring.xml 而不是 logback.xml )。
如果你使用标准配置位置,Spring不能完全控制日志初始化。
|
当从 "可执行的jar "中运行时,Java Util Logging有一些已知的类加载问题,会导致问题。 如果可能的话,我们建议你在从 "可执行的jar" 中运行时避免使用它。 |
为了帮助定制,其他一些属性从Spring的 Environment
转移到System properties,如下表所示。
Spring Environment | System Property | 备注 |
---|---|---|
|
|
记录异常时使用的转换词。 |
|
|
如果定义了,它将用于默认的日志配置中。 |
|
|
如果定义了,它将用于默认的日志配置中。 |
|
|
在控制台(stdout)使用的日志输出模式。 |
|
|
date 格式化. |
|
|
控制台输出日志的字符编码。 |
|
|
用于控制台日志记录的日志级别。 |
|
|
要在文件中使用的日志模式(如果 |
|
|
文件日志的字符编码(如果 |
|
|
用于文件日志记录的日志级别。 |
|
|
渲染日志级别时使用的格式(默认为 |
|
|
当前的进程ID |
如果你使用Logback,以下属性也会被转移。
Spring Environment | System Property | 备注 |
---|---|---|
|
|
滚动日志文件名的模式(默认为 |
|
|
是否在启动时清理归档日志文件。 |
|
|
最大日志文件大小。 |
|
|
要保留的日志备份的总大小。 |
|
|
要保留的最大归档日志文件数量。 |
所有支持的日志系统在解析其配置文件时都可以从 System properties 中获取属性。
例子见 spring-boot.jar
中的默认配置。
如果你想在日志属性中使用占位符,你应该使用Spring Boot的语法而不是底层框架的语法。
值得注意的是,如果你使用Logback,你应该使用 |
你可以通过只覆盖 2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0] demo.Controller Handling authenticated request |
4.9. Logback 扩展
Spring Boot包括一些对Logback的扩展,可以帮助进行高级配置。
你可以在你的 logback-spring.xml
配置文件中使用这些扩展。
因为标准的 logback.xml 配置文件被过早加载,你不能在其中使用扩展。
你需要使用 logback-spring.xml 或者定义一个 logging.config 属性。
|
扩展程序不能与 Logback的配置扫描 一起使用。 如果你试图这样做,对配置文件进行修改会导致类似于以下的错误被记录下来。 |
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]] ERROR in ch.qos.logback.core.joran.spi.Interpreter@4: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)
。
查看 Spring 框架参考指南 以了解更多细节。
下面的列表显示了三个样本配置文件。
<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. (环境属性)Environment Properties
<springProperty>
标签可以访问 Spring Environment
中的属性,以便在Logback中使用。
如果你想在Logback配置中访问 application.properties
文件中的值,这样做会很有用。
该标签的工作方式与Logback的标准 <property>
标签类似。
然而,你不是直接指定一个 value
,而是指定属性的 source
(来自 Environment
)。
如果你需要在 local
范围以外的地方存储该属性,你可以使用 scope
属性。
如果你需要一个后备值(默认值)(万一该属性没有在 Environment
中设置),你可以使用 defaultValue
属性。
下面的例子显示了如何公开属性以便在Logback中使用。
<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 必须以kebab风格指定(如 my.property-name )。
然而,属性可以通过使用宽松的规则添加到 Environment 中。
|
4.10. Log4j2 扩展
Spring Boot包括一些对Log4j2的扩展,可以帮助进行高级配置。你可以在任何 log4j2-spring.xml
配置文件中使用这些扩展。
因为标准的 log4j2.xml 配置文件被过早加载,你不能在其中使用扩展。你需要使用 log4j2-spring.xml 或者定义一个 属性。
|
这些扩展取代了Log4J提供的 Spring Boot支持。
你应该确保在你的构建中不包括 org.apache.logging.log4j:log4j-spring-boot 模块。
|
4.10.1. 特定配置文件配置
<SpringProfile>
标签让你可以根据活动的Spring配置文件选择性地包括或排除配置的部分。配置文件部分被支持在 <Configuration>
元素的任何地方。使用 name
属性来指定哪个配置文件接受配置。 <SpringProfile>
标签可以包含一个配置文件名称(例如 staging
)或一个配置文件表达式。
配置文件表达式允许表达更复杂的配置文件逻辑,例如 production & (eu-central | eu-west)
。查看 Spring框架参考指南 以了解更多细节。
下面的列表显示了三个样本配置文件。
<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.10.2. 环境(Environment)属性查找
如果你想在Log4j2配置中引用Spring Environment
中的属性,你可以使用 spring:
前缀 查找。如果你想在Log4j2配置中访问 application.properties
文件中的值,这样做会很有用。
下面的例子显示了如何设置一个名为 applicationName
的Log4j2属性,它从Spring Environment
中读取 spring.application.name
。
<Properties>
<Property name="applicationName">${spring:spring.application.name}</Property>
</Properties>
查询key应以kebabf风格指定(如 my.property-name )。
|
4.10.3. Log4j2 系统属性(System Properties)
Log4j2支持一些 System Properties,可以用来配置各种项目。例如,log4j2.skipJansi
系统属性可以用来配置 ConsoleAppender
是否会在Windows上尝试使用 Jansi 输出流。
Log4j2 初始化后加载的所有系统属性都可以从Spring Environment
中获得。例如,你可以在 application.properties
文件中添加 log4j2.skipJansi=false
,让 ConsoleAppender
在Windows上使用Jansi。
只有当系统属性(system properties)和操作系统环境变量不包含正在加载的值时,才会考虑Spring Environment 。
|
在早期Log4j2初始化期间加载的系统属性不能引用Spring Environment 。例如,Log4j2用于允许选择默认Log4j2实现的属性是在 Spring Environment 可用之前使用的。
|
5. 国际化
Spring Boot支持本地化的消息,这样你的应用程序就可以满足不同语言偏好的用户。
默认情况下,Spring Boot会在classpath的根部寻找 messages
资源包(resource bundle)的存在。
自动配置只有在资源包中存在默认的properties文件(默认为 messages.properties )时才生效。如果资源包中只有特定语言的properties,那你需要添加默认的。
没有没有找到任何base name和语言相匹配的文件,MessageSource 不会自动配置。
|
资源包的basename以及其他几个属性可以使用 spring.messages
命名空间进行配置,如下面的例子所示。
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
spring:
messages:
basename: "messages,config.i18n.messages"
fallback-to-system-locale: false
spring.messages.basename 支持逗号分隔的位置列表,可以是包的限定词或从classpath根路径解析的资源。
|
参见 MessageSourceProperties
以了解更多支持的选项。
6. JSON
Spring Boot提供了与三个JSON库的集成。
-
Gson
-
Jackson
-
JSON-B
Jackson是首选和默认的库。
6.1. Jackson
Jackson是自动配置,它是 spring-boot-starter-json
的一部分。
当Jackson在classpath上时,会自动配置 ObjectMapper
bean。
你也可以通过一些配置属性来定制 ObjectMapper
。
6.1.1. 自定义 Serializers 和 Deserializers
如果你使用Jackson来序列化和反序列化JSON数据,你可能想编写自己的 JsonSerializer
和 JsonDeserializer
类。自定义序列化器通常是 通过模块向Jackson注册的,但Spring Boot提供了一个替代性的 @JsonComponent
注解,使直接注册Spring Bean变得更容易。
你可以直接在 JsonSerializer
、 JsonDeserializer
或 KeyDeserializer
的实现上使用 @JsonComponent
注解。
你也可以在包含 serializers/deserializers 作为内部类的类上使用它,如下面的例子所示。
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> {
@Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
jgen.writeEndObject();
}
}
public static class Deserializer extends JsonDeserializer<MyObject> {
@Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
String name = tree.get("name").textValue();
int age = tree.get("age").intValue();
return new MyObject(name, age);
}
}
}
@JsonComponent
class MyJsonComponent {
class Serializer : JsonSerializer<MyObject>() {
@Throws(IOException::class)
override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializerProvider) {
jgen.writeStartObject()
jgen.writeStringField("name", value.name)
jgen.writeNumberField("age", value.age)
jgen.writeEndObject()
}
}
class Deserializer : JsonDeserializer<MyObject>() {
@Throws(IOException::class, JsonProcessingException::class)
override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): MyObject {
val codec = jsonParser.codec
val tree = codec.readTree<JsonNode>(jsonParser)
val name = tree["name"].textValue()
val age = tree["age"].intValue()
return MyObject(name, age)
}
}
}
ApplicationContext
中的所有 @JsonComponent
Bean都会自动向Jackson注册。
因为 @JsonComponent
是用 @Component
元注解的,所以通常的组件扫描规则适用。
Spring Boot还提供了 JsonObjectSerializer
和 JsonObjectDeserializer
基类,在序列化对象时提供了标准Jackson版本的有用替代品。详情见Javadoc中的 JsonObjectSerializer
和 JsonObjectDeserializer
。
上面的例子可以改写为使用 JsonObjectSerializer
/JsonObjectDeserializer
,如下所示。
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> {
@Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
@Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age);
}
}
}
`object`
@JsonComponent
class MyJsonComponent {
class Serializer : JsonObjectSerializer<MyObject>() {
@Throws(IOException::class)
override fun serializeObject(value: MyObject, jgen: JsonGenerator, provider: SerializerProvider) {
jgen.writeStringField("name", value.name)
jgen.writeNumberField("age", value.age)
}
}
class Deserializer : JsonObjectDeserializer<MyObject>() {
@Throws(IOException::class)
override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext,
codec: ObjectCodec, tree: JsonNode): MyObject {
val name = nullSafeValue(tree["name"], String::class.java)
val age = nullSafeValue(tree["age"], Int::class.java)
return MyObject(name, age)
}
}
}
7. 任务执行和调度(Task Execution and Scheduling)
如果context没有 Executor
bean,Spring Boot会自动配置一个 ThreadPoolTaskExecutor
,它具有合理的默认值,可以自动与异步任务执行( @EnableAsync
)和Spring MVC的异步请求处理相关联。
如果你在context定义了一个自定义的 自动配置的 |
线程池使用8个核心线程,可以根据负载伸缩。
这些默认设置可以使用 spring.task.execution
命名空间进行微调,如以下例子所示。
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
spring:
task:
execution:
pool:
max-size: 16
queue-capacity: 100
keep-alive: "10s"
这将线程池改为使用有界队列,因此当队列满了(100个任务),线程池增加到最大16个线程。 线程池的收缩更加积极,因为当线程闲置10秒(而不是默认的60秒)时就会被回收。
如果需要与计划(定时)任务执行(例如使用 @EnableScheduling
)相关联,也可以自动配置一个 ThreadPoolTaskScheduler
。
线程池默认使用一个线程,它的设置可以使用 spring.task.scheduling
命名空间进行微调,如下面的例子所示。
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
spring:
task:
scheduling:
thread-name-prefix: "scheduling-"
pool:
size: 2
如果需要创建一个自定义的执行器或调度器,上下文中的 TaskExecutorBuilder
bean和 TaskSchedulerBuilder
bean都是可用的。
8. 测试
Spring Boot提供了许多实用程序和注解,以便在测试你的应用程序时提供帮助。
测试支持由两个模块提供: spring-boot-test
包含核心项目, spring-boot-test-autoconfigure
支持测试的自动配置。
大多数开发者使用 spring-boot-starter-test
“Starter”,它同时导入Spring Boot测试模块以及JUnit Jupiter、AssertJ、Hamcrest和其他一些有用的库。
如果你有使用JUnit 4的测试,可以使用JUnit 5的vintage引擎来运行它们。
要使用vintage引擎,请添加对
|
hamcrest-core
被排除在外,而支持 org.hamcrest:hamcrest
,这是 spring-boot-starter-test
的一部分。
8.1. Test 依赖的 Scope
spring-boot-starter-test
“Starter” (在 test
范围内)包含以下提供的库。
-
JUnit 5: 单元测试Java应用程序的事实标准。
-
Spring Test & Spring Boot Test: 为Spring Boot应用程序提供实用程序和集成测试支持。
-
AssertJ: 一个 fluent 风格的断言库。
-
Hamcrest: 一个匹配器对象库(也被称为约束或谓词)。
-
Mockito: 一个 Java mocking 框架.
-
JSONassert: 一个用于JSON的断言库。
-
JsonPath: 用于JSON的XPath。
我们一般认为这些常用的库在编写测试时是很有用的。 如果这些库不适合你的需要,你可以添加你自己的额外的测试依赖。
8.2. 测试 Spring Application
依赖注入的主要优点之一是,它应该使你的代码更容易进行单元测试。
你可以通过使用 new
来实例化对象,甚至不需要涉及 Spring。
你还可以使用 mock object 来代替真实的依赖关系。
通常,你需要超越单元测试,开始进行集成测试(使用Spring ApplicationContext
)。
能够进行集成测试而不需要部署你的应用程序或需要连接到其他基础设施是非常有用的。
Spring框架包括一个专门的测试模块,用于此类集成测试。
你可以直接向 org.springframework:spring-test
声明依赖关系,或者使用 spring-boot-starter-test
“Starter” 将其转入。
如果你以前没有使用过 spring-test
模块,你应该从阅读Spring框架参考文档的 相关部分 开始。
8.3. 测试 Spring Boot Application
Spring Boot应用程序是一个Spring ApplicationContext
,所以除了通常对普通Spring上下文的测试外,不需要做什么特别的测试。
只有当你使用 SpringApplication 来创建时,Spring Boot的外部属性、日志和其他功能才会默认安装在上下文中。
|
Spring Boot提供了一个 @SpringBootTest
注解,当你需要Spring Boot功能时,可以用它来替代标准的 spring-test
@ContextConfiguration
注解。该注解通过SpringApplication创建测试中使用的ApplicationContext来工作。除了 @SpringBootTest
之外,还提供了其他一些注解,用于测试应用程序的更多具体片断。
如果你使用JUnit 4,不要忘记在你的测试中也添加 @RunWith(SpringRunner.class) ,否则注解会被忽略掉。如果你使用的是JUnit 5,就不需要添加等价的 @ExtendWith(SpringExtension.class) ,因为 @SpringBootTest 和其他的 @…Test 注解已经被注解了。
|
默认情况下,@SpringBootTest
不会启动一个服务器。你可以使用 @SpringBootTest
的 webEnvironment
属性来进一步完善你的测试运行方式。
-
MOCK
(Default) : 加载一个WebApplicationContext
并提供一个模拟的Web环境。当使用此注解时,嵌入式服务器不会被启动。如果你的classpath上没有web环境,这种模式会自动退回到创建一个普通的非webApplicationContext
。它可以与@AutoConfigureMockMvc
或@AutoConfigureWebTestClient
一起使用,用于对你的Web应用进行基于模拟的测试。 -
RANDOM_PORT
: 加载一个WebServerApplicationContext
并提供一个真实的web环境。嵌入式服务器被启动并监听一个随机端口。 -
DEFINED_PORT
: 加载一个WebServerApplicationContext
并提供一个真实的web环境。嵌入式服务器被启动并监听一个定义的端口(来自你的application.properties
)或默认的8080
端口。 -
NONE
: 通过使用SpringApplication
加载一个ApplicationContext
,但不提供任何Web环境(模拟或其他)。
如果你的测试是 @Transactional ,它默认在每个测试方法结束时回滚事务。然而,由于使用这种安排与 RANDOM_PORT 或 DEFINED_PORT 隐含地提供了一个真正的servlet环境,HTTP客户端和服务器在独立的线程中运行,因此,在独立的事务中。在这种情况下,服务器上启动的任何事务都不会回滚。
|
@SpringBootTest 与 webEnvironment = WebEnvironment.RANDOM_PORT 也将在一个单独的随机端口上启动 management server,如果你的应用程序为 management server 使用一个不同的端口。
|
8.3.1. 检测 Web Application 的类型
如果有Spring MVC,就会配置一个基于MVC的常规application context。 如果你只有Spring WebFlux,我们将检测并配置一个基于WebFlux的application context。
如果两者都存在,Spring MVC优先。
如果你想在这种情况下测试一个响应式Web应用,你必须设置 spring.main.web-application-type
属性。
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {
// ...
}
@SpringBootTest(properties = ["spring.main.web-application-type=reactive"])
class MyWebFluxTests {
// ...
}
8.3.2. 检测测试配置(Test Configuration)
如果你熟悉Spring测试框架,你可能习惯于使用 @ContextConfiguration(classes=…)
来指定加载哪个Spring @Configuration
。另外,你可能经常在你的测试中使用嵌套的 @Configuration
类。
在测试Spring Boot应用程序时,通常不需要这样做。
只要你没有明确定义,Spring Boot的 @*Test
注解就会自动搜索你的 main configuration。
搜索算法从包含测试的包开始向上搜索,直到找到一个用 @SpringBootApplication
或 @SpringBootConfiguration
注解的类。
只要你以合理的方式 组织你的代码,通常就能找到你的 main configuration。
如果你使用test注解来测试你的应用程序的一个更具体的片断,你应该避免在main方法的application类上添加特定于某个区域的配置。
|
如果你想定制主配置,你可以使用一个嵌套的 @TestConfiguration
类。
与嵌套的 @Configuration
类不同的是,嵌套的 @TestConfiguration
类是在你的应用程序的主要配置之外使用的,它将代替你的应用程序的主要配置。
Spring的测试框架在测试之间缓存了应用程序上下文。 因此,只要你的测试共享相同的配置(无论它是如何被发现的),加载上下文的潜在耗时过程只发生一次。 |
8.3.3. 使用 Test Configuration 的main方法
通常,由 @SpringBootTest
发现的 test configuration 将是你的main @SpringBootApplication
。在大多数结构良好的应用程序中,这个配置类也将包括用于启动应用程序的main方法。
例如,以下是一个典型的Spring Boot应用程序的非常常见的代码模式。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
在上面的例子中,main
方法除了委托给 SpringApplication.run
之外没有做任何事情。
然而,在调用 SpringApplication.run
之前,可以有一个更复杂的 main
方法来应用自定义。
例如,这里有一个改变banner mode和设置额外profiles的应用程序。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.setAdditionalProfiles("myprofile");
application.run(args);
}
}
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
setAdditionalProfiles("myprofile");
}
}
由于在 main
方法中的定制可以影响产生的 ApplicationContext
,所以你有可能也想使用 main
方法来创建你测试中使用的 ApplicationContext
。
默认情况下,@SpringBootTest
不会调用你的 main
方法,而是直接使用类本身来创建 ApplicationContext
。
如果你想改变这种行为,你可以将 @SpringBootTest
的 useMainMethod
属性改为 UseMainMethod.ALWAYS
或 UseMainMethod.WHEN_AVAILABLE
。
当设置为 ALWAYS
时,如果找不到 main
方法,测试将失败。
当设置为 WHEN_AVAILABLE
时,如果 main
方法可用,将被使用,否则将使用标准加载机制。
例如,下面的测试将调用 MyApplication
的 main
方法,以创建 ApplicationContext
。
如果main方法设置了额外的profile,那么当 ApplicationContext
启动时,这些profile(配置文件)将被激活。
@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {
@Test
void exampleTest() {
// ...
}
}
@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {
@Test
fun exampleTest() {
// ...
}
}
8.3.4. 排除测试配置(Excluding Test Configuration)
如果你的应用程序使用组件扫描(例如,如果你使用 @SpringBootApplication
或 @ComponentScan
),你可能会发现你只为特定测试创建的顶级配置类意外地被到处捡到(加载?)。
正如我们前面所看到的,@TestConfiguration
可以用在一个测试的内部类上,以定制主要配置。当放在一个顶层类上时,@TestConfiguration
表示 src/test/java
中的类不应该被扫描加载。然后你可以在需要的地方明确地导入该类,如下面的例子所示。
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
@Test
void exampleTest() {
// ...
}
}
@SpringBootTest
@Import(MyTestsConfiguration::class)
class MyTests {
@Test
fun exampleTest() {
// ...
}
}
如果你直接使用 @ComponentScan (也就是不通过 @SpringBootApplication ),你需要向它注册 TypeExcludeFilter 。
详情见 Javadoc。
|
8.3.5. 使用应用参数(Application arguments)
如果你的应用程序期望有参数,你可以让 @SpringBootTest
使用 args
属性注入它们。
@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");
}
}
@SpringBootTest(args = ["--app.test=one"])
class MyApplicationArgumentTests {
@Test
fun applicationArgumentsPopulated(@Autowired args: ApplicationArguments) {
assertThat(args.optionNames).containsOnly("app.test")
assertThat(args.getOptionValues("app.test")).containsOnly("one")
}
}
8.3.6. 使用模拟环境进行测试
默认情况下,@SpringBootTest
不会启动服务器,而是设置一个模拟环境来测试Web endpoint。
通过Spring MVC,我们可以使用 MockMvc
或 WebTestClient
来查询我们的Web端点,如以下例子所示。
@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");
}
}
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {
@Test
fun testWithMockMvc(@Autowired mvc: MockMvc) {
mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
}
// If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
@Test
fun testWithWebTestClient(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
如果你想只关注Web层而不启动一个完整的 ApplicationContext ,可以考虑使用 @WebMvcTest 代替。
|
对于Spring WebFlux端点,你可以使用 WebTestClient
,如下例所示。
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
fun exampleTest(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
在模拟的环境中进行测试,通常比使用完整的servlet容器运行要快。 然而,由于mock发生在Spring MVC层,依赖低级别的servlet容器行为的代码不能直接用MockMvc测试。 例如,Spring Boot的错误处理是基于Servlet容器提供的 “error page” 支持。这意味着,虽然你可以测试你的MVC层抛出并按预期处理异常,但你不能直接测试特定的自定义错误页面是否呈现。如果你需要测试这些低层次的问题,你可以启动一个完整运行的服务器,如下一节所述。 |
8.3.7. 用运行中的服务器进行测试
如果你需要启动一个完整运行的服务器,我们建议你使用随机端口。
如果你使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
,每次测试运行时都会随机挑选一个可用端口。
@LocalServerPort
注解可用于将 实际使用的端口注入你的测试中。
为了方便起见,需要对启动的服务器进行REST调用的测试可以另外 @Autowire
一个 WebTestClient
,它可以解析到运行中的服务器的相对链接,并带有一个专门的API来验证响应,如下例所示。
@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");
}
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {
@Test
fun exampleTest(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Hello World")
}
}
WebTestClient 可以针对实时服务器和模拟环境使用。
|
这种设置要求classpath上有 spring-webflux
。
如果你不能或不愿添加webflux,Spring Boot也提供了一个 TestRestTemplate
设施。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
void exampleTest(@Autowired TestRestTemplate restTemplate) {
String body = restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
fun exampleTest(@Autowired restTemplate: TestRestTemplate) {
val body = restTemplate.getForObject("/", String::class.java)
assertThat(body).isEqualTo("Hello World")
}
}
8.3.8. 自定义 WebTestClient
为了定制 WebTestClient
Bean,请配置一个 WebTestClientBuilderCustomizer
Bean。任何这样的Bean都是与用于创建 WebTestClient
的 WebTestClient.Builder
一起调用的。
8.3.9. 使用 JMX
由于测试context框架缓存了context,JMX默认是禁用的,以防止相同的组件在同一域注册。
如果这样的测试需要访问一个 MBeanServer
,请考虑把它也标记为脏(dirty)。
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {
@Autowired
private MBeanServer mBeanServer;
@Test
void exampleTest() {
assertThat(this.mBeanServer.getDomains()).contains("java.lang");
// ...
}
}
@SpringBootTest(properties = ["spring.jmx.enabled=true"])
@DirtiesContext
class MyJmxTests(@Autowired val mBeanServer: MBeanServer) {
@Test
fun exampleTest() {
assertThat(mBeanServer.domains).contains("java.lang")
// ...
}
}
8.3.10. 使用指标(Metrics)
无论你的classpath如何,除了内存中的 meter registries,在使用 @SpringBootTest
时都不会自动配置。
如果你需要将指标导出到不同的后端,作为集成测试的一部分,用 @AutoConfigureObservability
注解。
8.3.11. 使用追踪(Tracing)
无论你的classpath如何,当使用 @SpringBootTest
时,tracing 不会被自动配置。
如果你需要时,tracing 作为集成测试的一部分,请用 @AutoConfigureObservability
注解。
8.3.12. MockBean 和 SpyBean
当运行测试时,有时需要在你的应用环境中模拟某些组件。 例如,你可能有一个在开发期间不可用的远程服务的接口。 当你想模拟在真实环境中很难触发的故障时,mock也很有用。
Spring Boot包含一个 @MockBean
注解,可以用来为 ApplicationContext
中的Bean定义一个Mockito mock。
你可以使用该注解来添加新的Bean或替换现有的单一Bean定义。
该注解可以直接用于测试类,测试中的字段,或 @Configuration
类和字段。
当在一个字段上使用时,创建的模拟实例也被注入。
模拟Bean在每个测试方法后都会自动重置。
如果你的测试使用了Spring Boot的一个测试注解(如 Java
Kotlin
|
下面的例子用一个模拟的实现替换了现有的 RemoteService
Bean。
@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");
}
}
@SpringBootTest
class MyTests(@Autowired val reverser: Reverser, @MockBean val remoteService: RemoteService) {
@Test
fun exampleTest() {
given(remoteService.value).willReturn("spring")
val reverse = reverser.reverseValue // Calls injected RemoteService
assertThat(reverse).isEqualTo("gnirps")
}
}
@MockBean 不能用来模拟在应用application context刷新期间行使的Bean的行为。
当测试被执行时,application context的刷新已经完成,配置模拟的行为已经太晚了。
我们建议在这种情况下使用 @Bean 方法来创建和配置模拟行为。
|
此外,你还可以使用 @SpyBean
将任何现有的Bean包装成 Mockito spy
。
详细内容请参见 Javadoc。
CGLib代理,比如那些为作用域Bean创建的代理,将代理的方法声明为 final 。这使得Mockito无法正常工作,因为在其默认配置中,它不能对 final 方法进行mock或spy。如果你想对这样的Bean进行mock或spy,请将 org.mockito:mockito-inline 添加到你的应用程序的测试依赖中,以配置Mockito使用其内联模拟器。这使得Mockito可以对 final 方法进行mock和spy。
|
虽然Spring的测试框架在测试之间缓存application context,并为共享相同配置的测试重用一个context,但使用 @MockBean 或 @SpyBean 会影响缓存key,这很可能会增加上下文的数量。
|
如果你使用 @SpyBean 来spy一个有 @Cacheable 方法的Bean,这些方法通过名称来引用参数,你的应用程序必须用 -parameters 来编译。
这可以确保一旦Bean被spy到,参数名称就可以被缓存基础设施使用。
|
当你使用 @SpyBean 来spy被Spring代理的Bean时,你可能需要在某些情况下移除Spring的代理,例如在使用 given 或 when 设置expectation时。
使用 AopTestUtils.getTargetObject(yourProxiedSpy) 来做到这一点。
|
8.3.13. 自动配置的测试
Spring Boot的自动配置系统对应用程序来说效果很好,但对测试来说有时会有点过头。 通常情况下,只加载测试应用程序的 “slice” 所需的配置部分就可以了。 例如,你可能想测试Spring MVC控制器是否正确映射了URL,而你不想在这些测试中涉及数据库调用,或者你可能想测试JPA实体,而你对这些测试运行时的Web层不感兴趣。
spring-boot-test-autoconfigure
模块包括一些注解,可以用来自动配置这种 “slices”。
每一个都以类似的方式工作,提供一个加载 ApplicationContext
的 @…Test
注解和一个或多个 @AutoConfigure…
注解,可用于自定义自动配置设置。
每个slice将组件扫描限制在适当的组件上,并加载一组非常有限的自动配置类。
如果你需要排除其中一个,大多数 @…Test 注释提供了一个 excludeAutoConfiguration 属性。
或者,你可以使用 @ImportAutoConfiguration#exclude 。
|
不支持通过在一个测试中使用几个 @…Test 注解来包括多个 “slices”。
如果你需要多个 “slices”,选择其中一个 @…Test` 注解,然后手动包括其他 “slices” 的 @AutoConfigure… 注解。
|
也可以将 @AutoConfigure… 注解与标准的 @SpringBootTest 注解一起使用。如果你对 “slice” 你的应用程序不感兴趣,但你想要一些自动配置的测试Bean,你可以使用这种组合。
|
8.3.14. JSON 测试的自动配置
为了测试对象JSON序列化和反序列化是否按预期工作,你可以使用 @JsonTest
注解。
@JsonTest
自动配置可用的支持JSON映射器,它可以是以下库之一。
-
Jackson
ObjectMapper
, 任何@JsonComponent
bean 和任何 JacksonModule
-
Gson
-
Jsonb
@JsonTest 启用的自动配置列表可以在附录中找到。
|
如果你需要配置自动配置的元素,你可以使用 @AutoConfigureJsonTesters
注解。
Spring Boot包括基于AssertJ的helper,与JSONAssert和JsonPath库一起工作,以检查JSON是否符合预期。
JacksonTester
、GsonTester
、JsonbTester
和 BasicJsonTester
类可以分别用于Jackson、Gson、Jsonb和Strings。
当使用 @JsonTest
时,测试类上的任何helper字段都可以是 @Autowired
。
下面的例子显示了一个Jackson的测试类。
@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");
}
}
@JsonTest
class MyJsonTests(@Autowired val json: JacksonTester<VehicleDetails>) {
@Test
fun serialize() {
val details = VehicleDetails("Honda", "Civic")
// Assert against a `.json` file in the same package as the test
assertThat(json.write(details)).isEqualToJson("expected.json")
// Or use JSON path based assertions
assertThat(json.write(details)).hasJsonPathStringValue("@.make")
assertThat(json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda")
}
@Test
fun deserialize() {
val content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"
assertThat(json.parse(content)).isEqualTo(VehicleDetails("Ford", "Focus"))
assertThat(json.parseObject(content).make).isEqualTo("Ford")
}
}
JSON helper 类也可以直接用于标准单元测试。
要做到这一点,如果你不使用 @JsonTest ,请在你的 @Before 方法中调用该helper的 initFields 方法。
|
如果你使用 Spring Boot 的基于AssertJ的helper来断言给定JSON路径的数字值,你可能无法使用 isEqualTo
,这取决于类型。
相反,你可以使用AssertJ的 satisfies
来断言该值符合给定条件。
例如,下面的例子断言实际数字是一个接近 0.15
的浮点数,偏移量为 0.01
。
@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)));
}
@Test
fun someTest() {
val value = SomeObject(0.152f)
assertThat(json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
.satisfies(ThrowingConsumer { number ->
assertThat(number.toFloat()).isCloseTo(0.15f, within(0.01f))
})
}
8.3.15. Spring MVC 测试的自动配置
要测试Spring MVC控制器是否按预期工作,可以使用 @WebMvcTest
注解。@WebMvcTest
自动配置Spring MVC基础设施,并将扫描的Bean限制在 @Controller
、@ControllerAdvice
、@JsonComponent
、Converter
、GenericConverter
、Filter
、HandlerInterceptor
、WebMvcConfigurer
、WebMvcRegistrations
以及 HandlerMethodArgumentResolver
。当使用 @WebMvcTest
注解时,常规的 @Component
和 @ConfigurationProperties`
Bean不会被扫描。@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
@WebMvcTest 所启用的自动配置设置的列表可以在附录中找到。
|
如果你需要注册额外的组件,比如Jackson Module ,你可以通过在测试中使用 @Import 导入额外的配置类。
|
通常情况下,@WebMvcTest
仅限于一个controller,并与 @MockBean
结合使用,为需要的合作者提供mock实现。
@WebMvcTest
也自动配置了 MockMvc
。
Mock MVC提供了一种强大的方式来快速测试MVC controller,而不需要启动一个完整的HTTP服务器。
你也可以在非 @WebMvcTest (如 @SpringBootTest )中自动配置 MockMvc ,方法是用 @AutoConfigureMockMvc 注解它。
下面的例子使用了 MockMvc 。
|
@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"));
}
}
@WebMvcTest(UserVehicleController::class)
class MyControllerTests(@Autowired val mvc: MockMvc) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().string("Honda Civic"))
}
}
如果你需要配置自动配置的元素(例如,何时应该应用servlet filter),你可以使用 @AutoConfigureMockMvc 注解中的属性。
|
如果你使用HtmlUnit和Selenium,自动配置也提供了一个HtmlUnit WebClient
Bean 和/或一个Selenium WebDriver
Bean。
下面的例子使用HtmlUnit。
@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");
}
}
@WebMvcTest(UserVehicleController::class)
class MyHtmlUnitTests(@Autowired val webClient: WebClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot")).willReturn(VehicleDetails("Honda", "Civic"))
val page = webClient.getPage<HtmlPage>("/sboot/vehicle.html")
assertThat(page.body.textContent).isEqualTo("Honda Civic")
}
}
默认情况下,Spring Boot将 WebDriver Bean放在一个特殊的 “scope” 中,以确保每次测试后驱动程序都会退出,并注入一个新实例。
如果你不想要这种行为,你可以在你的 WebDriver @Bean 定义中添加 @Scope("singleton") 。
|
Spring Boot创建的 webDriver scope将取代任何用户定义的同名scope。
如果你定义了自己的 webDriver scope,你可能会发现当你使用 @WebMvcTest 时,它就停止工作了。
|
如果你在classpath上有Spring Security,@WebMvcTest
也将扫描 WebSecurityConfigurer
Bean。
你可以使用Spring Security的测试支持,而不是为这类测试完全禁用security。
关于如何使用Spring Security的 MockMvc
支持的更多细节,可以在这个 howto.html 的how-to部分找到。
有时仅仅编写Spring MVC测试是不够的;Spring Boot可以帮助你用实际的服务器运行完整的端到端测试。 |
8.3.16. Spring WebFlux 测试的自动配置
为了测试 Spring WebFlux 控制器是否按预期工作,你可以使用 @WebFluxTest
注解。
@WebFluxTest
自动配置Spring WebFlux基础设施,并将扫描的bean限制在 @Controller
、@ControllerAdvice
、@JsonComponent
、Converter
、GenericConverter
、WebFilter
和 WebFluxConfigurer
。
当使用 @WebFluxTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean 不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
@WebFluxTest 启用的自动配置列表可以在 在附录中找到。
|
如果你需要注册额外的组件,如Jackson Module ,你可以在测试中使用 @Import 导入额外的配置类。
|
通常,@WebFluxTest
被限制在一个单一的controller,并与 @MockBean
注解结合使用,为所需的合作者提供mock实现。
@WebFluxTest
还自动配置了https://docs.spring.io/spring-framework/docs/6.1.0-M1/reference/html/testing.html#webtestclient[WebTestClient
],这为快速测试WebFlux controller 提供了一种强大的方式,而不需要启动完整的HTTP服务器。
你也可以在非 @WebFluxTest (如 @SpringBootTest )中自动配置 WebTestClient ,方法是用 @AutoConfigureWebTestClient 来注解它。
下面的例子显示了一个同时使用 @WebFluxTest 和 WebTestClient 的类。
|
@WebFluxTest(UserVehicleController.class)
class MyControllerTests {
@Autowired
private WebTestClient webClient;
@MockBean
private UserVehicleService userVehicleService;
@Test
void testExample() {
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");
}
}
@WebFluxTest(UserVehicleController::class)
class MyControllerTests(@Autowired val webClient: WebTestClient) {
@MockBean
lateinit var userVehicleService: UserVehicleService
@Test
fun testExample() {
given(userVehicleService.getVehicleDetails("sboot"))
.willReturn(VehicleDetails("Honda", "Civic"))
webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
.expectStatus().isOk
.expectBody<String>().isEqualTo("Honda Civic")
}
}
这种设置只被WebFlux应用程序支持,因为在模拟的Web应用程序中使用 WebTestClient 目前只适用于WebFlux。
|
@WebFluxTest 不能检测到通过function(RouterFunction)注册的路由。
对于在上下文中测试 RouterFunction Bean,可以考虑通过使用 @Import 或使用 @SpringBootTest 自己导入你的 RouterFunction 。
|
@WebFluxTest 无法检测到作为 @Bean 类型的 SecurityWebFilterChain 注册的自定义security配置。
要在你的测试中包括这一点,你需要通过使用 @Import 或使用 @SpringBootTest 来导入注册bean的配置。
|
有时仅仅编写Spring WebFlux测试是不够的;Spring Boot可以帮助你用实际的服务器运行完整的端到端测试。 |
8.3.17. Spring GraphQL 测试的自动配置
Spring GraphQL提供了一个专门的测试支持模块;你需要将其添加到你的项目中。
<dependencies>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Unless already present in the compile scope -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
dependencies {
testImplementation("org.springframework.graphql:spring-graphql-test")
// Unless already present in the implementation configuration
testImplementation("org.springframework.boot:spring-boot-starter-webflux")
}
这个测试模块搭载了 GraphQlTester。该 tester 在测试中被大量使用,所以一定要熟悉使用它。有一些 GraphQlTester
的变体,Spring Boot会根据测试的类型自动配置它们。
-
ExecutionGraphQlServiceTester
在服务器端进行测试,没有client,也没有transport。HttpGraphQlTester
用一个连接到服务器的客户端进行测试,无论是否有一个实时服务器。
Spring Boot通过 @GraphQlTest
注解帮助你测试你的 Spring GraphQL Controller 。@GraphQlTest
自动配置 Spring GraphQL 基础设施,不涉及任何transport或server。这将扫描的Bean限制在 @Controller
、 RuntimeWiringConfigurer
、JsonComponent
、Converter
、GenericConverter
、DataFetcherExceptionResolver
、Instrumentation
和 GraphQlSourceBuilderCustomizer
。当使用 @GraphQlTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean 不会被扫描。 @EnableConfigurationProperties
可以用来包括(include)@ConfigurationProperties
Bean。
在附录中可以找到 @GraphQlTest 所启用的自动配置的列表。
|
通常,@GraphQlTest
仅限于一组 Controller,并与 @MockBean
注解结合使用,为所需的合作者提供模拟实现。
@GraphQlTest(GreetingController.class)
class GreetingControllerTests {
@Autowired
private GraphQlTester graphQlTester;
@Test
void shouldGreetWithSpecificName() {
this.graphQlTester.document("{ greeting(name: \"Alice\") } ")
.execute()
.path("greeting")
.entity(String.class)
.isEqualTo("Hello, Alice!");
}
@Test
void shouldGreetWithDefaultName() {
this.graphQlTester.document("{ greeting } ")
.execute()
.path("greeting")
.entity(String.class)
.isEqualTo("Hello, Spring!");
}
}
@GraphQlTest(GreetingController::class)
internal class GreetingControllerTests {
@Autowired
lateinit var graphQlTester: GraphQlTester
@Test
fun shouldGreetWithSpecificName() {
graphQlTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String::class.java)
.isEqualTo("Hello, Alice!")
}
@Test
fun shouldGreetWithDefaultName() {
graphQlTester.document("{ greeting } ").execute().path("greeting").entity(String::class.java)
.isEqualTo("Hello, Spring!")
}
}
@SpringBootTest
测试是完全的集成测试,涉及整个应用程序。当使用一个随机或定义的端口时,一个实时服务器被配置,一个 HttpGraphQlTester
Bean被自动贡献出来,所以你可以使用它来测试你的服务器。当配置了一个MOCK环境时,你也可以通过用 @AutoConfigureHttpGraphQlTester
注解你的测试类来请求一个 HttpGraphQlTester
Bean。
@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {
@Test
void shouldGreetWithSpecificName(@Autowired HttpGraphQlTester graphQlTester) {
HttpGraphQlTester authenticatedTester = graphQlTester.mutate()
.webTestClient((client) -> client.defaultHeaders((headers) -> headers.setBasicAuth("admin", "ilovespring")))
.build();
authenticatedTester.document("{ greeting(name: \"Alice\") } ")
.execute()
.path("greeting")
.entity(String.class)
.isEqualTo("Hello, Alice!");
}
}
@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {
@Test
fun shouldGreetWithSpecificName(@Autowired graphQlTester: HttpGraphQlTester) {
val authenticatedTester = graphQlTester.mutate()
.webTestClient { client: WebTestClient.Builder ->
client.defaultHeaders { headers: HttpHeaders ->
headers.setBasicAuth("admin", "ilovespring")
}
}.build()
authenticatedTester.document("{ greeting(name: \"Alice\") } ").execute()
.path("greeting").entity(String::class.java).isEqualTo("Hello, Alice!")
}
}
8.3.18. Cassandra 测试的自动配置
你可以使用 @DataCassandraTest
来测试Cassandra应用程序。
默认情况下,它配置一个 CassandraTemplate
,扫描 @Table
类,并配置 Spring Data Cassandra Repository。
当使用 @DataCassandraTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用Cassandra的更多信息,请参阅"data.html"。)
由 @DataCassandraTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了在Spring Boot中使用Cassandra测试的一个典型设置。
@DataCassandraTest
class MyDataCassandraTests {
@Autowired
private SomeRepository repository;
}
@DataCassandraTest
class MyDataCassandraTests(@Autowired val repository: SomeRepository)
8.3.19. Couchbase 测试的自动配置
你可以使用 @DataCouchbaseTest
来测试Couchbase应用程序。
默认情况下,它配置一个 CouchbaseTemplate
或 ReactiveCouchbaseTemplate
,扫描 @Document
类,并配置Spring Data Couchbase Repository。
当使用 @DataCouchbaseTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用Couchbase的更多信息,请参见本章前面的"data.html"。)
由 @DataCouchbaseTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了在Spring Boot中使用Couchbase测试的一个典型设置。
@DataCouchbaseTest
class MyDataCouchbaseTests {
@Autowired
private SomeRepository repository;
// ...
}
@DataCouchbaseTest
class MyDataCouchbaseTests(@Autowired val repository: SomeRepository) {
// ...
}
8.3.20. Elasticsearch 测试的自动配置
你可以使用 @DataElasticsearchTest
来测试Elasticsearch应用程序。
默认情况下,它配置一个 ElasticsearchRestTemplate
,扫描 @Document
类,并配置Spring Data Elasticsearch Repository。
当使用 @DataElasticsearchTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用Elasticsearch的更多信息,请参见本章前面的"data.html"。)
由 @DataElasticsearchTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了在Spring Boot中使用Elasticsearch测试的一个典型设置。
@DataElasticsearchTest
class MyDataElasticsearchTests {
@Autowired
private SomeRepository repository;
// ...
}
@DataElasticsearchTest
class MyDataElasticsearchTests(@Autowired val repository: SomeRepository) {
// ...
}
8.3.21. Data JPA 测试的自动配置
你可以使用 @DataJpaTest
注解来测试JPA应用程序。
默认情况下,它会扫描 @Entity
类并配置Spring Data JPA Repository。
如果classpath上有一个嵌入式数据库,它也会配置一个。
默认情况下,通过设置 spring.jpa.show-sql
属性为 true
来记录SQL查询。
这可以通过注解的 showSql
属性来禁用。
当使用 @DataJpaTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
@DataJpaTest 启用的自动配置设置的列表可以在在附录中找到。
|
默认情况下,JPA测试是在事务性中进行的,并在每次测试结束后回滚。 更多细节请参见Spring框架参考文档中的 相关章节。 如果这不是你想要的,你可以为一个测试或整个类停用事务管理,如下所示。
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {
// ...
}
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {
// ...
}
JPA测试也可以注入一个 TestEntityManager
bean,它提供了一个标准JPA EntityManager
的替代品,是专门为测试设计的。
TestEntityManager 也可以通过添加 @AutoConfigureTestEntityManager 自动配置到任何基于Spring的测试类中。
当这样做时,确保你的测试在事务中运行,例如在你的测试类或方法上添加 @Transactional 。
|
如果你需要,也可以使用 JdbcTemplate
。
下面的例子显示了 @DataJpaTest
注解的使用情况。
@DataJpaTest
class MyRepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository repository;
@Test
void testExample() {
this.entityManager.persist(new User("sboot", "1234"));
User user = this.repository.findByUsername("sboot");
assertThat(user.getUsername()).isEqualTo("sboot");
assertThat(user.getEmployeeNumber()).isEqualTo("1234");
}
}
@DataJpaTest
class MyRepositoryTests(@Autowired val entityManager: TestEntityManager, @Autowired val repository: UserRepository) {
@Test
fun testExample() {
entityManager.persist(User("sboot", "1234"))
val user = repository.findByUsername("sboot")
assertThat(user?.username).isEqualTo("sboot")
assertThat(user?.employeeNumber).isEqualTo("1234")
}
}
内存中的嵌入式数据库通常对测试有很好的效果,因为它们速度快而且不需要任何安装。
然而,如果你喜欢针对真实的数据库运行测试,你可以使用 @AutoConfigureTestDatabase
注解,如下面的例子所示。
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {
// ...
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MyRepositoryTests {
// ...
}
8.3.22. JDBC 测试的自动配置
@JdbcTest
与 @DataJpaTest
类似,但用于只需要 DataSource
而不使用Spring Data JDBC的测试。
默认情况下,它配置了一个内存嵌入式数据库和一个 JdbcTemplate
。
当使用 @JdbcTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
@JdbcTest 启用的自动配置列表可以在在附录中找到。
|
默认情况下,JDBC测试是在事务中进行的,并在每个测试结束后回滚。 更多细节请参见Spring框架参考文档中的 相关章节。 如果这不是你想要的,你可以为一个测试或整个类停用事务管理,如下所示。
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests {
}
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests
如果你希望你的测试针对真实的数据库运行,你可以使用 @AutoConfigureTestDatabase
注解,方法与 DataJpaTest
一样。
(参见"Data JPA 测试的自动配置"。)
8.3.23. Data JDBC 测试的自动配置
@DataJdbcTest
与 @JdbcTest
类似,但用于使用Spring Data JDBC Repository 的测试。
默认情况下,它配置了一个内存中的嵌入式数据库、一个 JdbcTemplate
和Spring Data JDBC Repository。
当使用 @DataJdbcTest
注解时,只有 AbstractJdbcConfiguration
子类被扫描,普通的`@Component` 和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
@DataJdbcTest 启用的自动配置列表可以在在附录中找到。
|
默认情况下,Data JDBC测试是在事务中进行的,并在每次测试结束后回滚。 更多细节请参见Spring框架参考文档中的 相关章节。 如果这不是你想要的,你可以像在JDBC例子中所示那样,禁用某个测试或整个测试类的事务管理(transaction management)。
如果你希望你的测试针对真实的数据库运行,你可以使用 @AutoConfigureTestDatabase
注解,方法与 DataJpaTest
一样。
(参见"Data JPA 测试的自动配置"。)
8.3.24. jOOQ 测试的自动配置
你可以以类似于 @JdbcTest
的方式使用 @JooqTest
,但是用于jOOQ相关的测试。
由于jOOQ在很大程度上依赖于与数据库schema相对应的基于Java的schema,因此使用了现有的 DataSource
。
如果你想用内存数据库取代它,你可以使用 @AutoConfigureTestDatabase
来覆盖这些设置。
(关于在Spring Boot中使用jOOQ的更多信息,见"data.html")。
当使用 @JooqTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
@JooqTest 启用的自动配置列表可以在在附录中找到。
|
@JooqTest
配置了一个 DSLContext
。
下面的例子显示了 @JooqTest
注解的使用情况。
@JooqTest
class MyJooqTests {
@Autowired
private DSLContext dslContext;
// ...
}
@JooqTest
class MyJooqTests(@Autowired val dslContext: DSLContext) {
// ...
}
JOOQ测试是在事务中进行的,默认在每个测试结束时回滚。 如果这不是你想要的,你可以像在JDBC例子中所示那样,禁用某个测试或整个测试类的事务管理。
8.3.25. MongoDB 测试的自动配置
你可以使用 @DataMongoTest
来测试MongoDB应用程序。
默认情况下,它配置了一个 MongoTemplate
,扫描了 @Document
类,并配置了Spring Data MongoDB Repositroy。
当使用 @DataMongoTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可用于包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用MongoDB的更多信息,请参阅"data.html"。)
@DataMongoTest 启用的自动配置设置列表可以在在附录中找到。
|
下面的类显示了 @DataMongoTest
注解的使用情况。
@DataMongoTest
class MyDataMongoDbTests {
@Autowired
private MongoTemplate mongoTemplate;
// ...
}
@DataMongoTest
class MyDataMongoDbTests(@Autowired val mongoTemplate: MongoTemplate) {
// ...
}
8.3.26. Neo4j 测试的自动配置
你可以使用 @DataNeo4jTest
来测试Neo4j应用程序。
默认情况下,它扫描 @Node
类,并配置Spring Data Neo4j Repository。
当使用 @DataNeo4jTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用Neo4J的更多信息,请参阅"data.html"。)
@DataNeo4jTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了在Spring Boot中使用Neo4J测试的一个典型设置。
@DataNeo4jTest
class MyDataNeo4jTests {
@Autowired
private SomeRepository repository;
// ...
}
@DataNeo4jTest
class MyDataNeo4jTests(@Autowired val repository: SomeRepository) {
// ...
}
默认情况下,Data Neo4j测试是在事务中进行的,并在每次测试结束后回滚。 详情请参见Spring框架参考文档中的 相关章节。 如果这不是你想要的,你可以为一个测试或整个类停用事务管理(transaction management),如下所示。
@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests {
}
@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests
事务测试不支持响应式(reactive)访问。
如果你使用这种风格,你必须如上所述配置 @DataNeo4jTest 测试。
|
8.3.27. Redis 测试的自动配置
你可以使用 @DataRedisTest
来测试Redis应用程序。
默认情况下,它扫描 @RedisHash
类并配置 Spring Data Redis Repository。
当使用 @DataRedisTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用Redis的更多信息,见"data.html"。)
@DataRedisTest 启用的自动配置设置列表可以在在附录中找到。
|
下面的例子显示了 @DataRedisTest
注解的使用情况。
@DataRedisTest
class MyDataRedisTests {
@Autowired
private SomeRepository repository;
// ...
}
@DataRedisTest
class MyDataRedisTests(@Autowired val repository: SomeRepository) {
// ...
}
8.3.28. LDAP 测试的自动配置
你可以使用 @DataLdapTest
来测试LDAP应用程序。
默认情况下,它配置内存中的嵌入式LDAP(如果可用),配置 LdapTemplate
,扫描 @Entry
类,并配置Spring Data LDAP Respotiroy。
当使用 @DataLdapTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
(关于在Spring Boot中使用LDAP的更多信息,请参阅"data.html"。)
@DataLdapTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了 @DataLdapTest
注解的使用情况。
@DataLdapTest
class MyDataLdapTests {
@Autowired
private LdapTemplate ldapTemplate;
// ...
}
@DataLdapTest
class MyDataLdapTests(@Autowired val ldapTemplate: LdapTemplate) {
// ...
}
内存中的嵌入式 LDAP 通常对测试很有效,因为它速度快,而且不需要任何开发人员安装。 然而,如果你喜欢针对真实的LDAP服务器运行测试,你应该排除嵌入式LDAP的自动配置,如下面的例子所示。
@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
class MyDataLdapTests {
// ...
}
@DataLdapTest(excludeAutoConfiguration = [EmbeddedLdapAutoConfiguration::class])
class MyDataLdapTests {
// ...
}
8.3.29. REST Client 的自动配置
你可以使用 @RestClientTest
注解来测试REST客户端。
默认情况下,它自动配置Jackson、GSON和Jsonb支持,配置 RestTemplateBuilder
,并增加对 MockRestServiceServer
的支持。
当使用 @RestClientTest
注解时,常规的 @Component
和 @ConfigurationProperties
Bean不会被扫描。
@EnableConfigurationProperties
可以用来包括 @ConfigurationProperties
Bean。
由 @RestClientTest 启用的自动配置设置的列表可以在在附录中找到。
|
你想测试的特定Bean应该通过使用 @RestClientTest
的 value
或 components
属性来指定,如以下例子所示。
@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {
@Autowired
private RemoteVehicleDetailsService service;
@Autowired
private MockRestServiceServer server;
@Test
void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
@RestClientTest(RemoteVehicleDetailsService::class)
class MyRestClientTests(
@Autowired val service: RemoteVehicleDetailsService,
@Autowired val server: MockRestServiceServer) {
@Test
fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails(): Unit {
server.expect(MockRestRequestMatchers.requestTo("/greet/details"))
.andRespond(MockRestResponseCreators.withSuccess("hello", MediaType.TEXT_PLAIN))
val greeting = service.callRestService()
assertThat(greeting).isEqualTo("hello")
}
}
8.3.30. Spring REST Docs 测试的自动配置
你可以使用 @AutoConfigureRestDocs
注解,在你的测试中使用 Spring REST Docs 与Mock MVC、REST Assured或WebTestClient。
它消除了Spring REST Docs中对JUnit扩展的需求。
@AutoConfigureRestDocs
可用于覆盖默认输出目录(如果使用Maven,则为 target/generated-snippets
,如果使用Gradle,则为 build/generated-snippets
)。
它还可以用来配置出现在任何文档化URI中的主机、scheme和端口。
使用 Mock MVC 的 Spring REST Docs 测试的自动配置
@AutoConfigureRestDocs
定制了 MockMvc
Bean,以便在测试基于servlet的Web应用程序时使用Spring REST Docs。
你可以通过使用 @Autowired
来注入它,并在你的测试中使用它,就像你在使用Mock MVC和Spring REST Docs时通常所做的那样,如以下例子所示。
@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"));
}
}
@WebMvcTest(UserController::class)
@AutoConfigureRestDocs
class MyUserDocumentationTests(@Autowired val mvc: MockMvc) {
@Test
fun listUsers() {
mvc.perform(MockMvcRequestBuilders.get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk)
.andDo(MockMvcRestDocumentation.document("list-users"))
}
}
如果你需要对Spring REST Docs的配置进行更多的控制,而不是由 @AutoConfigureRestDocs
的属性提供,你可以使用 RestDocsMockMvcConfigurationCustomizer
bean,如下例所示。
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {
@Override
public void customize(MockMvcRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsMockMvcConfigurationCustomizer {
override fun customize(configurer: MockMvcRestDocumentationConfigurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
}
}
如果你想利用Spring REST Docs对参数化输出目录的支持,你可以创建一个 RestDocumentationResultHandler
bean。
自动配置用这个 result handler 调用 alwaysDo
,从而使每个 MockMvc
调用自动生成默认的片段(snippet)。
下面的例子显示了一个 RestDocumentationResultHandler
被定义。
@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {
@Bean
public RestDocumentationResultHandler restDocumentation() {
return MockMvcRestDocumentation.document("{method-name}");
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyResultHandlerConfiguration {
@Bean
fun restDocumentation(): RestDocumentationResultHandler {
return MockMvcRestDocumentation.document("{method-name}")
}
}
使用 WebTestClient 的 Spring REST Docs 测试的自动配置
@AutoConfigureRestDocs
也可以在测试响应式Web应用时与 WebTestClient
一起使用。
你可以通过使用 @Autowired
来注入它,并在你的测试中使用它,就像你在使用 @WebFluxTest
和Spring REST Docs时通常使用的那样,如下例所示。
@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {
@Autowired
private WebTestClient webTestClient;
@Test
void listUsers() {
this.webTestClient
.get().uri("/")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith(document("list-users"));
}
}
@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests(@Autowired val webTestClient: WebTestClient) {
@Test
fun listUsers() {
webTestClient
.get().uri("/")
.exchange()
.expectStatus()
.isOk
.expectBody()
.consumeWith(WebTestClientRestDocumentation.document("list-users"))
}
}
如果你需要对Spring REST Docs的配置进行更多的控制,而不是由 @AutoConfigureRestDocs
的属性提供,你可以使用 RestDocsWebTestClientConfigurationCustomizer
bean,如下例所示。
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer {
@Override
public void customize(WebTestClientRestDocumentationConfigurer configurer) {
configurer.snippets().withEncoding("UTF-8");
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsWebTestClientConfigurationCustomizer {
override fun customize(configurer: WebTestClientRestDocumentationConfigurer) {
configurer.snippets().withEncoding("UTF-8")
}
}
如果你想利用Spring REST Docs对参数化输出目录的支持,你可以使用 WebTestClientBuilderCustomizer
来为每个实体exchange result配置一个consumer。
下面的例子显示了这样一个 WebTestClientBuilderCustomizer
的定义。
@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {
@Bean
public WebTestClientBuilderCustomizer restDocumentation() {
return (builder) -> builder.entityExchangeResultConsumer(document("{method-name}"));
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyWebTestClientBuilderCustomizerConfiguration {
@Bean
fun restDocumentation(): WebTestClientBuilderCustomizer {
return WebTestClientBuilderCustomizer { builder: WebTestClient.Builder ->
builder.entityExchangeResultConsumer(
WebTestClientRestDocumentation.document("{method-name}")
)
}
}
}
使用 Assured 的 Spring REST Docs 测试的自动配置
@AutoConfigureRestDocs
使一个 RequestSpecification
bean,预先配置为使用Spring REST Docs,可用于你的测试。
你可以通过使用 @Autowired
来注入它,并在你的测试中使用它,就像你在使用REST Assured和Spring REST Docs时一样,如以下例子所示。
@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));
}
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {
@Test
fun listUsers(@Autowired documentationSpec: RequestSpecification?, @LocalServerPort port: Int) {
RestAssured.given(documentationSpec)
.filter(RestAssuredRestDocumentation.document("list-users"))
.`when`()
.port(port)["/"]
.then().assertThat()
.statusCode(Matchers.`is`(200))
}
}
如果你需要对Spring REST Docs的配置进行更多的控制,而不是由 @AutoConfigureRestDocs
的属性提供,可以使用 RestDocsRestAssuredConfigurationCustomizer
bean,如以下例子所示。
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer {
@Override
public void customize(RestAssuredRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsRestAssuredConfigurationCustomizer {
override fun customize(configurer: RestAssuredRestDocumentationConfigurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
}
}
8.3.31. Spring Web Services 测试的自动配置
Spring Web Services Client 测试的自动配置
你可以使用 @WebServiceClientTest
来测试使用Spring Web Service 项目调用Web Service 的应用程序。
默认情况下,它配置了一个模拟的 WebServiceServer
bean,并自动定制了你的 WebServiceTemplateBuilder
。
(关于使用Spring Boot的Web Service 的更多信息,请参见"io.html")。
由 @WebServiceClientTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了 @WebServiceClientTest
注解的使用。
@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);
}
}
@WebServiceClientTest(SomeWebService::class)
class MyWebServiceClientTests(@Autowired val server: MockWebServiceServer, @Autowired val someWebService: SomeWebService) {
@Test
fun mockServerCall() {
server
.expect(RequestMatchers.payload(StringSource("<request/>")))
.andRespond(ResponseCreators.withPayload(StringSource("<response><status>200</status></response>")))
assertThat(this.someWebService.test()).extracting(Response::status).isEqualTo(200)
}
}
Spring Web Service Server 测试的自动配置
你可以使用 @WebServiceServerTest
来测试使用Spring Web Service项目实现Web Service的应用程序。
默认情况下,它配置了一个 MockWebServiceClient
bean,可以用来调用你的Web Service端点。
(关于使用Spring Boot的Web服务的更多信息,请参见"io.html")。
由 @WebServiceServerTest 启用的自动配置设置的列表可以在在附录中找到。
|
下面的例子显示了 @WebServiceServerTest
注解的使用。
@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>")));
}
}
@WebServiceServerTest(ExampleEndpoint::class)
class MyWebServiceServerTests(@Autowired val client: MockWebServiceClient) {
@Test
fun mockServerCall() {
client
.sendRequest(RequestCreators.withPayload(StringSource("<ExampleRequest/>")))
.andExpect(ResponseMatchers.payload(StringSource("<ExampleResponse>42</ExampleResponse>")))
}
}
8.3.32. 额外的自动配置和分片(slice)
每个片断(slice)提供一个或多个 @AutoConfigure…
注解,即定义自动配置,应该作为片断的一部分。
通过创建一个自定义的 @AutoConfigure…
注解或在测试中添加 @ImportAutoConfiguration
,可以在每个测试的基础上添加额外的自动配置,如下例所示。
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests {
}
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration::class)
class MyJdbcTests
请确保不要使用常规的 @Import 注解来导入自动配置,因为Spring Boot会以特定的方式处理这些配置。
|
或者,可以为切片(slice)注解的任何使用添加额外的自动配置,方法是将它们注册到存储在 META-INF/spring
中的文件中,如以下示例所示:
com.example.IntegrationAutoConfiguration
在这个例子中,com.example.IntegrationAutoConfiguration
在每个用 @JdbcTest
注解的测试中都被启用。
你可以在这个文件中使用带有 # 的注释。
|
只要用 @ImportAutoConfiguration 进行元注解,切片或 @AutoConfigure… 注解就可以用这种方式定制。
|
8.3.33. 用户配置和分片(Slice)
因此,不要在应用程序的 main 类中加入针对其功能的特定领域的配置设置,这一点变得很重要。
假设你正在使用Spring Data MongoDB,你依赖它的自动配置,并且你已经启用了审计。你可以这样定义你的 @SpringBootApplication
:
@SpringBootApplication
@EnableMongoAuditing
public class MyApplication {
// ...
}
@SpringBootApplication
@EnableMongoAuditing
class MyApplication {
// ...
}
因为这个类是测试的源配置,任何切片测试实际上都试图启用Mongo审计,这绝对不是你想做的。
推荐的方法是将特定区域的配置移到与你的应用程序相同级别的单独的 @Configuration
类中,如下面的例子所示。
@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
public class MyMongoConfiguration {
// ...
}
@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
class MyMongoConfiguration {
// ...
}
根据你的应用程序的复杂性,你可以有一个单一的 @Configuration 类用于你的定制,或者每个领域区域有一个类。
后一种方法可以让你在测试中启用它,如果有必要的话,用 @Import 注解。
关于何时你可能想为切片测试启用特定的 @Configuration 类,请看这个 "how-to"部分,了解更多细节。
|
测试片(Test slice)从扫描中排除了 @Configuration
类 。
例如,对于 @WebMvcTest
,以下配置将不包括测试片加载的应用程序上下文中的给定 WebMvcConfigurer
Bean。
@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {
@Bean
public WebMvcConfigurer testConfigurer() {
return new WebMvcConfigurer() {
// ...
};
}
}
@Configuration(proxyBeanMethods = false)
class MyWebConfiguration {
@Bean
fun testConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
// ...
}
}
}
然而,下面的配置将导致自定义的 WebMvcConfigurer
被测试片加载。
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
// ...
}
@Component
class MyWebMvcConfigurer : WebMvcConfigurer {
// ...
}
另一个迷惑的来源是classpath扫描。 假设在你以合理的方式结构化你的代码时,你需要扫描一个额外的包。 你的应用程序可能类似于下面的代码。
@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {
// ...
}
@SpringBootApplication
@ComponentScan("com.example.app", "com.example.another")
class MyApplication {
// ...
}
这样做有效地覆盖了默认的组件扫描指令,其副作用是扫描这两个包,而不管你选择的是哪一个片断。
例如,一个 @DataJpaTest
似乎突然扫描了你的应用程序的组件和用户配置。
同样,将自定义指令移到一个单独的类中是解决这个问题的好方法。
如果这对你来说不是一个选择,你可以在你的测试层次结构的某个地方创建一个 @SpringBootConfiguration ,这样它就会被使用。
或者,你可以为你的测试指定一个源,这将禁用寻找默认源的行为。
|
8.3.34. 使用Spock来测试Spring Boot应用程序
Spock 2.2或更高版本可用于测试Spring Boot应用程序。要做到这一点,请在你的应用程序的构建中添加对Spock的 spock-spring
模块的 -groovy-4.0
版本的依赖。spock-spring
将Spring的测试框架集成到Spock中。更多细节请参见 Spock的Spring模块文档。
8.4. Testcontainers
Testcontainers 库提供了一种管理Docker容器内运行的服务的方法。它与JUnit集成,允许你写一个测试类,可以在任何测试运行之前启动容器。Testcontainers对于编写与MySQL、MongoDB、Cassandra等真实后端服务对话的集成测试特别有用。
Testcontainers 可以在Spring Boot测试中使用,如下所示:
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
}
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
val neo4j = Neo4jContainer("neo4j:5")
}
}
这将在运行任何测试之前启动一个运行Neo4j的docker容器(如果Docker在本地运行)。在大多数情况下,你需要配置应用程序以连接到容器中运行的服务。
8.4.1. 服务连接
服务连接是与任何远程服务的连接。Spring Boot的自动配置可以消费服务连接的细节,并使用它们来建立与远程服务的连接。这样做的时候,连接细节(connection details)优先于任何与连接相关的配置属性。
当使用Testcontainers时,通过注解测试类中的容器字段,可以为运行在容器中的服务自动创建连接细节。
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
@ServiceConnection
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
}
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
@ServiceConnection
val neo4j = Neo4jContainer("neo4j:5")
}
}
由于 @ServiceConnection
,上述配置允许应用程序中与Neo4j相关的Bean与Testcontainers管理的Docker容器内运行的Neo4j进行通信。这是通过自动定义一个 Neo4jConnectionDetails
Bean 来实现的,然后Neo4j的自动配置就会使用这个bean,覆盖任何与连接相关的配置属性。
你需要添加 spring-boot-testcontainers 模块作为测试依赖,以便使用 Testcontainers 的服务连接。
|
服务连接注解由在 spring.factories
中注册的 ContainerConnectionDetailsFactory
类处理。 ContainerConnectionDetailsFactory
可以根据特定的 Container
子类或Docker镜像名称创建 ConnectionDetails
Bean。
下列服务连接工厂在 spring-boot-testcontainers
jar中提供:
Connection Details | 匹配的是 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
命名为 "redis" 的容器。 |
|
命名为 "openzipkin/zipkin" 的容器。 |
默认情况下,所有适用的连接细节 ban 将为一个给定的 如果你想只创建一个适用类型的子集,你可以使用 |
默认情况下, Container.getDockerImageName()
被用来获取用于查找连接细节的名称。如果你使用一个自定义的docker镜像,你可以使用 @ServiceConnection
的 name
属性来覆盖它。
例如,如果你有一个 GenericContainer
,使用 registry.mycompany.com/mirror/myredis
的Docker镜像,你会使用 @ServiceConnection(name="redis")
来确保 RedisConnectionDetails
被创建。
8.4.2. 动态 Properties
与服务连接相比,@DynamicPropertySource
是一个稍显啰嗦但也更灵活的选择。一个静态的 @DynamicPropertySource
方法允许向Spring环境添加动态属性值。
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
}
}
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
val neo4j = Neo4jContainer("neo4j:5")
@DynamicPropertySource
fun neo4jProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.neo4j.uri") { neo4j.boltUrl }
}
}
}
上述配置允许应用程序中与Neo4j相关的Bean与运行在Testcontainers管理的Docker容器内的Neo4j进行通信。
8.4.3. 在开发时使用 Testcontainers
除了使用Testcontainers进行集成测试外,也可以在开发时使用它们。这种方法允许开发人员快速启动应用程序所依赖的服务的容器,而不需要手动配置数据库服务器等东西。以这种方式使用Testcontainers提供了类似于Docker Compose的功能,只是你的容器配置是用Java而不是YAML。
要在开发时使用Testcontainers,你需要使用 “test” classpath而不是 "main" 启动你的应用程序。这将允许你访问所有声明的测试依赖,并给你一个自然的地方来写你的测试配置。
为了创建你的应用程序的可测试启动版本,你应该在 src/test
目录中创建一个 “Application” 类。例如,如果你的主程序在 src/main/java/com/example/MyApplication.java
中,你应该创建 src/test/java/com/example/TestMyApplication.java
。
TestMyApplication
类可以使用 SpringApplication.from(…)
方法来启动真实的应用程序:
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main).run(args);
}
}
fun main(args: Array<String>) {
fromApplication<MyApplication>().run(*args)
}
你还需要定义你想和你的应用程序一起启动的容器实例。要做到这一点,你需要确保 spring-boot-testcontainers
模块已被添加为 test
依赖。一旦这样做了,你就可以创建一个 @TestConfiguration
类,为你想启动的容器声明 @Bean
方法。
你也可以用 @ServiceConnection
来注解你的 @Bean
方法,以便创建 ConnectionDetails
Bean。关于支持的技术的细节,请看上面的 服务连接 部分。
一个典型的 Testcontainers 配置会是这样的:
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
@Bean
@ServiceConnection
public Neo4jContainer<?> neo4jContainer() {
return new Neo4jContainer<>("neo4j:5");
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
@ServiceConnection
fun neo4jContainer(): Neo4jContainer<*> {
return Neo4jContainer("neo4j:5")
}
}
Container bean 的生命周期由Spring Boot自动管理。容器将被自动启动和停止。
|
一旦你定义了你的测试配置,你可以使用 with(…)
方法将其附加到你的测试启动器上:
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args);
}
}
fun main(args: Array<String>) {
fromApplication<MyApplication>().with(MyContainersConfiguration::class).run(*args)
}
现在你可以像启动任何普通的 Java main
方法应用程序一样启动 TestMyApplication
,以启动你的应用程序和它需要运行的容器。
你可以使用 Maven goal spring-boot:test-run 或 Gradle 任务 bootTestRun 从命令行完成这一工作。
|
开发时的贡献动态属性
如果你想在开发时从你的 Container
@Bean
方法中贡献动态属性,你可以通过注入一个 DynamicPropertyRegistry
来实现。这与你可以在测试中使用的 @DynamicPropertySource
注解 的工作方式类似。它允许你添加那些一旦你的容器启动就会可用的属性。
一个典型的配置是这样的:
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
@Bean
public MongoDBContainer monogDbContainer(DynamicPropertyRegistry properties) {
MongoDBContainer container = new MongoDBContainer("mongo:5.0");
properties.add("spring.data.mongodb.host", container::getHost);
properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
return container;
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
fun monogDbContainer(properties: DynamicPropertyRegistry): MongoDBContainer {
var container = MongoDBContainer("mongo:5.0")
properties.add("spring.data.mongodb.host", container::getHost);
properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
return container
}
}
建议尽可能使用 @ServiceConnection ,然而,对于还没有 @ServiceConnection 支持的技术,动态属性可以是一个有用的退路。
|
导入 Testcontainer 声明类
在使用 Testcontainers 时,一个常见的模式是将 Container
实例声明为静态字段。通常这些字段是直接定义在测试类上的。它们也可以在父类或测试实现的接口上声明。
例如,下面的 MyContainers
接口声明了 mongo
和 neo4j
容器:
public interface MyContainers {
@Container
MongoDBContainer mongoContainer = new MongoDBContainer("mongo:5.0");
@Container
Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5");
}
如果你已经有了以这种方式定义的容器,或者你只是喜欢这种风格,你可以导入这些声明类,而不是把你的容器定义为 @Bean
方法。要做到这一点,在你的测试配置类中添加 @ImportTestcontainers
注解:
@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers.class)
public class MyContainersConfiguration {
}
@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers::class)
class MyContainersConfiguration {
}
你可以在 Container 字段上使用 @ServiceConnection 注解来建立服务连接。你也可以在你的声明类中添加 @DynamicPropertySource 注解的方法。
|
在开发时使用DevTools与Testcontainers
在使用 devtools 时,你可以用 @RestartScope
来注解Bean和Bean方法。当devtools重新启动应用程序时,这些Bean不会被重新创建。这对 Testcontainer Container
Bean特别有用,因为它们在应用程序重启时仍能保持其状态。
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
@Bean
@RestartScope
public MongoDBContainer monogDbContainer() {
return new MongoDBContainer("mongo:5.0");
}
}
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
@RestartScope
fun monogDbContainer(): MongoDBContainer {
return MongoDBContainer("mongo:5.0")
}
}
如果你使用Gradle并想使用这个功能,你需要将 spring-boot-devtools 依赖的配置从 developmentOnly 改为 testImplementation 。在默认的 developmentOnly scope 内,bootTestRun 任务不会接收到你代码中的变化,因为 devtools 没有被激活。
|
8.5. 测试工具
一些在测试你的应用程序时通常有用的测试工具类被打包成 spring-boot
的一部分。
8.5.1. ConfigDataApplicationContextInitializer
ConfigDataApplicationContextInitializer
是一个 ApplicationContextInitializer
,你可以应用于你的测试来加载Spring Boot application.properties
文件。
当你不需要 @SpringBootTest
提供的全套功能时,你可以使用它,如下面的例子所示。
@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {
// ...
}
@ContextConfiguration(classes = [Config::class], initializers = [ConfigDataApplicationContextInitializer::class])
class MyConfigFileTests {
// ...
}
单独使用 ConfigDataApplicationContextInitializer 并不提供对 @Value("${…}") 的注入支持。
它唯一的工作是确保 application.properties 文件被加载到Spring的 Environment 。
对于 @Value 支持,你需要额外配置一个 PropertySourcesPlaceholderConfigurer 或使用 @SpringBootTest ,它可以为你自动配置一个。
|
8.5.2. TestPropertyValues
TestPropertyValues
让你快速添加属性到 ConfigurableEnvironment
或 ConfigurableApplicationContext
。
你可以用 key=value
字符串来调用它,如下所示。
class MyEnvironmentTests {
@Test
void testPropertySources() {
MockEnvironment environment = new MockEnvironment();
TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
assertThat(environment.getProperty("name")).isEqualTo("Boot");
}
}
class MyEnvironmentTests {
@Test
fun testPropertySources() {
val environment = MockEnvironment()
TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment)
assertThat(environment.getProperty("name")).isEqualTo("Boot")
}
}
8.5.3. OutputCapture
OutputCapture
是一个JUnit的 Extension
(扩展),你可以用来捕获 System.out
和 System.err
输出。
要使用它,请添加 @ExtendWith(OutputCaptureExtension.class)
并将 CapturedOutput
作为参数注入你的测试类构造函数或测试方法,如下所示。
@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {
@Test
void testName(CapturedOutput output) {
System.out.println("Hello World!");
assertThat(output).contains("World");
}
}
@ExtendWith(OutputCaptureExtension::class)
class MyOutputCaptureTests {
@Test
fun testName(output: CapturedOutput?) {
println("Hello World!")
assertThat(output).contains("World")
}
}
8.5.4. TestRestTemplate
TestRestTemplate
是Spring的 RestTemplate
的便利替代品,在集成测试中很有用。
你可以得到一个虚无缥缈的template,或者一个发送Basic HTTP认证(有用户名和密码)的template。
在这两种情况下,template都是容错的。
这意味着它的行为对测试有利,不会在4xx和5xx错误中抛出异常。
相反,这种错误可以通过返回的 ResponseEntity
和它的状态代码来检测。
Spring Framework 5.0提供了一个新的 WebTestClient ,可用于WebFlux集成测试以及WebFlux和MVC端到端测试。
它为断言提供了fluent风格的API,与 TestRestTemplate 不同。
|
建议使用Apache HTTP客户端(版本5.1或更高),但不是必须的。
如果你的classpath上有这个客户端,TestRestTemplate
会通过适当配置客户端来响应。
如果你使用Apache的HTTP客户端,一些额外的测试友好功能将被启用。
-
重定向不被跟踪(所以你可以断言响应的位置)。
-
Cookies被忽略(所以template是无状态的)。
TestRestTemplate
可以在你的集成测试中直接实例化,如下面的例子所示。
class MyTests {
private final TestRestTemplate template = new TestRestTemplate();
@Test
void testRequest() {
ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
}
}
class MyTests {
private val template = TestRestTemplate()
@Test
fun testRequest() {
val headers = template.getForEntity("https://myhost.example.com/example", String::class.java)
assertThat(headers.headers.location).hasHost("other.example.com")
}
}
另外,如果你使用 @SpringBootTest
注解与 WebEnvironment.RANDOM_PORT
或 WebEnvironment.DEFINED_PORT
,你可以注入一个完整配置的 TestRestTemplate
并开始使用它。
如果有必要,可以通过 RestTemplateBuilder
Bean 应用额外的定制功能。
任何没有指定主机和端口的URL都会自动连接到嵌入式服务器,如以下例子所示。
@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));
}
}
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests(@Autowired val template: TestRestTemplate) {
@Test
fun testRequest() {
val headers = template.getForEntity("/example", String::class.java).headers
assertThat(headers.location).hasHost("other.example.com")
}
@TestConfiguration(proxyBeanMethods = false)
internal class RestTemplateBuilderConfiguration {
@Bean
fun restTemplateBuilder(): RestTemplateBuilder {
return RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
.setReadTimeout(Duration.ofSeconds(1))
}
}
}
9. Docker Compose 的支持
Docker Compose是一种流行的技术,可以用来定义和管理你的应用程序所需的多个服务容器。通常在你的应用程序旁边创建一个 compose.yml
文件,它定义和配置服务容器。
使用 Docker Compose 的典型工作流程是运行 docker compose up
,用它连接启动的服务来处理你的应用程序,然后在完成后运行 docker compose down
。
sring-boot-docker-compose
模块可以包含在项目中,为使用 Docker Compose 的容器工作提供支持。将该模块的依赖添加到你的构建中,如下面Maven和Gradle的列表所示:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
dependencies {
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}
当这个模块作为依赖被包含时,Spring Boot 将做以下工作:
-
在你的应用程序目录中搜索
compose.yml
和其他常见的 compose 文件名 -
用发现的
compose.yml
来调用docker compose up
。 -
为每个支持的容器创建服务连接bean。
-
当应用程序关闭时调用
docker compose stop
。
docker compose 或 docker-compose CLI应用程序需要在你的路径上,以便Spring Boot 的支持能够正常工作。
|
9.1. 服务连接
服务连接是与任何远程服务的连接。Spring Boot的自动配置可以消费服务连接的细节,并使用它们来建立与远程服务的连接。这样做的时候,连接细节优先于任何与连接相关的配置属性。
当使用Spring Boot的Docker Compose支持时,服务连接被建立到容器映射的端口。
Docker compose 的使用方式通常是将容器内的端口映射到你电脑上的短暂端口。例如,一个Postgres服务器可能在容器内使用5432端口运行,但在本地被映射到一个完全不同的端口。服务连接将始终发现并使用本地映射的端口。 |
服务连接是通过使用容器的镜像名称建立的。目前支持以下服务连接:
Connection Details | 匹配于 |
---|---|
|
命名为 "cassandra" 的容器。 |
|
命名为 "elasticsearch" 的容器。 |
|
命名为 "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql" 或 "postgres" 的容器。 |
|
命名为 "mongo" 的容器。 |
|
命名为 "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql" 或 "postgres" 的容器。 |
|
命名为 "rabbitmq" 的容器。 |
|
命名为 "redis" 的容器。 |
|
命名为 "openzipkin/zipkin" 的容器。 |
9.2. 自定义镜像
有时你可能需要使用你自己版本的镜像来提供服务。你可以使用任何自定义镜像,只要它的行为方式与标准镜像相同。具体来说,标准镜像支持的任何环境变量也必须在你的自定义镜像中使用。
如果你的镜像使用不同的名称,你可以在 compose.yml
文件中使用一个标签,这样Spring Boot就可以提供一个服务连接。使用一个名为 org.springframework.boot.service-connection
的标签来提供服务名称。
例如:
services:
redis:
image: 'mycompany/mycustomredis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.service-connection: redis
9.3. 跳过特定的容器
如果你在 compose.yml
中定义了一个不希望连接到你的应用程序的容器镜像,你可以使用标签来忽略它。任何带有 org.springframework.boot.ignore
标签的容器都会被 Spring Boot 忽略。
例如:
services:
redis:
image: 'redis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.ignore: true
9.4. 使用特定的 compose 文件
如果你的编译文件与你的应用程序不在同一个目录下,或者它的名字不同,你可以在 application.properties
或 application.yaml
中使用 spring.docker.compose.file
来指向一个不同的文件。Properties 可以被定义为准确的路径或与你的应用程序相对的路径。
例如:
spring.docker.compose.file=../my-compose.yml
spring:
docker:
compose:
file: "../my-compose.yml"
9.5. 等待 Container 准备就绪
由 Docker Compose 启动的容器可能需要一些时间才能完全就绪。检查准备情况的推荐方法是在 compose.yml
文件的服务定义下添加 healthcheck
部分。
由于 healthcheck
配置在 compose.yml
文件中被省略的情况并不少见,Spring Boot也会直接检查服务的就绪情况。默认情况下,当TCP/IP连接可以建立到其映射的端口时,容器被认为是准备好了。
你可以通过在 compose.yml
文件中添加 org.springframework.boot.readiness-check.tcp.disable
标签,在每个容器的基础上禁用这个。
例如:
services:
redis:
image: 'redis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.readiness-check.tcp.disable: true
你也可以在你的 application.properties
或 application.yaml
文件中改变超时值:
spring.docker.compose.readiness.tcp.connect-timeout=10s
spring.docker.compose.readiness.tcp.read-timeout=5s
spring:
docker:
compose:
readiness:
tcp:
connect-timeout: 10s
read-timeout: 5s
整体超时可以使用 spring.docker.compose.readiness.timeout
进行配置。
9.6. 控制 Docker Compose 的生命周期
默认情况下,Spring Boot 在你的应用程序启动时调用 docker compose up
,在关闭时调用 docker compose stop
。如果你喜欢不同的生命周期管理,你可以使用 spring.docker.compose.lifecycle-management
属性。
支持以下值:
-
none
- 不要启动或停止Docker Compose -
start-only
- 在应用程序启动时启动Docker Compose,并让其运行。 -
start-and-stop
- 在应用程序启动时启动Docker Compose,在JVM退出时停止它
此外,你可以使用 spring.docker.compose.start.command
属性来改变是使用 docker compose up
还是 docker compose start
。 spring.docker.compose.stop.command
允许你配置是否使用 docker compose down
或 docker compose stop
。
下面的例子显示了如何配置生命周期管理:
spring.docker.compose.lifecycle-management=start-and-stop
spring.docker.compose.start.command=start
spring.docker.compose.stop.command=down
spring.docker.compose.stop.timeout=1m
spring:
docker:
compose:
lifecycle-management: start-and-stop
start:
command: start
stop:
command: down
timeout: 1m
9.7. 激活Docker Compose配置文件
Docker Compose 配置文件与 Spring 配置文件类似,它们可以让你为特定环境调整Docker Compose 配置。如果你想激活一个特定的 Docker Compose 配置文件,你可以在 application.properties
或 application.yaml
文件中使用 spring.docker.compose.profiles.active
属性:
spring.docker.compose.profiles.active=myprofile
spring:
docker:
compose:
profiles:
active: "myprofile"
10. 创建你自己的自动配置
如果你在一家开发共享库的公司工作,或者你在一个开源或商业库上工作,你可能想开发自己的自动配置。 自动配置类可以被绑定在外部jar里,并且仍然可以被Spring Boot接收。
自动配置可以与一个 “starter” 相关联,该starter提供自动配置代码以及你将使用的典型库。 我们首先介绍建立你自己的自动配置所需的知识,然后再介绍创建自定义starter的典型步骤。
10.1. 理解自动配置Bean
实现自动配置的类用 @AutoConfiguration
来注解。
这个注解本身是用 @Configuration
进行元注解的,使自动配置成为标准的 @Configuration
类。
额外的 @Conditional
注解被用来限制自动配置应该何时应用。
通常,自动配置类使用 @ConditionalOnClass
和 @ConditionalOnMissingBean
注解。
这确保自动配置只在找到相关的类和你没有声明你自己的 @Configuration
时适用。
你可以浏览 spring-boot-autoconfigure
的源代码,看看Spring提供的 @AutoConfiguration
类(见 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件)。
10.2. 定位自动配置的候选对象(Candidates)
Spring Boot检查你发布的jar中是否存在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件。
该文件应该列出你的配置类,每行有一个类名,如下面的例子所示。
com.mycorp.libx.autoconfigure.LibXAutoConfiguration com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
你可以使用 # 字符在imports文件中添加注释。
|
自动配置必须只通过在imports文件中被命名来加载。
确保它们被定义在一个特定的包空间中,并且它们永远不会成为组件扫描的目标。
此外,自动配置类不应该使组件扫描找到额外的组件。
应该使用特定的 @Import 注解来代替。
|
如果你的配置需要以特定的顺序应用,你可以使用 @AutoConfiguration
注解上的 before
、beforeName
、after
和 afterName
属性或专用的 @AutoConfigureBefore
和 @AutoConfigureAfter
注解。
例如,如果你提供了web特定的配置,你的类可能需要在 WebMvcAutoConfiguration
之后应用。
如果你想对某些自动配置进行排序,它们之间不应该有任何直接的知识,你也可以使用 @AutoConfigureOrder
。
该注解与普通的 @Order
注解具有相同的语义,但为自动配置类提供了一个专门的顺序。
与标准的 @Configuration
类一样,自动配置类的应用顺序只影响其Bean定义的顺序。
随后创建这些Bean的顺序不受影响,由每个Bean的依赖关系和任何 @DependsOn
关系决定。
10.3. Condition 注解
你几乎总是想在你的自动配置类中包含一个或多个 @Conditional
注解。
@ConditionalOnMissingBean
注解是一个常见的例子,它被用来允许开发人员在对默认值不满意时覆盖自动配置。
Spring Boot包括许多 @Conditional
注解,你可以通过注解 @Configuration
类或单个 @Bean
方法在自己的代码中重用。
这些注解包括。
10.3.1. Class Conditions
@ConditionalOnClass
和 @ConditionalOnMissingClass
注解让 @Configuration
类基于特定类的存在或不存在而被包含。
由于注解元数据是通过使用 ASM 来解析的,你可以使用 value
属性来引用真正的类,即使该类可能没有实际出现在运行的应用程序classpath上。
如果你想通过使用 String
值来指定类的名称,你也可以使用 name
属性。
这种机制并不适用于 @Bean
方法,在这种情况下,返回类型是条件的目标:在方法的条件适用之前,JVM已经加载了类,并可能处理了方法引用,如果该类不存在,就会失败。
为了处理这种情况,可以使用一个单独的 @Configuration
类来隔离条件,如下面的例子所示。
@AutoConfiguration
// 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();
}
}
}
@Configuration(proxyBeanMethods = false)
// Some conditions ...
class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService::class)
class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
}
如果你使用 @ConditionalOnClass 或 @ConditionalOnMissingClass 作为元注解的一部分来组成你自己的组成注解,你必须使用 name ,因为在这种情况下引用类是无法处理的。
|
10.3.2. Bean Conditions
@ConditionalOnBean
和 @ConditionalOnMissingBean
注解让一个Bean根据特定Bean的存在或不存在而被包含。
你可以使用 value
属性按class指定Bean,或者使用 name
按名称指定Bean。
search
属性可以让你限制搜索Bean时应该考虑的 ApplicationContext
层次结构。
当放在一个 @Bean
方法上时,目标类型默认为该方法的返回类型,如下面的例子所示。
@AutoConfiguration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
@Configuration(proxyBeanMethods = false)
class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
在前面的例子中,如果 ApplicationContext
中没有 SomeService
类型的Bean,则将创建 SomeService
Bean。
你需要非常注意添加Bean定义的顺序,因为这些条件是根据到目前为止已经处理过的内容进行评估的。
出于这个原因,我们建议在自动配置类上只使用 @ConditionalOnBean 和 @ConditionalOnMissingBean 注解(因为这些注解可以保证在任何用户定义的Bean定义被添加后加载)。
|
@ConditionalOnBean 和 @ConditionalOnMissingBean 不会阻止 @Configuration 类被创建。
在类级别使用这些条件和用注解标记每个包含的 @Bean 方法之间的唯一区别是,如果条件不匹配,前者会阻止将 @Configuration 类注册为Bean。
|
当声明一个 @Bean 方法时,在方法的返回类型中提供尽可能多的类型信息。
例如,如果你的Bean的具体类实现了一个接口,那么Bean方法的返回类型应该是具体类而不是接口。
当使用Bean条件时,在 @Bean 方法中提供尽可能多的类型信息尤为重要,因为它们的评估只能依赖于方法签名中的类型信息。
|
10.3.3. Property Conditions
@ConditionalOnProperty
注解让配置基于Spring Environment property 被包含。
使用 prefix
和 name
属性来指定应该被检查的属性。
默认情况下,任何存在且不等于 false
的属性都被匹配。
你也可以通过使用 havingValue
和 matchIfMissing
属性创建更高级的检查。
10.3.4. Resource Conditions
@ConditionalOnResource
注解让配置仅在特定资源存在的情况下被包含。
资源可以通过使用通常的Spring约定来指定,如下面的例子所示: file:/home/user/test.dat
。
10.3.5. Web Application Condition
@ConditionalOnWebApplication
和 @ConditionalOnNotWebApplication
注解允许根据应用是否是 “web application” 来包含配置。
基于servlet的Web应用是任何使用Spring的 WebApplicationContext
、定义 session
scope或具有 ConfigurableWebEnvironment
的应用。
响应式Web应用是指任何使用 ReactiveWebApplicationContext
或拥有 ConfigurableReactiveWebEnvironment
的应用。
@ConditionalOnWarDeployment
和 @ConditionalOnNotWarDeployment
注解让 configuration 被包括在内,这取决于应用程序是否是被部署到servlet容器的传统WAR应用程序。这个 condition 将不符合使用嵌入式Web服务器运行的应用程序。
10.3.6. SpEL Expression Condition
@ConditionalOnExpression
注解允许根据 SpEL表达式的结果来包含配置。
在表达式中引用Bean将导致该Bean在上下文刷新处理中很早就被初始化。 因此,该Bean将没有资格进行后(post-processing)处理(比如配置属性绑定),其状态可能是不完整的。 |
10.4. 测试你的自动配置
一个自动配置可以受到许多因素的影响:用户配置(@Bean
定义和 Environment
定制),条件评估(特定库的存在),以及其他。
具体来说,每个测试应该创建一个定义良好的 ApplicationContext
,代表这些定制的组合。
ApplicationContextRunner
提供了一个很好的方法来实现这一点。
ApplicationContextRunner
通常被定义为测试类的一个字段,以收集基本的、通用的配置。
下面的例子确保了 MyServiceAutoConfiguration
总是被调用。
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
如果必须定义多个自动配置,则不需要对它们的声明进行排序,因为它们被调用的顺序与运行应用程序时完全相同。 |
每个测试都可以使用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");
}
}
@Test
fun defaultServiceBacksOff() {
contextRunner.withUserConfiguration(UserConfiguration::class.java)
.run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context).getBean("myCustomService")
.isSameAs(context.getBean(MyService::class.java))
}
}
@Configuration(proxyBeanMethods = false)
internal class UserConfiguration {
@Bean
fun myCustomService(): MyService {
return 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");
});
}
@Test
fun serviceNameCanBeConfigured() {
contextRunner.withPropertyValues("user.name=test123").run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context.getBean(MyService::class.java).name).isEqualTo("test123")
}
}
runner也可以用来显示 ConditionEvaluationReport
。
报告可以在 INFO
或 DEBUG
级别打印。
下面的例子显示了如何使用 ConditionEvaluationReportLoggingListener
来打印自动配置测试的报告。
class MyConditionEvaluationReportingTests {
@Test
void autoConfigTest() {
new ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
class MyConditionEvaluationReportingTests {
@Test
fun autoConfigTest() {
ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run { context: AssertableApplicationContext? -> }
}
}
10.4.1. 模拟web上下文(Context)
如果你需要测试一个只在Servlet或响应式Web应用上下文中操作的自动配置,请分别使用 WebApplicationContextRunner
或 ReactiveWebApplicationContextRunner
。
10.4.2. 覆盖Classpath
也可以测试当某个特定的class 和/或 package在运行时不存在时会发生什么。
Spring Boot提供了一个 FilteredClassLoader
,runner可以轻松使用。
在下面的例子中,我们断言,如果 MyService
不存在,自动配置就会被正确地禁用。
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
@Test
fun serviceIsIgnoredIfLibraryIsNotPresent() {
contextRunner.withClassLoader(FilteredClassLoader(MyService::class.java))
.run { context: AssertableApplicationContext? ->
assertThat(context).doesNotHaveBean("myService")
}
}
10.5. 创建你自己的 Starter
一个典型的Spring Boot starter包含自动配置和定制特定技术的基础设施的代码,我们称之为 "acme"。 为了使其易于扩展,可以将专用命名空间中的一些配置key暴露给环境。 最后,提供一个单一的 "starter" 依赖,以帮助用户尽可能容易地开始使用。
具体来说,一个自定义 starter 可以包含以下内容。
-
autoconfigure
模块包含 "acme" 的自动配置代码。 -
starter
模块提供了对autoconfigure
模块的依赖,以及 "acme" 和任何其他通常有用的依赖。 简而言之,添加starter应该提供开始使用该库所需的一切。
这种分离在两个模块中是没有必要的。
如果 "acme" 有几种口味、选项或可选功能,那么最好将自动配置分开,因为你可以清楚地表达一些功能是可选的事实。
此外,你有能力制作一个starter,提供关于那些可选依赖的意见。
同时,其他人可以只依靠 autoconfigure
模块,制作他们自己的starter,并提出不同的意见。
如果自动配置相对简单,没有可选功能,那么在starter中合并两个模块绝对是一个选择。
10.5.1. 命名
你应该确保为你的starter提供一个合适的命名空间。
不要用 spring-boot
开始你的模块名称,即使你使用不同的Maven groupId
。
我们可能会在未来为你自动配置的东西提供官方支持。
根据经验,你应该用starter的名字来命名一个组合模块。
例如,假设你正在为 "acme" 创建一个starter,你将自动配置模块命名为 acme-spring-boot
,starter命名为 acme-spring-boot-starter
。
如果你只有一个模块,将两者结合起来,就命名为 acme-spring-boot-starter
。
10.5.2. 配置KEY
如果你的starter提供了配置key,请为它们使用一个独特的命名空间。
特别是,不要把你的key放在Spring Boot使用的命名空间中(如 server
、management
、spring
等)。
如果你使用相同的命名空间,我们可能会在未来修改这些命名空间,从而破坏你的模块。
作为一个经验法则,用你自己的命名空间作为你所有键的前缀(例如 acme
)。
通过为每个属性添加字段javadoc,确保配置key被记录下来,如下面的例子中所示。
@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);
}
@ConfigurationProperties("acme")
class AcmeProperties(
/**
* Whether to check the location of acme resources.
*/
var isCheckLocation: Boolean = true,
/**
* Timeout for establishing a connection to the acme server.
*/
var loginTimeout:Duration = Duration.ofSeconds(3))
你应该只使用纯文本的 @ConfigurationProperties 字段Javadoc,因为它们在被添加到JSON之前没有被处理。
|
以下是我们内部遵循的一些规则,以确保描述的一致性。
-
不要以 "The" 或 "A" 作为描述的开头。
-
对于
boolean
类型,用 "Whether" 或 "Enable" 开始描述。 -
对于基于集合的类型,用 "逗号分隔的列表" 来开始描述。
-
使用
java.time.Duration
而不是long
,如果默认单位与毫秒不同,则描述它,例如 "如果没有指定持续时间的后缀,将使用秒"。 -
不要在描述中提供默认值,除非它必须在运行时确定。
请确保触发元数据的生成,以便IDE对你的key也能提供帮助。
你可能想查看生成的元数据(META-INF/spring-configuration-metadata.json
)以确保你的key被正确记录。
在一个兼容的IDE中使用你自己的starter,也是验证元数据质量的好主意。
10.5.3. “autoconfigure” 模块
autoconfigure
模块包含了开始使用该库所需的一切。
它还可能包含配置键的定义(如 @ConfigurationProperties
)和任何回调接口,可用于进一步定制组件的初始化方式。
你应该把对该库的依赖标记为可选(optional)的,这样你就可以更容易地在你的项目中包括 autoconfigure 模块。
如果你这样做,该库就不会被提供,而且在默认情况下,Spring Boot会退缩。
|
Spring Boot使用注解处理器来收集元数据文件(META-INF/spring-autoconfigure-metadata.properties
)中关于自动配置的条件。
如果该文件存在,它被用来急切地过滤不匹配的自动配置,这将改善启动时间。
用Maven构建时,建议在包含自动配置的模块中添加以下依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
如果你在应用程序中直接定义了自动配置,请确保配置 spring-boot-maven-plugin
,以防止 repackage
goal 将依赖加入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中,应该在 annotationProcessor
配置中声明该依赖,如下面的例子所示。
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
10.5.4. Starter 模块(Module)
starter 实际上是一个空的jar。 它的唯一目的是提供必要的依赖,以便与该库一起工作。 你可以把它看作是对开始工作所需要的东西的一种意见性看法。
不要对加入你的starter的项目做假设。 如果你要自动配置的库通常需要其他starter,也要提到它们。 如果可选依赖项的数量较多,提供一套合适的 默认 依赖项可能会很困难,因为你应该避免包括那些对库的典型使用来说不必要的依赖项。 换句话说,你不应该包括可选的依赖关系。
无论哪种方式,你的starter都必须直接或间接地引用Spring Boot核心starter(spring-boot-starter )(如果你的starter依赖另一个starter,就没有必要添加它)。
如果一个项目只用你的自定义starter创建,Spring Boot的核心功能将因核心starter的存在而得到尊重。
|
11. 对Kotlin的支持
Spring Boot通过利用Spring Framework、Spring Data和Reactor等其他Spring项目中的支持来提供Kotlin支持。 更多信息请参见 Spring Framework Kotlin支持文档。
开始使用Spring Boot和Kotlin的最简单方法是遵循 这个综合教程。你可以通过使用 start.spring.io 创建新的Kotlin项目。如果你需要支持,请随时加入 Kotlin Slack 的 #spring channel,或在 Stack Overflow 上用spring和kotlin标签提问。
11.1. 要求
Spring Boot至少需要Kotlin 1.7.x,并通过依赖管理来管理合适的Kotlin版本。
要使用Kotlin,org.jetbrains.kotlin:kotlin-stdlib
和 org.jetbrains.kotlin:kotlin-reflect
必须出现在classpath上。
也可以使用 kotlin-stdlib`变种 `kotlin-stdlib-jdk7
和 kotlin-stdlib-jdk8
。
由于 Kotlin类默认是final的,你很可能想配置 kotlin-spring 插件,以便自动打开(开放)Spring 注解的类,使它们可以被代理。
Jackson的Kotlin模块 是在Kotlin中序列化/反序列化JSON数据所必需的。当在classpath上找到它时,它会被自动注册。如果Jackson和Kotlin都存在,但Jackson Kotlin模块不存在,则会记录一条警告信息。
如果在 start.spring.io 上启动一个Kotlin项目,这些依赖和插件是默认提供的。 |
11.2. 安全的Null
Kotlin的关键特性之一是 null-safety。它在编译时处理 null
,而不是将问题推迟到运行时并遇到 NullPointerException
。这有助于消除一个常见的bug来源,而无需承受像 Optional
这样的封装器带来的性能损耗。Kotlin还允许使用具有可忽略值的功能结构,正如这份关于 Kotlin中的无效安全的综合指南 中所描述的那样。
尽管Java不允许人们在其类型系统中表达null-safety,但Spring Framework、Spring Data和Reactor现在通过工具友好的注解提供其API的null-safety。 默认情况下,Kotlin中使用的Java API中的类型被识别为 platform types,其null值检查被放宽了。 Kotlin对JSR 305注解的支持 与nullability注解相结合,为Kotlin中的相关Spring API提供了null-safety。
JSR 305检查可以通过添加 -Xjsr305
编译器标志和以下选项进行配置。-Xjsr305={strict|warn|ignore}
。
默认行为与 -Xjsr305=warn
相同。
为了让从Spring API推断出的Kotlin类型考虑到null-safety,需要使用 strict
值,但在使用时要注意Spring API的nullability声明可能会发生变化,甚至在小版本之间,未来可能会添加更多检查。
11.3. Kotlin API
11.3.1. runApplication
Spring Boot提供了一种习惯性的方法来运行应用程序,即 runApplication<MyApplication>(*args)
,如下例所示。
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
这是 SpringApplication.run(MyApplication::class.java, *args)
的一个直接替换。
它还允许对应用程序进行定制,如下面的例子所示。
runApplication<MyApplication>(*args) {
setBannerMode(OFF)
}
11.3.2. 扩展
Kotlin扩展提供了用额外功能 扩展 现有类的能力。 Spring Boot Kotlin API利用这些扩展来为现有的API添加新的Kotlin特定便利。
提供了 TestRestTemplate
扩展,类似于Spring Framework为 RestOperations
所提供的扩展。
在其他方面,这些扩展使其有可能利用Kotlin的统一类型参数。
11.4. 依赖管理
为了避免在classpath上混合不同版本的Kotlin依赖项,Spring Boot导入了Kotlin BOM。
在Maven中,可以通过设置 kotlin.version
属性来定制Kotlin版本,并为 kotlin-maven-plugin
提供插件管理。
通过Gradle,Spring Boot插件会自动将 kotlin.version
与Kotlin插件的版本对齐。
Spring Boot还通过导入Kotlin Coroutines BOM来管理Coroutines的依赖版本。
可以通过设置 kotlin-coroutines.version
属性来定制版本。
如果一个Kotlin项目在 start.spring.io 上启动时至少有一个响应式依赖,那么 org.jetbrains.kotlinx:kotlinx-coroutines-reactor 依赖是默认提供的。
|
11.5. @ConfigurationProperties
@ConfigurationProperties
与构造器绑定结合使用时,支持具有不可变的 val
属性的类,如下例所示。
@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
val name: String,
val description: String,
val myService: MyService) {
data class MyService(
val apiToken: String,
val uri: URI
)
}
要使用注解处理器生成你自己的元数据,https://kotlinlang.org/docs/reference/kapt.html[kapt 应该配置] spring-boot-configuration-processor 依赖项。
请注意,由于kapt提供的模型的限制,一些功能(如检测默认值或废弃的项目)无法工作。
|
11.6. 测试
虽然可以使用JUnit 4来测试Kotlin代码,但JUnit 5是默认提供的,并被推荐。
JUnit 5使一个测试类能够被实例化一次,并在该类的所有测试中重复使用。
这使得在非静态方法上使用 @BeforeAll
和 @AfterAll
注解成为可能,这很适合Kotlin。
为了模拟Kotlin类,推荐使用 MockK。如果你需要相当于Mockito特定的@MockBean和@SpyBean注解的 MockK
,你可以使用 SpringMockK,它提供类似的 @MockkBean
和 @SpykBean
注解。
11.7. 资源(Resources)
11.7.1. 延伸阅读
-
Kotlin Slack (有一个专门的 #spring channel)
11.7.2. 例子
-
spring-boot-kotlin-demo: 常规的Spring Boot + Spring Data JPA项目
-
mixit: Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB
-
spring-kotlin-fullstack: WebFlux Kotlin全栈实例,前端使用Kotlin2js而不是JavaScript或TypeScript
-
spring-petclinic-kotlin: Kotlin版本的Spring PetClinic示例应用程序
-
spring-kotlin-deepdive: 从Boot 1.0 + Java到Boot 2.0 + Kotlin的一步步迁移。
-
spring-boot-coroutines-demo: Coroutines示例项目
12. SSL
Spring Boot提供了配置SSL信任材料(trust material)的能力,这些材料可以应用于几种类型的连接,以支持安全通信。前缀为 spring.ssl.bundle
的配置属性可用于指定命名的信任材料集和相关信息。
12.1. 用 Java KeyStore 文件配置 SSL
前缀为 spring.ssl.bundle.jks
的配置属性可用于配置用 Java keytool
工具创建的信任材料 bundle,并以 JKS 或 PKCS12 格式存储在 Java KeyStore 文件中。每个bundle都有一个用户提供的名称,可以用来引用bundle。
当用于保护嵌入式 Web 服务器时,keystore
通常被配置为包含证书和私钥的 Java KeyStore,如本例中所示:
spring.ssl.bundle.jks.mybundle.key.alias=application
spring.ssl.bundle.jks.mybundle.keystore.location=classpath:application.p12
spring.ssl.bundle.jks.mybundle.keystore.password=secret
spring.ssl.bundle.jks.mybundle.keystore.type=PKCS12
spring:
ssl:
bundle:
jks:
mybundle:
key:
alias: "application"
keystore:
location: "classpath:application.p12"
password: "secret"
type: "PKCS12"
当用来保证客户端连接的安全时,truststore
通常被配置为一个包含服务器证书的 Java KeyStore,如本例所示:
spring.ssl.bundle.jks.mybundle.truststore.location=classpath:server.p12
spring.ssl.bundle.jks.mybundle.truststore.password=secret
spring:
ssl:
bundle:
jks:
mybundle:
truststore:
location: "classpath:server.p12"
password: "secret"
参见 JksSslBundleProperties 以了解全部支持的属性。
12.2. 用 PEM 编码的证书配置SSL
前缀为 spring.ssl.bundle.pem
的配置属性可用于配置PEM编码文本形式的信任材料包。每个 bundle 都有一个用户提供的名称,可以用来引用该 bundle。
当用于保护嵌入式Web服务器时,keystore
通常配置有证书和私钥,如本例中所示:
spring.ssl.bundle.pem.mybundle.keystore.certificate=classpath:application.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=classpath:application.key
spring:
ssl:
bundle:
pem:
mybundle:
keystore:
certificate: "classpath:application.crt"
private-key: "classpath:application.key"
当用于保护客户端连接时,通常用服务器证书配置 truststore
,如本例中所示:
spring.ssl.bundle.pem.mybundle.truststore.certificate=classpath:server.crt
spring:
ssl:
bundle:
pem:
mybundle:
truststore:
certificate: "classpath:server.crt"
参见 PemSslBundleProperties 以了解全部支持的属性。
12.4. 使用 SSL Bundle
Spring Boot 自动配置了一个 SslBundles
类型的Bean,提供对使用 spring.ssl.bundle
属性配置的每个命名的 bundle 的访问。
SslBundle
可以从自动配置的 SslBundles
bean 中获取,并用于创建用于配置客户端库中 SSL 连接的对象。SslBundle
提供了一个获取这些SSL对象的分层方法:
-
getStores()
提供了对 key store 和 trust storejava.security.KeyStore
实例以及任何所需 key store 密码的访问。 -
getManagers()
提供了对java.net.ssl.KeyManagerFactory
和java.net.ssl.TrustManagerFactory
实例以及它们所创建的java.net.ssl.KeyManager
和java.net.ssl.TrustManager
数组的访问。 -
createSslContext()
提供了一个方便的方法来获得一个新的java.net.ssl.SSLContext
实例。
此外,SslBundle
还提供了关于正在使用的key、要使用的协议和任何应该应用于SSL引擎的选项的详细信息。
下面的例子显示了检索一个 SslBundle
并使用它来创建一个 SSLContext
:
@Component
public class MyComponent {
public MyComponent(SslBundles sslBundles) {
SslBundle sslBundle = sslBundles.getBundle("mybundle");
SSLContext sslContext = sslBundle.createSslContext();
// do something with the created sslContext
}
}
@Component
class MyComponent(sslBundles: SslBundles) {
init {
val sslBundle = sslBundles.getBundle("mybundle")
val sslContext = sslBundle.createSslContext()
// do something with the created sslContext
}
}
13. 接下来读什么
如果你想进一步了解本节所讨论的任何一个类,请看 Spring Boot API文档,或者你可以直接浏览 源代码。如果你有具体的问题,请看how-to部分。
如果你对Spring Boot的核心功能感到满意,你可以继续阅读有关生产就绪的功能。