本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

本节深入介绍了Spring Boot的细节。在这里,你可以了解到你可能想要使用和定制的关键功能。如果你还没有这样做,你可能想阅读 "入门" 和 "使用Spring Boot开发" 部分,这样你就有了良好的基础知识。

1. SpringApplication

通过 SpringApplication 类,你可以从 main() 方法中启动Spring应用程序。 在许多情况下,你可以直接调动 SpringApplication.run 静态方法,如以下例子所示。

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

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

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication


@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-infofalse 来关闭。 这也将关闭应用程序的激活的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的堆大小进行微调。

可以使用 SpringApplicationBuilderlazyInitialization 方法或 SpringApplicationsetLazyInitialization 方法以编程方式启用懒初始化。 另外,也可以使用 spring.main.lazy-initialization 属性来启用,如下例所示。

Properties
spring.main.lazy-initialization=true
Yaml
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,以及以下任何占位符。

Table 1. Banner variables
变量 介绍

${application.version}

你的应用程序的版本号,也就是 MANIFEST.MF 中声明的。 例如,Implementation-Version: 1.0 被打印为 1.0

${application.formatted-version}

你的应用程序的版本号,如在`MANIFEST.MF`中声明的那样,并以格式化显示(用括号包围,以 v 为前缀)。 例如 (v1.0)

${spring-boot.version}

你所使用的Spring Boot版本。 例如 3.2.0-SNAPSHOT

${spring-boot.formatted-version}

你正在使用的Spring Boot版本,格式化显示(用大括号包围并以 v 为前缀)。 例如 (v3.2.0-SNAPSHOT)

${Ansi.NAME} (或 ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME})

其中 NAME 是一个ANSI转义代码的名称。 详见 AnsiPropertySource

${application.title}

你的应用程序的标题,正如在 MANIFEST.MF 中声明的那样。 例如, Implementation-Title: MyApp 被打印成 MyApp

如果你想以编程方式生成一个Banner,可以使用 SpringApplication.setBanner(..) 方法。 实现 org.springframework.boot.Banner 接口并实现你自己的 printBanner() 方法。

你也可以使用 spring.main.banner-mode 属性来决定Beann打印模式。例如打印到 System.outconsole)上,发送到配置的logger(log),或者根本不打印(off)。

打印的Banner被注册为一个单例Bean,名字是:springBootBanner

${application.version}${application.formatted-version} 属性只有在你使用Spring Boot启动器时才可用。 如果你正在运行一个未打包的jar,并使用 java -cp <classpath> <mainclass> 启动它,这些值将不会被解析。

这就是为什么我们建议你总是使用 java org.springframework.boot.loader.JarLauncher 来启动未打包的jar。 这将在构建classpath和启动你的应用程序之前初始化 application.* 的Banner变量。

1.4. 自定义 SpringApplication

如果 SpringApplication 的默认值不符合你的需求,你可以创建一个实例并对其进行自定义。 例如,要关闭Banner,你可以这样写。

Java
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

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

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@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 允许你链式调用多个方法,包括调用 parentchild 方法,创建一个层次结构,如下例所示。

Java
new SpringApplicationBuilder().sources(Parent.class)
    .child(Application.class)
    .bannerMode(Banner.Mode.OFF)
    .run(args);
Kotlin
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” 告诉平台,它暂时不应该将流量发送到该应用程序。 这通常发生在启动期间,当 CommandLineRunnerApplicationRunner 组件还在被处理的时候,或者是应用程序觉得目前负载已经到了极限,不能再处理额外的请求的时候。

一旦 ApplicationRunnerCommandLineRunner 被调用完成,就认为应用程序已经准备好了,见Spring Boot应用程序生命周期和相关的Application Event

预计在启动期间运行的任务应该交由 CommandLineRunnerApplicationRunner 组件执行,而不是使用Spring组件的生命周期回调,如 @PostConstruct

1.6.3. 管理应用程序的可用性状态

应用组件可以在任何时候通过注入 ApplicationAvailability 接口并调用其上的方法来检索当前的可用性状态。 更多时候,应用程序会想要监听状态更新或更新应用程序的状态。

例如,我们可以将应用程序的 "Readiness" 状态导出到一个文件,这样Kubernetes的 "exec Probe" 就可以查看这个文件了。

Java
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class MyReadinessStateExporter {

    @EventListener
    public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
        switch (event.getState()) {
            case ACCEPTING_TRAFFIC:
                // create file /tmp/healthy
                break;
            case REFUSING_TRAFFIC:
                // remove file /tmp/healthy
                break;
        }
    }

}
Kotlin
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.ReadinessState
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component

@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 -> {
                // ...
            }
        }
    }

}

当应用程序中断而无法恢复时,我们还可以更新应用程序的状态。

Java
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class MyLocalCacheVerifier {

    private final ApplicationEventPublisher eventPublisher;

    public MyLocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void checkLocalCache() {
        try {
            // ...
        }
        catch (CacheCompletelyBrokenException ex) {
            AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
        }
    }

}
Kotlin
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.LivenessState
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component

@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 还会发布一些额外的应用事件。

有些事件实际上是在 ApplicationContext 被创建之前触发的,所以你不能以 @Bean 的形式注册一个监听器。 你可以通过 SpringApplication.addListeners(…​) 方法或 SpringApplicationBuilder.listeners(…​) 方法注册它们。

如果你想让这些监听器自动注册,不管应用程序是如何创建的,你可以在你的项目中添加一个 META-INF/spring.factories 文件,并通过 org.springframework.context.ApplicationListener 属性来配置你的监听器,如以下例子所示。

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

当应用程序运行时,Application event按以下顺序发布。

  1. 一个 ApplicationStartingEvent 在运行开始时被发布,但在任何处理之前,除了注册监听器和初始化器之外。

  2. 当在上下文中使用的 Environment 已知,但在创建上下文之前,将发布 ApplicationEnvironmentPreparedEvent

  3. ApplicationContext 已准备好并且 ApplicationContextInitializers 被调用,但在任何Bean定义被加载之前,ApplicationContextInitializedEvent 被发布。

  4. 一个 ApplicationPreparedEvent 将在刷新开始前但在Bean定义加载后被发布。

  5. 在上下文被刷新之后,但在任何应用程序和命令行运行程序被调用之前,将发布一个 ApplicationStartedEvent

  6. 紧接着发布 LivenessState.CORRECT 状态的 AvailabilityChangeEvent,表明应用程序被认为是存活的。

  7. 在任何ApplicationRunner 和 CommandLineRunner被调用后,将发布一个 ApplicationReadyEvent

  8. 紧接着发布 ReadinessState.ACCEPTING_TRAFFIC 状态的 AvailabilityChangeEvent,表明应用程序已经准备好为请求提供服务。

  9. 如果启动时出现异常,将发布一个 ApplicationFailedEvent

以上列表仅包括与 SpringApplication 相关的 SpringApplicationEvent。 除此以外,以下事件也会在 ApplicationPreparedEvent 之后和 ApplicationStartedEvent 之前发布。

  • WebServer 准备好后发布 WebServerInitializedEventServletWebServerInitializedEventReactiveWebServerInitializedEvent 分别对应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[] 参数以及经过解析的 optionnon-option 参数。如以下例子所示。

Java
import java.util.List;

import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();
        if (debug) {
            System.out.println(files);
        }
        // if run with "--debug logfile.txt" prints ["logfile.txt"]
    }

}
Kotlin
import org.springframework.boot.ApplicationArguments
import org.springframework.stereotype.Component

@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 启动后运行一些特定的代码,你可以实现 ApplicationRunnerCommandLineRunner 接口。 这两个接口以相同的方式工作,并提供一个单一的 run 方法,该方法在 SpringApplication.run(…​) 执行完毕之前被调用。

这很适合用于执行那些需要在处理HTTP请求之前执行的任务。

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

Java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        // Do something...
    }

}
Kotlin
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component

@Component
class MyCommandLineRunner : CommandLineRunner {

    override fun run(vararg args: String) {
        // Do something...
    }

}

如果定义了多个 CommandLineRunnerApplicationRunner 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() ,将其作为状态代码返回,如下面的例子所示。

Java
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApplication {

    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
        return () -> 42;
    }

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

}
Kotlin
import org.springframework.boot.ExitCodeGenerator
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

import kotlin.system.exitProcess

@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. 应用程序启动追踪

在应用程序启动期间,SpringApplicationApplicationContext 执行许多与应用程序生命周期相关的任务。 beans的生命周期,甚至是处理应用事件。 通过 ApplicationStartup, ,Spring框架 允许你用 StartupStep 对象来跟踪应用程序的启动顺序。 这些数据可以为分析目的而收集,或者只是为了更好地了解应用程序的启动过程。

你可以在设置 SpringApplication 实例时选择一个 ApplicationStartup 实现。 例如,要使用 BufferingApplicationStartup,你可以这么写。

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setApplicationStartup(new BufferingApplicationStartup(2048));
        application.run(args);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup
import org.springframework.boot.runApplication

@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 可以覆盖前面属性源中定义的值。 按以下顺序考虑。

  1. 默认属性(通过 SpringApplication.setDefaultProperties 指定)。

  2. @Configuration 类上的 @PropertySource 注解。请注意,这样的属性源直到application context被刷新时才会被添加到环境中。这对于配置某些属性来说已经太晚了,比如 logging.*spring.main.* ,它们在刷新开始前就已经被读取了。

  3. 配置数据(如 application.properties 文件)。

  4. RandomValuePropertySource,它只有 random.* 属性。

  5. 操作系统环境变量

  6. Java System properties (System.getProperties()).

  7. java:comp/env 中的 JNDI 属性。

  8. ServletContext init parameters.

  9. ServletConfig init parameters.

  10. 来自 SPRING_APPLICATION_JSON 的属性(嵌入环境变量或系统属性中的内联JSON)。

  11. 命令行参数

  12. 你在测试中的 properties 属性。在 @SpringBootTest 和测试注解中可用,用于测试你的应用程序的一个特定片断

  13. @DynamicPropertySource 注解在你的测试中。

  14. 你测试中的https://docs.spring.io/spring-framework/docs/6.1.0-M1/javadoc-api/org/springframework/test/context/TestPropertySource.html[@TestPropertySource] 注解.

  15. 当devtools处于活动状态时,$HOME/.config/spring-boot 目录下的Devtools全局设置属性

配置数据文件按以下顺序考虑。

  1. 在你的jar中打包的Application properties(application.properties 和 YAML)。

  2. 在你的jar中打包的 特定的 Profile application propertiesapplication-{profile}.properties 和 YAML)。

  3. 在你打包的jar之外的Application properties性(application.properties和YAML)。

  4. 在你打包的jar之外的特定的 Profile application propertiesapplication-{profile}.properties 和YAML)。

建议你在整个应用程序中坚持使用一种格式。如果你同时有 .properties 和YAML格式的配置文件,那么 .properties 优先。
如果你使用环境变量而不是系统属性,大多数操作系统不允许使用句点分隔的键名,但你可以使用下划线代替(例如, SPRING_CONFIG_NAME 代替 spring.config.name)。详见 从环境变量绑定
如果你的应用程序在servlet容器或应用服务器中运行,那么JNDI属性(在 java:comp/env 中)或servlet上下文初始化参数可以代替环境变量或系统属性,或者与之一样。

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

Java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@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")。

envconfigprops 端点在确定一个属性为什么有一个特定的值时非常有用。你可以使用这两个端点来诊断意外的属性值。详见 "生产就绪功能" 部分。

2.1. 访问命令行属性

默认情况下,SpringApplication 会将任何命令行选项参数(即以 -- 开头的参数,如 --server.port=9000 )转换为 property 并将其添加到Spring Environment 中。 如前所述,命令行属性总是优先于基于文件的属性源。

如果你不希望命令行属性被添加到 Environment 中,你可以通过 SpringApplication.setAddCommandLineProperties(false) 禁用它们。

2.2. JSON Application Properties

环境变量和系统属性往往有限制,这意味着有些属性名称不能使用。 为了帮助解决这个问题,Spring Boot允许你将一个属性块编码为一个单一的JSON结构。

当你的应用程序启动时,任何 spring.application.jsonSPRING_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 值将被添加到生成的属性源中,但 PropertySourcesPropertyResolvernull 属性视为缺失值。 这意味着JSON不能用 null 值覆盖来自低阶属性源的属性。

2.3. 外部的 Application Properties

当你的应用程序启动时,Spring Boot会自动从以下位置找到并加载 application.propertiesapplication.yaml 文件。

  1. classpath

    1. classpath 根路径

    2. classpath 下的 /config

  2. 当前目录

    1. 当前目录下

    2. 当前目录下的 config/ 子目录

    3. config/ 子目录的直接子目录

列表按优先级排序(较低项目的值覆盖较早项目的值)。 加载的文件被作为 PropertySources 添加到Spring的 Environment 中。

如果你不喜欢 application 作为配置文件名称,你可以通过指定 spring.config.name 环境属性切换到另一个文件名称。 例如,为了寻找 myproject.propertiesmyproject.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.locationclasspath: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/ ,考虑的完整位置集如下。

  1. optional:classpath:custom-config/

  2. optional:file:./custom-config/

如果你喜欢添加额外的位置,而不是替换它们,你可以使用 spring.config.extra-location 。 从附加位置加载的属性可以覆盖默认位置的属性。 例如,如果 spring.config.extra-location 被配置为 optional:classpath:/custom-config/,optional:file:./custom-config/ ,考虑的完整位置集如下。

  1. optional:classpath:/;optional:classpath:/config/

  2. optional:file:./;optional:file:./config/;optional:file:./config/*/

  3. optional:classpath:custom-config/

  4. 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.locationspring.config.extra-location 属性中使用通配符位置。

通配符位置必须只包含一个 * 并以 */ 结尾,用于搜索属于目录的位置,或 */<filename> 用于搜索属于文件的位置。 带有通配符的位置将根据文件名的绝对路径按字母顺序排序。
通配符位置只对外部目录起作用。 你不能在 classpath: 位置中使用通配符。

2.3.3. 特定文件(Profile Specific Files)

除了 application 属性文件,Spring Boot还将尝试使用 application-{profile} 的命名惯例加载profile特定的文件。 例如,如果你的应用程序激活了名为 prod 的配置文件(spring.profiles.active=prod)并使用YAML文件,那么 application.yamlapplication-prod.yaml 都将被考虑。

特定文件(profiles)的属性与标准的 application.properties 的位置相同,特定文件总是优先于非特定文件。 如果指定了几个配置文件,则采用最后胜出的策略。 例如,如果配置文件 prod,live 是由 spring.profiles.active 属性指定的,application-prod.properties 中的值可以被 application-live.properties 中的值所覆盖。

最后胜出的策略适用于location group级别。 spring.config.locationclasspath:/cfg/,classpath:/ext/ 将不会有与 classpath:/cfg/;classpath:/ext/ 相同的覆盖规则。

例如,拿我们上面的 prod,live 例子来说,我们可能有以下文件。

/cfg
  application-live.properties
/ext
  application-live.properties
  application-prod.properties

当我们有一个 spring.config.locationclasspath:/cfg/,classpath:/ext/ 时,我们会在所有 /ext 文件之前处理所有 /cfg 文件。

  1. /cfg/application-live.properties

  2. /ext/application-prod.properties

  3. /ext/application-live.properties

当我们用 classpath:/cfg/;classpath:/ext/ 代替时(用 ; 分隔符),我们在同一级别处理 /cfg/ext

  1. /ext/application-prod.properties

  2. /cfg/application-live.properties

  3. /ext/application-live.properties

Environment 有一组默认的配置文件(默认为 [default] ),如果没有设置活动的配置文件,就会使用这些配置文件。 换句话说,如果没有明确激活的配置文件,那么就会考虑来自 application-default 的属性。

属性文件只被加载一次。 如果你已经直接导入了一个配置文件的特定属性文件,那么它将不会被第二次导入。

2.3.4. 导入额外的数据

application properties 中可以使用 spring.config.import 属性从其他地方导入更多的配置数据。 导入在被发现时被处理,并被视为紧接着声明导入的文件下面插入的额外文件。

例如,你可能在你的 classpath application.properties 文件中有以下内容。

Properties
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
Yaml
spring:
  application:
    name: "myapp"
  config:
    import: "optional:file:./dev.properties"

这将触发导入当前目录下的 dev.properties 文件(如果存在这样的文件)。 导入的 dev.properties 中的值将优先于触发导入的文件。 在上面的例子中,dev.properties 可以将 spring.application.name 重新定义为一个不同的值。

一个导入只会被导入一次,无论它被声明多少次。 一个导入在properties/yaml文件内的单个文件中被定义的顺序并不重要。 例如,下面的两个例子产生相同的结果。

Properties
spring.config.import=my.properties
my.property=value
Yaml
spring:
  config:
    import: "my.properties"
my:
  property: "value"
Properties
my.property=value
spring.config.import=my.properties
Yaml
my:
  property: "value"
spring:
  config:
    import: "my.properties"

在上述两个例子中,my.properties 文件的值将优先于触发其导入的文件。

在一个单一的 spring.config.import 属性下可以指定多个位置。 位置将按照它们被定义的顺序被处理,后来的导入将被优先处理。

在适当的时候,特定配置文件的变体也被考虑导入。 上面的例子将导入 my.properties 以及任何 my-<profile>.properties 变体。

Spring Boot 提供了可插拔的API(插件),允许支持各种不同的位置地址。 默认情况下,你可以导入Java Properties、YAML和 “配置树” 。

第三方jar可以提供对其他技术的支持(不要求必须是本地文件)。 例如,你可以想象配置数据来自外部存储,如Consul、Apache ZooKeeper或Netflix Archaius(包括Nacos)。

如果你想支持你自己的位置(实现自己定义的配置加载),请参阅 org.springframework.boot.context.config 包中的 ConfigDataLocationResolverConfigDataLoader 类。

2.3.5. 导入无扩展名的文件

有些云平台不能为卷装文件(volume mounted files)添加文件扩展名。 要导入这些无扩展名的文件,你需要给Spring Boot一个提示,以便它知道如何加载它们。 你可以通过把扩展名提示放在方括号里来做到这一点。

例如,假设你有一个 /etc/config/myconfig 文件,你希望以yaml形式导入。 你可以用下面的方法从你的 application.properties 中导入它。

Properties
spring.config.import=file:/etc/config/myconfig[.yaml]
Yaml
spring:
  config:
    import: "file:/etc/config/myconfig[.yaml]"

2.3.6. 使用配置树(Configuration Trees)

当在云平台(如Kubernetes)上运行应用程序时,你经常需要读取平台提供的配置值。 将环境变量用于此类目的并不少见,但这可能有缺点,特别是如果该值是 secret 的。

作为环境变量的替代方案,许多云平台现在允许你将配置映射到挂载的数据卷。 例如,Kubernetes 可以卷挂载 ConfigMapsSecrets

可以使用两种常见的 volume 挂载模式:

  1. 一个文件包含一套完整的属性(通常写成YAML)。

  2. 多个文件被写入一个目录树中,文件名成为 ‘key’,内容成为 ‘value’。

对于第一种情况,你可以使用 spring.config.import 直接导入YAML或属性文件,如上所述。 对于第二种情况,你需要使用 configtree: 前缀,以便Spring Boot知道它需要将所有文件作为属性公开。

举个例子,让我们想象一下,Kubernetes已经挂载了以下volume。

etc/
  config/
    myapp/
      username
      password

username 文件的内容将是一个配置值,而 password 的内容将是一个 secret。

要导入这些属性,你可以在你的 application.propertiesapplication.yaml 文件中添加以下内容。

Properties
spring.config.import=optional:configtree:/etc/config/
Yaml
spring:
  config:
    import: "optional:configtree:/etc/config/"

然后你可以从 Environment 中以常规方式访问或注入 myapp.usernamemyapp.password 属性。

配置树下的文件夹构成了属性名称。 在上面的例子中,为了访问属性为 usernamepassword,你可以将 spring.config.import 设置为 optional:configtree:/etc/config/myapp
带有点符号的文件名也会被正确映射。 例如,在上面的例子中,/etc/config 中名为 myapp.username 的文件在 Environment 中的属性名将会是 myapp.username
配置树的值可以被绑定到字符串 Stringbyte[] 类型,这取决于预期的内容。

如果你有多个配置树要从同一个父文件夹导入,你可以使用通配符快捷方式。 任何以 /*/ 结尾的 configtree: 位置将导入所有直接的子文件夹作为配置树。

例如,给定以下volume:

etc/
  config/
    dbconfig/
      db/
        username
        password
    mqconfig/
      mq/
        username
        password

你可以使用 configtree:/etc/config/*/ 作为导入位置。

Properties
spring.config.import=optional:configtree:/etc/config/*/
Yaml
spring:
  config:
    import: "optional:configtree:/etc/config/*/"

这将添加 db.usernamedb.passwordmq.usernamemq.password 属性。

使用通配符加载的目录是按字母顺序排列的。 如果你需要一个不同的顺序,那么你应该把每个位置作为一个单独的导入列出。

配置树也可用于Docker secret。 当Docker swarm服务被授予对secret的访问权时,该secret会被装载到容器中。 例如,如果一个名为 db.password 的secret。被挂载在 /run/secrets/ 的位置,你可以用以下方法让 db.password 对Spring环境可用。

Properties
spring.config.import=optional:configtree:/run/secrets/
Yaml
spring:
  config:
    import: "optional:configtree:/run/secrets/"

2.3.7. 属性占位符

application.propertiesapplication.yaml 中的值在使用时通过现有的 Environment 过滤,所以你可以参考以前定义的值(例如,来自系统属性或环境变量)。 标准的 ${name} 属性占位符语法可以用在一个值的任何地方。 属性占位符也可以指定一个默认值,使用 : 来分隔默认值和属性名称,例如 ${name:default}

下面的例子显示了带默认值和不带默认值的占位符的使用情况。

Properties
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
Yaml
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使用与宽松绑定 @ConfigurationProperties 时相同的逻辑。

例如,${demo.item-price} 将从 application.properties 文件中获取 demo.item-pricedemo.itemPrice 形式的属性,以及从系统环境中获取 DEMO_ITEMPRICE 。 如果你用 ${demo.itemPrice} 的话, demo.item-priceDEMO_ITEMPRICE 就不会被考虑。

你也可以使用这种技术来创建现有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.* 有条件地激活一个属性文件。

激活属性有如下。

Table 2. activation properties
属性 说明

on-profile

一个必须与之匹配的配置文件表达式,以使文件处于活动状态(激活指定的配置文件时有效)。

on-cloud-platform

必须检测到的 CloudPlatform,以使文件处于活动状态。(云平台状态下有效)

例如,下面指定第二个文件只有在Kubernetes上运行时才有效,并且只有在 “prod” 或 “staging” 配置文件处于活动状态时才有效。

Properties
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
Yaml
myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

2.4. 加密配置属性(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 ListSet 对象。 更多细节见下面的 “类型安全的配置属性” 部分。
YAML文件不能通过使用 @PropertySource@TestPropertySource 注解来加载。 所以,在你需要以这种方式加载值的情况下,你需要使用一个 properties 文件。

2.5.2. 直接加载YAML

Spring Framework提供了两个方便的类,可以用来加载YAML文档。 YamlPropertiesFactoryBean 将YAML作为 Properties 加载,YamlMapFactoryBean 将YAML作为 Map 加载。

如果你想把YAML加载为Spring的 PropertySource ,你也可以使用 YamlPropertySourceLoader 类。

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,如下面的例子所示。

Properties
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
Yaml
my:
  secret: "${random.value}"
  number: "${random.int}"
  bignumber: "${random.long}"
  uuid: "${random.uuid}"
  number-less-than-ten: "${random.int(10)}"
  number-in-range: "${random.int[1024,65536]}"

random.int* 的语法是 OPEN value (,max) CLOSE,其中 OPEN,CLOSE 是任何字符, value,max 是整数。 如果提供了 max,那么 value 是最小值, max 是最大值(独占)。

2.7. 配置系统环境属性

Spring Boot支持为环境属性设置一个前缀。 如果系统环境被多个具有不同配置要求的Spring Boot应用程序共享,这就很有用。 系统环境属性的前缀可以直接在 SpringApplication 上设置。

例如,如果你将前缀设置为 input ,诸如 remote.timeout 这样的属性在系统环境中也将被解析为 input.remote.timeout

2.8. 类型安全的配置属性

使用 @Value("${property}") 注解来注入配置属性有时会很麻烦,特别是当你要处理多个属性或你的数据是分层的。 Spring Boot提供了一种处理属性的替代方法,让强类型的Bean管理和验证你的应用程序的配置。

2.8.1. JavaBean 属性绑定

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

Java
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my.service")
public class MyProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public boolean isEnabled() {
        return this.enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public void setRemoteAddress(InetAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    public Security getSecurity() {
        return this.security;
    }

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        // getters / setters...

        public String getUsername() {
            return this.username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return this.password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public List<String> getRoles() {
            return this.roles;
        }

        public void setRoles(List<String> roles) {
            this.roles = roles;
        }

    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.InetAddress

@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。

  • Map, 只要它们被初始化,就需要一个getter,但不一定需要一个setter,因为它们可以被绑定器突变。

  • Collection和array 可以通过索引(通常用YAML)或使用单个逗号分隔的值(属性)来访问。 在后一种情况下,一个setter是必须的。 我们建议总是为这类类型添加一个setter。 如果你初始化一个集合,确保它不是不可变的(如前面的例子)。

  • 如果嵌套的POJO属性被初始化(就像前面例子中的 Security 字段),就不需要setter。 如果你想让绑定器通过使用它的默认构造函数来即时创建实例,你需要一个setter。

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

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

2.8.2. 构造函数绑定

上一节的例子可以用不可变的方式重写,如下例所示。

Java
import java.net.InetAddress;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConfigurationProperties("my.service")
public class MyProperties {

    // fields...

    private final boolean enabled;

    private final InetAddress remoteAddress;

    private final Security security;

    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    // getters...

    public boolean isEnabled() {
        return this.enabled;
    }

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public Security getSecurity() {
        return this.security;
    }

    public static class Security {

        // fields...

        private final String username;

        private final String password;

        private final List<String> roles;

        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }

        // getters...

        public String getUsername() {
            return this.username;
        }

        public String getPassword() {
            return this.password;
        }

        public List<String> getRoles() {
            return this.roles;
        }

    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import java.net.InetAddress

@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 值强制转换为缺失属性的目标类型。

参考前面的例子,如果没有属性绑定到 SecurityMyProperties 实例将包含一个 security 类型的 null 值。 为了使它包含一个非 null 的 Security 实例,即使没有属性与之绑定(当使用Kotlin时,这将要求 Securityusernamepassword 参数被声明为 nullable,因为它们没有默认值),使用一个空的 @DefaultValue 注解。

Java
public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
    this.enabled = enabled;
    this.remoteAddress = remoteAddress;
    this.security = security;
}
Kotlin
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 类上,如下面的例子所示。

Java
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
Kotlin
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
Java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some.properties")
public class SomeProperties {

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("some.properties")
class SomeProperties

要使用配置属性扫描,请向你的application添加 @ConfigurationPropertiesScan 注解。 通常,它被添加到用 @SpringBootApplication 注解的main类中,但它也可以被添加到任何 @Configuration 类上。 默认情况下,扫描会从注解所在的包开始,你如果想自定义扫描其他包,可以参考如下。

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan

@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication

@ConfigurationProperties Bean使用配置属性扫描或通过 @EnableConfigurationProperties 注册时,该Bean有一个常规名称:<prefix>-<fqn>,其中 <prefix>@ConfigurationProperties 注解中指定的环境键前缀, <fqn> 是Bean的完全限定名称。 如果注解没有提供任何前缀,则只使用Bean的完全限定名称。

假设它在 com.example.app 包中,上面的 SomeProperties 例子的 bean 名称是 some.properties-com.example.app.SomeProperties

我们建议 @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相同的方式注入它们,如下例所示。

Java
import org.springframework.stereotype.Service;

@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();
        // ...
    }

    // ...

}
Kotlin
import org.springframework.stereotype.Service

@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 ,如下例所示。

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "another")
    public AnotherComponent anotherComponent() {
        return new AnotherComponent();
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@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 类。

Java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {

    private String firstName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {

    var firstName: String? = null

}

对以上的代码来说,以下的属性名称都可以使用。

Table 3. relaxed binding
Property Note

my.main-project.person.first-name

Kebab 风格(短横线隔开),建议在 .properties 和 YAML 文件中使用。

my.main-project.person.firstName

标准的驼峰语法。

my.main-project.person.first_name

下划线,这是一种用于 .properties 和 YAML 文件的替代格式。

MY_MAINPROJECT_PERSON_FIRSTNAME

大写格式,在使用系统环境变量时建议使用大写格式。

注解的 prefix必须 是kebab风格(小写并以 - 分隔,如 my.main-project.person )。
Table 4. 每种属性源的宽松绑定规则
属性源 简单的 列表

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>

Properties
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
Yaml
my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"
对于YAML文件,括号需要用引号包裹,以使key被正确解析。

上面的属性将绑定到一个 Map/key1/key2key3 作为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变量只能包含字母(azAZ )、数字( 09 )或下划线字符( _ )。 按照惯例,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 对象的 namedescription 属性默认为 null。 下面的例子从 MyProperties 中暴露了一个 MyPojo 对象的列表。

Java
import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

    private final List<MyPojo> list = new ArrayList<>();

    public List<MyPojo> getList() {
        return this.list;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

    val list: List<MyPojo> = ArrayList()

}

考虑以下配置。

Properties
my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
Yaml
my:
  list:
  - name: "my name"
    description: "my description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

如果 dev 配置文件未被激活,MyProperties.list 包含一个 MyPojo 条目,如之前定义的那样。 然而,如果 dev 配置文件被激活,list 仍然只包含一个条目(name 为 my another name,description为 null)。 这种配置不会在列表中添加第二个 MyPojo 实例,也不会合并项目。

当一个 List 在多个配置文件中被指定时,将使用具有最高优先级的那个(并且只有那个)。 考虑下面的例子。

Properties
my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
Yaml
my:
  list:
  - name: "my name"
    description: "my description"
  - name: "another name"
    description: "another description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

在前面的例子中,如果 dev 配置文件是激活的,MyProperties.list 包含 一个 MyPojo 条目(name 是 my another name,description是 null)。 对于YAML,逗号分隔的列表和YAML列表都可以用来完全覆盖列表的内容。

对于 Map 属性,你可以用从多个来源获取的属性值进行绑定。 然而,对于多个来源中的同一属性,使用具有最高优先级的那个。 下面的例子从 MyProperties 暴露了一个 Map<String, MyPojo>

Java
import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

    private final Map<String, MyPojo> map = new LinkedHashMap<>();

    public Map<String, MyPojo> getMap() {
        return this.map;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

    val map: Map<String, MyPojo> = LinkedHashMap()

}

考虑以下配置。

Properties
my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
Yaml
my:
  map:
    key1:
      name: "my name 1"
      description: "my description 1"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  map:
    key1:
      name: "dev name 1"
    key2:
      name: "dev name 2"
      description: "dev description 2"

如果 dev 配置文件没有激活,MyProperties.map 包含一个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秒)。

请考虑以下例子。

Java
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

    @DurationUnit(ChronoUnit.SECONDS)
    private Duration sessionTimeout = Duration.ofSeconds(30);

    private Duration readTimeout = Duration.ofMillis(1000);

    // getters / setters...

    public Duration getSessionTimeout() {
        return this.sessionTimeout;
    }

    public void setSessionTimeout(Duration sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }

    public Duration getReadTimeout() {
        return this.readTimeout;
    }

    public void setReadTimeout(Duration readTimeout) {
        this.readTimeout = readTimeout;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties {

    @DurationUnit(ChronoUnit.SECONDS)
    var sessionTimeout = Duration.ofSeconds(30)

    var readTimeout = Duration.ofMillis(1000)

}

要指定一个30秒的会话超时, 30PT30S30s 都是等价的。 读取超时为500ms,可以用以下任何一种形式指定。 500, PT0.5S500ms.

你也可以使用如下支持的时间单位。

  • ns 纳秒

  • us 微秒

  • ms 毫秒

  • s

  • m

  • h 小时

  • d

默认单位是毫秒,可以使用 @DurationUnit 来重写,如上面的例子所示。

如果你喜欢使用构造函数绑定,同样的属性可以被暴露出来,如下面的例子所示。

Java
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

    // fields...

    private final Duration sessionTimeout;

    private final Duration readTimeout;

    public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
            @DefaultValue("1000ms") Duration readTimeout) {
        this.sessionTimeout = sessionTimeout;
        this.readTimeout = readTimeout;
    }

    // getters...

    public Duration getSessionTimeout() {
        return this.sessionTimeout;
    }

    public Duration getReadTimeout() {
        return this.readTimeout;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@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兆字节)。

考虑以下例子。

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize bufferSize = DataSize.ofMegabytes(2);

    private DataSize sizeThreshold = DataSize.ofBytes(512);

    // getters/setters...

    public DataSize getBufferSize() {
        return this.bufferSize;
    }

    public void setBufferSize(DataSize bufferSize) {
        this.bufferSize = bufferSize;
    }

    public DataSize getSizeThreshold() {
        return this.sizeThreshold;
    }

    public void setSizeThreshold(DataSize sizeThreshold) {
        this.sizeThreshold = sizeThreshold;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties {

    @DataSizeUnit(DataUnit.MEGABYTES)
    var bufferSize = DataSize.ofMegabytes(2)

    var sizeThreshold = DataSize.ofBytes(512)

}

要指定一个10兆字节(Mb)的缓冲区大小, 1010MB 是等价的。 256字节的大小阈值可以指定为 256256B

你也可以使用如下这些支持的单位。

  • B 字节

  • KB KB

  • MB MB

  • GB GB

  • TB TB

默认单位是字节,可以使用 @DataSizeUnit 来重写,如上面的例子所示。

如果你喜欢使用构造函数绑定,同样的属性可以被暴露出来,如下面的例子所示。

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

    // fields...

    private final DataSize bufferSize;

    private final DataSize sizeThreshold;

    public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
            @DefaultValue("512B") DataSize sizeThreshold) {
        this.bufferSize = bufferSize;
        this.sizeThreshold = sizeThreshold;
    }

    // getters...

    public DataSize getBufferSize() {
        return this.bufferSize;
    }

    public DataSize getSizeThreshold() {
        return this.sizeThreshold;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
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实现,然后将约束注解添加到你的字段中,如下面的例子所示。

Java
import java.net.InetAddress;

import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

    @NotNull
    private InetAddress remoteAddress;

    // getters/setters...

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public void setRemoteAddress(InetAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

}
Kotlin
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

    var remoteAddress: @NotNull InetAddress? = null

}
你也可以通过在 configuration properties 的 @Bean 方法上注解 @Validated 来触发验证。

为了确保总是为嵌套的属性触发验证,即使没有找到属性,相关的字段必须用 @Valid 来注释。 下面的例子建立在前面的 MyProperties 的基础上。

Java
import java.net.InetAddress;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

    @NotNull
    private InetAddress remoteAddress;

    @Valid
    private final Security security = new Security();

    // getters/setters...

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public void setRemoteAddress(InetAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    public Security getSecurity() {
        return this.security;
    }

    public static class Security {

        @NotEmpty
        private String username;

        // getters/setters...

        public String getUsername() {
            return this.username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

    }

}
Kotlin
import jakarta.validation.Valid
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@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

有限制 (见 下文注释)

支持 Meta-data

Yes

No

SpEL 表达式

No

Yes

如果你确实想使用 @Value,我们建议你使用属性名称的规范形式(仅使用小写字母的kebab-case)来引用属性名称。 这将允许Spring Boot使用与 宽松绑定 @ConfigurationProperties 时相同的逻辑。

例如,@Value("${demo.item-price}") 将从 application.properties 文件中获取 demo.item-pricedemo.itemPrice 形式,以及从系统环境中获取 DEMO_ITEMPRICE。 如果你用 @Value("${demo.itemPrice}") 代替,demo.item-priceDEMO_ITEMPRICE 将不会被考虑。

如果你为你自己的组件定义了一组配置键,我们建议你将它们分组在一个用 @ConfigurationProperties 注解的POJO中。 这样做将为你提供结构化的、类型安全的对象,你可以将其注入到你自己的bean中。

来自应用application property 文件的 SpEL 表达式在解析这些文件和填充environment时不会被处理。 然而,可以在 @Value 中写一个 SpEL 表达式。 如果来自应用程序属性文件的属性值是一个 SpEL 表达式,它将在被 @Value 消费时被解析。

3. Profiles

Spring Profiles提供了一种方法来隔离你的应用程序配置的一部分,并使其仅在某些环境中可用。 任何 @Component@Configuration@ConfigurationProperties 都可以用 @Profile 标记,以限制它的加载时机,如下面的例子所示。

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {

    // ...

}
Kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile

@Configuration(proxyBeanMethods = false)
@Profile("production")
class ProductionConfiguration {

    // ...

}
如果 @ConfigurationProperties Bean是通过 @EnableConfigurationProperties 注册的,而不是自动扫描,则需要在具有 @EnableConfigurationProperties 注解的 @Configuration 类上指定 @Profile 注解。 在 @ConfigurationProperties 被扫描的情况下,@Profile 可以在 @ConfigurationProperties 类本身指定。

你可以使用 spring.profiles.active Environment 属性来指定哪些配置文件是活动的(active)。 你可以通过本章前面描述的任何方式来指定该属性。 例如,你可以在你的 application.properties 中包含它,如下面的例子所示。

Properties
spring.profiles.active=dev,hsqldb
Yaml
spring:
  profiles:
    active: "dev,hsqldb"

你也可以通过使用以下开关在命令行中指定它:--spring.profiles.active=dev,hsqldb

如果没有激活配置文件,则启用一个默认的配置文件。 默认配置文件的名称是 default ,可以使用 spring.profiles.default Environment 属性对其进行调整,如下面的例子所示。

Properties
spring.profiles.default=none
Yaml
spring:
  profiles:
    default: "none"

spring.profiles.activespring.profiles.default 只能在非配置文件的文件中使用。 这意味着它们不能包含在配置文件的特定文件或由 spring.config.activated.on-profile 激活的文件中。

例如,第二个文件配置是无效的。

Properties
# this document is valid
spring.profiles.active=prod
#---
# this document is invalid
spring.config.activate.on-profile=prod
spring.profiles.active=metrics
Yaml
# 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 也会被激活。

Properties
spring.profiles.include[0]=common
spring.profiles.include[1]=local
Yaml
spring:
  profiles:
    include:
      - "common"
      - "local"
spring.profiles.active 类似,spring.profiles.include 只能用于非配置文件的文件。 这意味着它不能包含在由 spring.config.activated.on-profile 激活的文件中。

如果一个给定的配置文件是活动的,配置文件组,在下一节中描述,也可以用来添加活动的配置文件。

3.2. 配置文件组(Profile Groups)

偶尔,你在你的应用程序中定义和使用的配置文件过于精细,使用起来就会很麻烦。 例如,你可能有 proddbprodmq 配置文件,用来独立启用数据库和消息传递功能。

为了帮助解决这个问题,Spring Boot允许你定义配置文件组。 配置文件组允许你为相关的配置文件组定义一个逻辑名称。

例如,我们可以创建一个 production 组,由 proddbprodmq 配置文件组成。

Properties
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
Yaml
spring:
  profiles:
    group:
      production:
      - "proddb"
      - "prodmq"

现在可以使用 --spring.profiles.active=production 来启动我们的应用程序,一次性激活 productionproddbprodmq 配置文件。

3.3. 以编程方式设置配置文件(Profile)

你可以在应用运行前通过调用 SpringApplication.setAdditionalProfiles(…​) 以编程方式设置激活的配置文件。 也可以通过使用Spring的 ConfigurableEnvironment 接口来激活配置文件。

3.4. 特定的配置文件

application.properties(或 application.yaml)和通过 @ConfigurationProperties 引用的文件的特定配置文件变体都被认为是文件并被加载。 参见"特定文件(Profile Specific Files)"以了解详情。

4. 日志

Spring Boot在所有内部日志中使用 Commons Logging ,但对底层日志的实现保持开放。 为 Java Util LoggingLog4j2Logback 提供了默认配置。 在每一种情况下,记录器(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. 控制台输出

默认情况下,日志会输出 ERRORWARNINFO 级别的消息到控制台。 你也可以通过用 --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)

下表描述了日志级别与颜色的映射关系。

日志级别 颜色

FATAL

ERROR

WARN

INFO

绿

DEBUG

绿

TRACE

绿

另外,你也可以通过为转换提供一个选项来指定应该使用的颜色或样式。 例如,要使文本为黄色,请使用以下设置。

%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow}

支持以下颜色和样式。

  • blue

  • cyan

  • faint

  • green

  • magenta

  • red

  • yellow

4.3. 输出到文件

默认情况下,Spring Boot只向控制台记录日志,不写日志文件。 如果你想在控制台输出之外写日志文件,你需要设置 logging.file.namelogging.file.path 属性(例如,在你的 application.properties 中)。

下表显示了 logging.* 属性如何被一起使用。

Table 5. Logging properties
logging.file.name logging.file.path Example Description

(none)

(none)

只在控制台进行记录。

指定文件

(none)

my.log

写入指定的日志文件。 名称可以是一个确切的位置,也可以是与当前目录的相对位置。

(none)

指定目录

/var/log

spring.log 写到指定目录。 名称可以是一个确切的位置,也可以是与当前目录的相对位置。

日志文件在达到10MB时就会轮换,与控制台输出一样,默认情况下会记录 ERRORWARN 级和 INFO 级别的信息。

日志属性独立于实际的日志基础设施。 因此,特定的配置属性(如Logback的 logback.configurationFile )不由spring Boot管理。

4.4. 文件轮换(滚动日志)

如果你使用Logback,可以使用你的 application.propertiesapplication.yaml 文件来微调日志轮换设置。 对于所有其他的日志系统,你将需要自己直接配置轮换设置(例如,如果你使用Log4J2,那么你可以添加一个 log4j2.xmllog4j2-spring.xml 文件)。

支持以下轮换策略属性。

属性 说明

logging.logback.rollingpolicy.file-name-pattern

用于创建日志归档的文件名模式。

logging.logback.rollingpolicy.clean-history-on-start

应用程序启动时,是否行日志归档清理。

logging.logback.rollingpolicy.max-file-size

日志文件归档前的最大尺寸(文件最大体积,达到这个体积就会归档)。

logging.logback.rollingpolicy.total-size-cap

日志档案在被删除前的最大尺寸(归档文件最大占用大小,超过这个大小后会被删除)。

logging.logback.rollingpolicy.max-history

要保留的归档日志文件的最大数量(默认为7)。

4.5. 日志级别

所有支持的日志系统都可以通过使用 logging.level.<logger-name>=<level> 在Spring的 Environment(例如,在 application.properties)中设置日志级别,其中 levelTRACE, DEBUG, INFO, WARN, ERROR, FATAL, 或 OFF 之一。 root 记录器(logger)的级别可以通过 logging.level.root 来配置。

下面的例子显示了 application.properties 中潜在的日志设置。

Properties
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
Yaml
logging:
  level:
    root: "warn"
    org.springframework.web: "debug"
    org.hibernate: "error"

也可以使用环境变量来设置日志级别。 例如,LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG 将设置 org.springframework.webDEBUG

上述方法只适用于包级日志。 由于宽松绑定总是将环境变量转换为小写字母,所以不可能用这种方式为单个类配置日志。 如果你需要为一个类配置日志,你可以使用SPRING_APPLICATION_JSON变量。

4.6. 日志组(Log Groups)

能够将相关的日志记录器分组,以便同时对它们进行配置,这通常很有用。 例如,你可能经常改变 所有 与Tomcat相关的记录器的记录级别,但你不容易记住最高级别的包。

为了帮助解决这个问题,Spring Boot允许你在Spring Environment 中定义日志组。 例如,你可以通过在 application.properties 中加入 “tomcat” group 来定义它。

Properties
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
Yaml
logging:
  group:
    tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"

一旦定义好后,就可以用一行代码来改变组中所有logger的级别。

Properties
logging.level.tomcat=trace
Yaml
logging:
  level:
    tomcat: "trace"

Spring Boot包括以下预定义的日志组,可以开箱即用。

组名 组中的logger

web

org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans

sql

org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

4.7. 使用日志 Shutdown Hook

为了在你的应用程序终止时释放日志资源,我们提供了一个Shutdown Hook,它将在JVM退出时触发日志系统清理。 除非你的应用程序是以war文件的形式部署的,否则这个Shutdown Hook会自动注册。 如果你的应用程序有复杂的上下文层次结构,Shutdown Hook可能无法满足你的需求。 如果不能,请禁用关机钩子,并研究底层日志系统直接提供的选项。 例如,Logback提供了 context selectors,允许每个记录器在它自己的上下文中被创建。 你可以使用 logging.register-shutdown-hook 属性来禁用Shutdown Hook。 将其设置为 false 将禁用注册。 你可以在你的 application.propertiesapplication.yaml 文件中设置该属性。

Properties
logging.register-shutdown-hook=false
Yaml
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

logback-spring.xml, logback-spring.groovy, logback.xml 或者 logback.groovy

Log4j2

log4j2-spring.xml 或者 log4j2.xml

JDK (Java Util Logging)

logging.properties

在可能的情况下,我们建议你使用 -spring 变体来进行日志配置(例如, logback-spring.xml 而不是 logback.xml )。 如果你使用标准配置位置,Spring不能完全控制日志初始化。
当从 "可执行的jar "中运行时,Java Util Logging有一些已知的类加载问题,会导致问题。 如果可能的话,我们建议你在从 "可执行的jar" 中运行时避免使用它。

为了帮助定制,其他一些属性从Spring的 Environment 转移到System properties,如下表所示。

Spring Environment System Property 备注

logging.exception-conversion-word

LOG_EXCEPTION_CONVERSION_WORD

记录异常时使用的转换词。

logging.file.name

LOG_FILE

如果定义了,它将用于默认的日志配置中。

logging.file.path

LOG_PATH

如果定义了,它将用于默认的日志配置中。

logging.pattern.console

CONSOLE_LOG_PATTERN

在控制台(stdout)使用的日志输出模式。

logging.pattern.dateformat

LOG_DATEFORMAT_PATTERN

date 格式化.

logging.charset.console

CONSOLE_LOG_CHARSET

控制台输出日志的字符编码。

logging.threshold.console

CONSOLE_LOG_THRESHOLD

用于控制台日志记录的日志级别。

logging.pattern.file

FILE_LOG_PATTERN

要在文件中使用的日志模式(如果 LOG_FILE 被启用)。

logging.charset.file

FILE_LOG_CHARSET

文件日志的字符编码(如果 LOG_FILE 被启用)。

logging.threshold.file

FILE_LOG_THRESHOLD

用于文件日志记录的日志级别。

logging.pattern.level

LOG_LEVEL_PATTERN

渲染日志级别时使用的格式(默认为 %5p )。

PID

PID

当前的进程ID

如果你使用Logback,以下属性也会被转移。

Spring Environment System Property 备注

logging.logback.rollingpolicy.file-name-pattern

LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN

滚动日志文件名的模式(默认为 ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz )。

logging.logback.rollingpolicy.clean-history-on-start

LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START

是否在启动时清理归档日志文件。

logging.logback.rollingpolicy.max-file-size

LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE

最大日志文件大小。

logging.logback.rollingpolicy.total-size-cap

LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP

要保留的日志备份的总大小。

logging.logback.rollingpolicy.max-history

LOGBACK_ROLLINGPOLICY_MAX_HISTORY

要保留的最大归档日志文件数量。

所有支持的日志系统在解析其配置文件时都可以从 System properties 中获取属性。 例子见 spring-boot.jar 中的默认配置。

如果你想在日志属性中使用占位符,你应该使用Spring Boot的语法而不是底层框架的语法。 值得注意的是,如果你使用Logback,你应该使用 : 作为属性名和其默认值之间的分隔符,而不是使用 :-

你可以通过只覆盖 LOG_LEVEL_PATTERN (或使用Logback的 logging.pattern.level )来向日志行添加MDC和其他临时内容。 例如,如果你使用 logging.pattern.level=user:%X{user} %5p ,那么默认的日志格式包含一个 "user" 的MDC条目,如果它存在的话,如下例所示。

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 或者定义一个 logging.config 属性。
这些扩展取代了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 命名空间进行配置,如下面的例子所示。

Properties
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
Yaml
spring:
  messages:
    basename: "messages,config.i18n.messages"
    fallback-to-system-locale: false
spring.messages.basename 支持逗号分隔的位置列表,可以是包的限定词或从classpath根路径解析的资源。

参见 MessageSourceProperties 以了解更多支持的选项。

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数据,你可能想编写自己的 JsonSerializerJsonDeserializer 类。自定义序列化器通常是 通过模块向Jackson注册的,但Spring Boot提供了一个替代性的 @JsonComponent 注解,使直接注册Spring Bean变得更容易。

你可以直接在 JsonSerializerJsonDeserializerKeyDeserializer 的实现上使用 @JsonComponent 注解。 你也可以在包含 serializers/deserializers 作为内部类的类上使用它,如下面的例子所示。

Java
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import org.springframework.boot.jackson.JsonComponent;

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

    }

}
Kotlin
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import java.io.IOException

@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还提供了 JsonObjectSerializerJsonObjectDeserializer 基类,在序列化对象时提供了标准Jackson版本的有用替代品。详情见Javadoc中的 JsonObjectSerializerJsonObjectDeserializer

上面的例子可以改写为使用 JsonObjectSerializer/JsonObjectDeserializer,如下所示。

Java
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;

import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer;
import org.springframework.boot.jackson.JsonObjectSerializer;

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

    }

}
Kotlin
`object`

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.ObjectCodec
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import org.springframework.boot.jackson.JsonObjectDeserializer
import org.springframework.boot.jackson.JsonObjectSerializer
import java.io.IOException

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

}

6.1.2. 混合(Mixins)

ackson支持混合器,可用于将额外的注释混合到已经在目标类上声明的注解中。 Spring Boot的Jackson自动配置将扫描你的应用程序的包,寻找用 @JsonMixin 注解的类,并将它们注册到自动配置的 ObjectMapper 。 注册是由Spring Boot的 JsonMixinModule 执行的。

6.2. Gson

为Gson提供了自动配置。当Gson在classpath上时,会自动配置一个 Gson Bean。 提供了几个 spring.gson.* 配置属性来定制配置。 需要进一步设置,可以使用一个或多个 GsonBuilderCustomizer bean。

6.3. JSON-B

为JSON-B提供了自动配置功能。 当JSON-B API和一个实现在classpath上时,一个 Jsonb Bean将被自动配置。 首选的JSON-B实现是Eclipse Yasson,Spring Boot为其提供了依赖管理(已经纳入了版本管理)。

7. 任务执行和调度(Task Execution and Scheduling)

如果context没有 Executor bean,Spring Boot会自动配置一个 ThreadPoolTaskExecutor ,它具有合理的默认值,可以自动与异步任务执行( @EnableAsync )和Spring MVC的异步请求处理相关联。

如果你在context定义了一个自定义的 Executor ,常规的任务执行(也就是 @EnableAsync )将透明地使用它,但Spring MVC支持将不会被配置,因为它需要一个 AsyncTaskExecutor 的实现(名为 applicationTaskExecutor )。 根据你的目标安排,你可以将你的 Executor 改为 ThreadPoolTaskExecutor ,或者同时定义一个 ThreadPoolTaskExecutor 和一个 AsyncConfigurer 来包装你的自定义 Executor

自动配置的 TaskExecutorBuilder 允许你轻松地创建实例,重现自动配置的默认做法(使用默认的配置)。

线程池使用8个核心线程,可以根据负载伸缩。 这些默认设置可以使用 spring.task.execution 命名空间进行微调,如以下例子所示。

Properties
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
Yaml
spring:
  task:
    execution:
      pool:
        max-size: 16
        queue-capacity: 100
        keep-alive: "10s"

这将线程池改为使用有界队列,因此当队列满了(100个任务),线程池增加到最大16个线程。 线程池的收缩更加积极,因为当线程闲置10秒(而不是默认的60秒)时就会被回收。

如果需要与计划(定时)任务执行(例如使用 @EnableScheduling )相关联,也可以自动配置一个 ThreadPoolTaskScheduler。 线程池默认使用一个线程,它的设置可以使用 spring.task.scheduling 命名空间进行微调,如下面的例子所示。

Properties
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
Yaml
spring:
  task:
    scheduling:
      thread-name-prefix: "scheduling-"
      pool:
        size: 2

如果需要创建一个自定义的执行器或调度器,上下文中的 TaskExecutorBuilder bean和 TaskSchedulerBuilder bean都是可用的。

8. 测试

Spring 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引擎,请添加对 junit-vint-engine 的依赖,如以下例子所示。

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

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 不会启动一个服务器。你可以使用 @SpringBootTestwebEnvironment 属性来进一步完善你的测试运行方式。

  • MOCK(Default) : 加载一个Web ApplicationContext 并提供一个模拟的Web环境。当使用此注解时,嵌入式服务器不会被启动。如果你的classpath上没有web环境,这种模式会自动退回到创建一个普通的非web ApplicationContext。它可以与@AutoConfigureMockMvc@AutoConfigureWebTestClient一起使用,用于对你的Web应用进行基于模拟的测试。

  • RANDOM_PORT: 加载一个 WebServerApplicationContext 并提供一个真实的web环境。嵌入式服务器被启动并监听一个随机端口。

  • DEFINED_PORT: 加载一个 WebServerApplicationContext 并提供一个真实的web环境。嵌入式服务器被启动并监听一个定义的端口(来自你的 application.properties )或默认的 8080 端口。

  • NONE: 通过使用 SpringApplication 加载一个 ApplicationContext,但不提供任何Web环境(模拟或其他)。

如果你的测试是 @Transactional ,它默认在每个测试方法结束时回滚事务。然而,由于使用这种安排与 RANDOM_PORTDEFINED_PORT 隐含地提供了一个真正的servlet环境,HTTP客户端和服务器在独立的线程中运行,因此,在独立的事务中。在这种情况下,服务器上启动的任何事务都不会回滚。
@SpringBootTestwebEnvironment = 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 属性。

Java
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {

    // ...

}
Kotlin
import org.springframework.boot.test.context.SpringBootTest

@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类上添加特定于某个区域的配置。

@SpringBootApplication 的底层组件扫描配置定义了排除Filter,用于确保切片(slicing)工作符合预期。 如果你在你的 @SpringBootApplication 注解的类上使用明确的 @ComponentScan 指令,请注意这些Filter将被禁用。 如果你正在使用分片,你应该重新定义它们。

如果你想定制主配置,你可以使用一个嵌套的 @TestConfiguration 类。 与嵌套的 @Configuration 类不同的是,嵌套的 @TestConfiguration 类是在你的应用程序的主要配置之外使用的,它将代替你的应用程序的主要配置。

Spring的测试框架在测试之间缓存了应用程序上下文。 因此,只要你的测试共享相同的配置(无论它是如何被发现的),加载上下文的潜在耗时过程只发生一次。

8.3.3. 使用 Test Configuration 的main方法

通常,由 @SpringBootTest 发现的 test configuration 将是你的main @SpringBootApplication。在大多数结构良好的应用程序中,这个配置类也将包括用于启动应用程序的main方法。

例如,以下是一个典型的Spring Boot应用程序的非常常见的代码模式。

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

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

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.docs.using.structuringyourcode.locatingthemainclass.MyApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

在上面的例子中,main 方法除了委托给 SpringApplication.run 之外没有做任何事情。 然而,在调用 SpringApplication.run 之前,可以有一个更复杂的 main 方法来应用自定义。

例如,这里有一个改变banner mode和设置额外profiles的应用程序。

Java
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.setAdditionalProfiles("myprofile");
        application.run(args);
    }

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.runApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args) {
        setBannerMode(Banner.Mode.OFF)
        setAdditionalProfiles("myprofile");
    }
}

由于在 main 方法中的定制可以影响产生的 ApplicationContext ,所以你有可能也想使用 main 方法来创建你测试中使用的 ApplicationContext。 默认情况下,@SpringBootTest 不会调用你的 main 方法,而是直接使用类本身来创建 ApplicationContext

如果你想改变这种行为,你可以将 @SpringBootTestuseMainMethod 属性改为 UseMainMethod.ALWAYSUseMainMethod.WHEN_AVAILABLE。 当设置为 ALWAYS 时,如果找不到 main 方法,测试将失败。 当设置为 WHEN_AVAILABLE 时,如果 main 方法可用,将被使用,否则将使用标准加载机制。

例如,下面的测试将调用 MyApplicationmain 方法,以创建 ApplicationContext。 如果main方法设置了额外的profile,那么当 ApplicationContext 启动时,这些profile(配置文件)将被激活。

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;

@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {

    @Test
    void exampleTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod
import org.springframework.context.annotation.Import

@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {

    @Test
    fun exampleTest() {
        // ...
    }

}

8.3.4. 排除测试配置(Excluding Test Configuration)

如果你的应用程序使用组件扫描(例如,如果你使用 @SpringBootApplication@ComponentScan),你可能会发现你只为特定测试创建的顶级配置类意外地被到处捡到(加载?)。

正如我们前面所看到的@TestConfiguration 可以用在一个测试的内部类上,以定制主要配置。当放在一个顶层类上时,@TestConfiguration 表示 src/test/java 中的类不应该被扫描加载。然后你可以在需要的地方明确地导入该类,如下面的例子所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {

    @Test
    void exampleTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Import

@SpringBootTest
@Import(MyTestsConfiguration::class)
class MyTests {

    @Test
    fun exampleTest() {
        // ...
    }

}
如果你直接使用 @ComponentScan(也就是不通过 @SpringBootApplication),你需要向它注册 TypeExcludeFilter。 详情见 Javadoc

8.3.5. 使用应用参数(Application arguments)

如果你的应用程序期望有参数,你可以让 @SpringBootTest 使用 args 属性注入它们。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {

    @Test
    void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
        assertThat(args.getOptionNames()).containsOnly("app.test");
        assertThat(args.getOptionValues("app.test")).containsOnly("one");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.test.context.SpringBootTest

@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,我们可以使用 MockMvcWebTestClient 来查询我们的Web端点,如以下例子所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {

    @Test
    void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
        mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
    }

    // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
    @Test
    void testWithWebTestClient(@Autowired WebTestClient webClient) {
        webClient
                .get().uri("/")
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("Hello World");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@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,如下例所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody

@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来验证响应,如下例所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody

@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 设施。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {

    @Test
    void exampleTest(@Autowired TestRestTemplate restTemplate) {
        String body = restTemplate.getForObject("/", String.class);
        assertThat(body).isEqualTo("Hello World");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.client.TestRestTemplate

@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都是与用于创建 WebTestClientWebTestClient.Builder 一起调用的。

8.3.9. 使用 JMX

由于测试context框架缓存了context,JMX默认是禁用的,以防止相同的组件在同一域注册。 如果这样的测试需要访问一个 MBeanServer,请考虑把它也标记为脏(dirty)。

Java
import javax.management.MBeanServer;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {

    @Autowired
    private MBeanServer mBeanServer;

    @Test
    void exampleTest() {
        assertThat(this.mBeanServer.getDomains()).contains("java.lang");
        // ...
    }

}
Kotlin
import javax.management.MBeanServer

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext

@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的一个测试注解(如 @SpringBootTest),这个功能会自动启用。 要用不同的安排来使用这个功能,必须明确地添加监听器,如下面的例子所示。

Java
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;

@ContextConfiguration(classes = MyConfig.class)
@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })
class MyTests {

    // ...

}
Kotlin
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners

@ContextConfiguration(classes = [MyConfig::class])
@TestExecutionListeners(
    MockitoTestExecutionListener::class,
    ResetMocksTestExecutionListener::class
)
class MyTests {

    // ...

}

下面的例子用一个模拟的实现替换了现有的 RemoteService Bean。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

@SpringBootTest
class MyTests {

    @Autowired
    private Reverser reverser;

    @MockBean
    private RemoteService remoteService;

    @Test
    void exampleTest() {
        given(this.remoteService.getValue()).willReturn("spring");
        String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService
        assertThat(reverse).isEqualTo("gnirps");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean

@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的代理,例如在使用 givenwhen 设置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 和任何 Jackson Module

  • Gson

  • Jsonb

@JsonTest 启用的自动配置列表可以在附录中找到。

如果你需要配置自动配置的元素,你可以使用 @AutoConfigureJsonTesters 注解。

Spring Boot包括基于AssertJ的helper,与JSONAssert和JsonPath库一起工作,以检查JSON是否符合预期。 JacksonTesterGsonTesterJsonbTesterBasicJsonTester 类可以分别用于Jackson、Gson、Jsonb和Strings。 当使用 @JsonTest 时,测试类上的任何helper字段都可以是 @Autowired。 下面的例子显示了一个Jackson的测试类。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;

import static org.assertj.core.api.Assertions.assertThat;

@JsonTest
class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

    @Test
    void serialize() throws Exception {
        VehicleDetails details = new VehicleDetails("Honda", "Civic");
        // Assert against a `.json` file in the same package as the test
        assertThat(this.json.write(details)).isEqualToJson("expected.json");
        // Or use JSON path based assertions
        assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
        assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
    }

    @Test
    void deserialize() throws Exception {
        String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
        assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
        assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.json.JsonTest
import org.springframework.boot.test.json.JacksonTester

@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

Java
@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)));
}
Kotlin
@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@JsonComponentConverterGenericConverterFilterHandlerInterceptorWebMvcConfigurerWebMvcRegistrations 以及 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
Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@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。

Java
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new VehicleDetails("Honda", "Civic"));
        HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
        assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
    }

}
Kotlin
import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.html.HtmlPage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean

@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@JsonComponentConverterGenericConverterWebFilterWebFluxConfigurer。 当使用 @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 来注解它。 下面的例子显示了一个同时使用 @WebFluxTestWebTestClient 的类。
Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.mockito.BDDMockito.given;

@WebFluxTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private WebTestClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() {
        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");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody

@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提供了一个专门的测试支持模块;你需要将其添加到你的项目中。

Maven
<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>
Gradle
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限制在 @ControllerRuntimeWiringConfigurerJsonComponentConverterGenericConverterDataFetcherExceptionResolverInstrumentationGraphQlSourceBuilderCustomizer。当使用 @GraphQlTest 注解时,常规的 @Component@ConfigurationProperties Bean 不会被扫描。 @EnableConfigurationProperties 可以用来包括(include)@ConfigurationProperties Bean。

附录中可以找到 @GraphQlTest 所启用的自动配置的列表。

通常,@GraphQlTest 仅限于一组 Controller,并与 @MockBean 注解结合使用,为所需的合作者提供模拟实现。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.docs.web.graphql.runtimewiring.GreetingController;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester;

@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!");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.docs.web.graphql.runtimewiring.GreetingController
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest
import org.springframework.graphql.test.tester.GraphQlTester

@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。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.graphql.test.tester.HttpGraphQlTester;

@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!");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.graphql.test.tester.HttpGraphQlTester
import org.springframework.http.HttpHeaders
import org.springframework.test.web.reactive.server.WebTestClient

@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测试的一个典型设置。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;

@DataCassandraTest
class MyDataCassandraTests {

    @Autowired
    private SomeRepository repository;

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest

@DataCassandraTest
class MyDataCassandraTests(@Autowired val repository: SomeRepository)

8.3.19. Couchbase 测试的自动配置

你可以使用 @DataCouchbaseTest 来测试Couchbase应用程序。 默认情况下,它配置一个 CouchbaseTemplateReactiveCouchbaseTemplate,扫描 @Document 类,并配置Spring Data Couchbase Repository。 当使用 @DataCouchbaseTest 注解时,常规的 @Component@ConfigurationProperties Bean不会被扫描。 @EnableConfigurationProperties 可以用来包括 @ConfigurationProperties Bean。 (关于在Spring Boot中使用Couchbase的更多信息,请参见本章前面的"data.html"。)

@DataCouchbaseTest 启用的自动配置设置的列表可以在在附录中找到

下面的例子显示了在Spring Boot中使用Couchbase测试的一个典型设置。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest;

@DataCouchbaseTest
class MyDataCouchbaseTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest

@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测试的一个典型设置。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest;

@DataElasticsearchTest
class MyDataElasticsearchTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest

@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框架参考文档中的 相关章节。 如果这不是你想要的,你可以为一个测试或整个类停用事务管理,如下所示。

Java
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {

    // ...

}
Kotlin
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {

    // ...

}

JPA测试也可以注入一个 TestEntityManager bean,它提供了一个标准JPA EntityManager 的替代品,是专门为测试设计的。

TestEntityManager 也可以通过添加 @AutoConfigureTestEntityManager 自动配置到任何基于Spring的测试类中。 当这样做时,确保你的测试在事务中运行,例如在你的测试类或方法上添加 @Transactional

如果你需要,也可以使用 JdbcTemplate。 下面的例子显示了 @DataJpaTest 注解的使用情况。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class MyRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    void testExample() {
        this.entityManager.persist(new User("sboot", "1234"));
        User user = this.repository.findByUsername("sboot");
        assertThat(user.getUsername()).isEqualTo("sboot");
        assertThat(user.getEmployeeNumber()).isEqualTo("1234");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager

@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 注解,如下面的例子所示。

Java
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {

    // ...

}
Kotlin
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest

@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框架参考文档中的 相关章节。 如果这不是你想要的,你可以为一个测试或整个类停用事务管理,如下所示。

Java
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests {

}
Kotlin
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests

如果你希望你的测试针对真实的数据库运行,你可以使用 @AutoConfigureTestDatabase 注解,方法与 DataJpaTest 一样。 (参见"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 注解的使用情况。

Java
import org.jooq.DSLContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jooq.JooqTest;

@JooqTest
class MyJooqTests {

    @Autowired
    private DSLContext dslContext;

    // ...

}
Kotlin
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jooq.JooqTest

@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 注解的使用情况。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoTemplate;

@DataMongoTest
class MyDataMongoDbTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest
import org.springframework.data.mongodb.core.MongoTemplate

@DataMongoTest
class MyDataMongoDbTests(@Autowired 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测试的一个典型设置。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;

@DataNeo4jTest
class MyDataNeo4jTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest

@DataNeo4jTest
class MyDataNeo4jTests(@Autowired val repository: SomeRepository) {

    // ...

}

默认情况下,Data Neo4j测试是在事务中进行的,并在每次测试结束后回滚。 详情请参见Spring框架参考文档中的 相关章节。 如果这不是你想要的,你可以为一个测试或整个类停用事务管理(transaction management),如下所示。

Java
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests {

}
Kotlin
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests
事务测试不支持响应式(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 注解的使用情况。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;

@DataRedisTest
class MyDataRedisTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest

@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 注解的使用情况。

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.ldap.core.LdapTemplate;

@DataLdapTest
class MyDataLdapTests {

    @Autowired
    private LdapTemplate ldapTemplate;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest
import org.springframework.ldap.core.LdapTemplate

@DataLdapTest
class MyDataLdapTests(@Autowired val ldapTemplate: LdapTemplate) {

    // ...

}

内存中的嵌入式 LDAP 通常对测试很有效,因为它速度快,而且不需要任何开发人员安装。 然而,如果你喜欢针对真实的LDAP服务器运行测试,你应该排除嵌入式LDAP的自动配置,如下面的例子所示。

Java
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;

@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
class MyDataLdapTests {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest

@DataLdapTest(excludeAutoConfiguration = [EmbeddedLdapAutoConfiguration::class])
class MyDataLdapTests {

    // ...

}

8.3.29. REST Client 的自动配置

你可以使用 @RestClientTest 注解来测试REST客户端。 默认情况下,它自动配置Jackson、GSON和Jsonb支持,配置 RestTemplateBuilder,并增加对 MockRestServiceServer 的支持。 当使用 @RestClientTest 注解时,常规的 @Component@ConfigurationProperties Bean不会被扫描。 @EnableConfigurationProperties 可以用来包括 @ConfigurationProperties Bean。

@RestClientTest 启用的自动配置设置的列表可以在在附录中找到

你想测试的特定Bean应该通过使用 @RestClientTestvaluecomponents 属性来指定,如以下例子所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {

    @Autowired
    private RemoteVehicleDetailsService service;

    @Autowired
    private MockRestServiceServer server;

    @Test
    void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
        this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
        String greeting = this.service.callRestService();
        assertThat(greeting).isEqualTo("hello");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest
import org.springframework.http.MediaType
import org.springframework.test.web.client.MockRestServiceServer
import org.springframework.test.web.client.match.MockRestRequestMatchers
import org.springframework.test.web.client.response.MockRestResponseCreators

@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时通常所做的那样,如以下例子所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void listUsers() throws Exception {
        this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andDo(document("list-users"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@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,如下例所示。

Java
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;

@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {

    @Override
    public void customize(MockMvcRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}
Kotlin
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer
import org.springframework.restdocs.templates.TemplateFormats

@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsMockMvcConfigurationCustomizer {

    override fun customize(configurer: MockMvcRestDocumentationConfigurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
    }

}

如果你想利用Spring REST Docs对参数化输出目录的支持,你可以创建一个 RestDocumentationResultHandler bean。 自动配置用这个 result handler 调用 alwaysDo,从而使每个 MockMvc 调用自动生成默认的片段(snippet)。 下面的例子显示了一个 RestDocumentationResultHandler 被定义。

Java
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;

@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {

    @Bean
    public RestDocumentationResultHandler restDocumentation() {
        return MockMvcRestDocumentation.document("{method-name}");
    }

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler

@TestConfiguration(proxyBeanMethods = false)
class MyResultHandlerConfiguration {

    @Bean
    fun restDocumentation(): RestDocumentationResultHandler {
        return MockMvcRestDocumentation.document("{method-name}")
    }

}
使用 WebTestClient 的 Spring REST Docs 测试的自动配置

@AutoConfigureRestDocs 也可以在测试响应式Web应用时与 WebTestClient 一起使用。 你可以通过使用 @Autowired 来注入它,并在你的测试中使用它,就像你在使用 @WebFluxTest 和Spring REST Docs时通常使用的那样,如下例所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;

@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void listUsers() {
        this.webTestClient
            .get().uri("/")
        .exchange()
        .expectStatus()
            .isOk()
        .expectBody()
            .consumeWith(document("list-users"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
import org.springframework.test.web.reactive.server.WebTestClient

@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,如下例所示。

Java
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer;

@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer {

    @Override
    public void customize(WebTestClientRestDocumentationConfigurer configurer) {
        configurer.snippets().withEncoding("UTF-8");
    }

}
Kotlin
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer

@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsWebTestClientConfigurationCustomizer {

    override fun customize(configurer: WebTestClientRestDocumentationConfigurer) {
        configurer.snippets().withEncoding("UTF-8")
    }

}

如果你想利用Spring REST Docs对参数化输出目录的支持,你可以使用 WebTestClientBuilderCustomizer 来为每个实体exchange result配置一个consumer。 下面的例子显示了这样一个 WebTestClientBuilderCustomizer 的定义。

Java
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer;
import org.springframework.context.annotation.Bean;

import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;

@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {

    @Bean
    public WebTestClientBuilderCustomizer restDocumentation() {
        return (builder) -> builder.entityExchangeResultConsumer(document("{method-name}"));
    }

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
import org.springframework.test.web.reactive.server.WebTestClient

@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时一样,如以下例子所示。

Java
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Test
    void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) {
        given(documentationSpec)
            .filter(document("list-users"))
        .when()
            .port(port)
            .get("/")
        .then().assertThat()
            .statusCode(is(200));
    }

}
Kotlin
import io.restassured.RestAssured
import io.restassured.specification.RequestSpecification
import org.hamcrest.Matchers
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.restdocs.restassured.RestAssuredRestDocumentation

@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,如以下例子所示。

Java
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;

@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer {

    @Override
    public void customize(RestAssuredRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}
Kotlin
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer
import org.springframework.restdocs.templates.TemplateFormats

@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 注解的使用。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest;
import org.springframework.ws.test.client.MockWebServiceServer;
import org.springframework.xml.transform.StringSource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.ws.test.client.RequestMatchers.payload;
import static org.springframework.ws.test.client.ResponseCreators.withPayload;

@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {

    @Autowired
    private MockWebServiceServer server;

    @Autowired
    private SomeWebService someWebService;

    @Test
    void mockServerCall() {
        this.server
            .expect(payload(new StringSource("<request/>")))
            .andRespond(withPayload(new StringSource("<response><status>200</status></response>")));
        assertThat(this.someWebService.test())
            .extracting(Response::getStatus)
            .isEqualTo(200);
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest
import org.springframework.ws.test.client.MockWebServiceServer
import org.springframework.ws.test.client.RequestMatchers
import org.springframework.ws.test.client.ResponseCreators
import org.springframework.xml.transform.StringSource

@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 注解的使用。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest;
import org.springframework.ws.test.server.MockWebServiceClient;
import org.springframework.ws.test.server.RequestCreators;
import org.springframework.ws.test.server.ResponseMatchers;
import org.springframework.xml.transform.StringSource;

@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {

    @Autowired
    private MockWebServiceClient client;

    @Test
    void mockServerCall() {
        this.client
            .sendRequest(RequestCreators.withPayload(new StringSource("<ExampleRequest/>")))
            .andExpect(ResponseMatchers.payload(new StringSource("<ExampleResponse>42</ExampleResponse>")));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest
import org.springframework.ws.test.server.MockWebServiceClient
import org.springframework.ws.test.server.RequestCreators
import org.springframework.ws.test.server.ResponseMatchers
import org.springframework.xml.transform.StringSource

@WebServiceServerTest(ExampleEndpoint::class)
class MyWebServiceServerTests(@Autowired 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,可以在每个测试的基础上添加额外的自动配置,如下例所示。

Java
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;

@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests {

}
Kotlin
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest

@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration::class)
class MyJdbcTests
请确保不要使用常规的 @Import 注解来导入自动配置,因为Spring Boot会以特定的方式处理这些配置。

或者,可以为切片(slice)注解的任何使用添加额外的自动配置,方法是将它们注册到存储在 META-INF/spring 中的文件中,如以下示例所示:

META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports
com.example.IntegrationAutoConfiguration

在这个例子中,com.example.IntegrationAutoConfiguration 在每个用 @JdbcTest 注解的测试中都被启用。

你可以在这个文件中使用带有 # 的注释。
只要用 @ImportAutoConfiguration 进行元注解,切片或 @AutoConfigure…​ 注解就可以用这种方式定制。

8.3.33. 用户配置和分片(Slice)

如果你以合理的方式构造你的代码,你的 @SpringBootApplication 类就会被默认为你测试的配置。

因此,不要在应用程序的 main 类中加入针对其功能的特定领域的配置设置,这一点变得很重要。

假设你正在使用Spring Data MongoDB,你依赖它的自动配置,并且你已经启用了审计。你可以这样定义你的 @SpringBootApplication

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@SpringBootApplication
@EnableMongoAuditing
public class MyApplication {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.data.mongodb.config.EnableMongoAuditing

@SpringBootApplication
@EnableMongoAuditing
class MyApplication {

    // ...

}

因为这个类是测试的源配置,任何切片测试实际上都试图启用Mongo审计,这绝对不是你想做的。 推荐的方法是将特定区域的配置移到与你的应用程序相同级别的单独的 @Configuration 类中,如下面的例子所示。

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
public class MyMongoConfiguration {

    // ...

}
Kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
class MyMongoConfiguration {

    // ...

}
根据你的应用程序的复杂性,你可以有一个单一的 @Configuration 类用于你的定制,或者每个领域区域有一个类。 后一种方法可以让你在测试中启用它,如果有必要的话,用 @Import 注解。 关于何时你可能想为切片测试启用特定的 @Configuration 类,请看这个 "how-to"部分,了解更多细节。

测试片(Test slice)从扫描中排除了 @Configuration 类 。 例如,对于 @WebMvcTest,以下配置将不包括测试片加载的应用程序上下文中的给定 WebMvcConfigurer Bean。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {

    @Bean
    public WebMvcConfigurer testConfigurer() {
        return new WebMvcConfigurer() {
            // ...
        };
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
class MyWebConfiguration {

    @Bean
    fun testConfigurer(): WebMvcConfigurer {
        return object : WebMvcConfigurer {
            // ...
        }
    }

}

然而,下面的配置将导致自定义的 WebMvcConfigurer 被测试片加载。

Java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    // ...

}
Kotlin
import org.springframework.stereotype.Component
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Component
class MyWebMvcConfigurer : WebMvcConfigurer {

    // ...

}

另一个迷惑的来源是classpath扫描。 假设在你以合理的方式结构化你的代码时,你需要扫描一个额外的包。 你的应用程序可能类似于下面的代码。

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan

@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测试中使用,如下所示:

Java
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

    @Test
    void myTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@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时,通过注解测试类中的容器字段,可以为运行在容器中的服务自动创建连接细节。

Java
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Container
    @ServiceConnection
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

    @Test
    void myTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@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 匹配的是

CassandraConnectionDetails

CassandraContainer 类型的容器。

CouchbaseConnectionDetails

CouchbaseContainer 类型的容器。

ElasticsearchConnectionDetails

ElasticsearchContainer 类型的容器。

FlywayConnectionDetails

JdbcDatabaseContainer 类型的容器。

JdbcConnectionDetails

JdbcDatabaseContainer 类型的容器。

KafkaConnectionDetails

KafkaContainerRedpandaContainer 类型的容器。

LiquibaseConnectionDetails

JdbcDatabaseContainer 类型的容器。

MongoConnectionDetails

MongoDBContainer 类型的容器。

Neo4jConnectionDetails

Neo4jContainer 类型的容器。

R2dbcConnectionDetails

MariaDBContainerMSSQLServerContainerMySQLContainer, OracleContainer, 或 PostgreSQLContainer 类型的容器。

RabbitConnectionDetails

RabbitMQContainer 类型的容器。

RedisConnectionDetails

命名为 "redis" 的容器。

ZipkinConnectionDetails

命名为 "openzipkin/zipkin" 的容器。

默认情况下,所有适用的连接细节 ban 将为一个给定的 Container 创建。例如,一个 PostgreSQLContainer 将同时创建 JdbcConnectionDetailsR2dbcConnectionDetails

如果你想只创建一个适用类型的子集,你可以使用 @ServiceConnectiontype 属性。

默认情况下, Container.getDockerImageName() 被用来获取用于查找连接细节的名称。如果你使用一个自定义的docker镜像,你可以使用 @ServiceConnectionname 属性来覆盖它。

例如,如果你有一个 GenericContainer,使用 registry.mycompany.com/mirror/myredis 的Docker镜像,你会使用 @ServiceConnection(name="redis") 来确保 RedisConnectionDetails 被创建。

8.4.2. 动态 Properties

与服务连接相比,@DynamicPropertySource 是一个稍显啰嗦但也更灵活的选择。一个静态的 @DynamicPropertySource 方法允许向Spring环境添加动态属性值。

Java
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

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

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@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(…​) 方法来启动真实的应用程序:

Java
import org.springframework.boot.SpringApplication;

public class TestMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).run(args);
    }

}
Kotlin
import org.springframework.boot.fromApplication

fun main(args: Array<String>) {
    fromApplication<MyApplication>().run(*args)
}

你还需要定义你想和你的应用程序一起启动的容器实例。要做到这一点,你需要确保 spring-boot-testcontainers 模块已被添加为 test 依赖。一旦这样做了,你就可以创建一个 @TestConfiguration 类,为你想启动的容器声明 @Bean 方法。

你也可以用 @ServiceConnection 来注解你的 @Bean 方法,以便创建 ConnectionDetails Bean。关于支持的技术的细节,请看上面的 服务连接 部分。

一个典型的 Testcontainers 配置会是这样的:

Java
import org.testcontainers.containers.Neo4jContainer;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {

    @Bean
    @ServiceConnection
    public Neo4jContainer<?> neo4jContainer() {
        return new Neo4jContainer<>("neo4j:5");
    }

}
Kotlin
import org.testcontainers.containers.Neo4jContainer

import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {

    @Bean
    @ServiceConnection
    fun neo4jContainer(): Neo4jContainer<*> {
        return Neo4jContainer("neo4j:5")
    }

}
Container bean 的生命周期由Spring Boot自动管理。容器将被自动启动和停止。

一旦你定义了你的测试配置,你可以使用 with(…​) 方法将其附加到你的测试启动器上:

Java
import org.springframework.boot.SpringApplication;

public class TestMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args);
    }

}
Kotlin
import org.springframework.boot.fromApplication
import org.springframework.boot.with

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 注解 的工作方式类似。它允许你添加那些一旦你的容器启动就会可用的属性。

一个典型的配置是这样的:

Java
import org.testcontainers.containers.MongoDBContainer;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistry;

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

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistry
import org.testcontainers.containers.MongoDBContainer

@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 接口声明了 mongoneo4j 容器:

import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;

public interface MyContainers {

    @Container
    MongoDBContainer mongoContainer = new MongoDBContainer("mongo:5.0");

    @Container
    Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5");

}

如果你已经有了以这种方式定义的容器,或者你只是喜欢这种风格,你可以导入这些声明类,而不是把你的容器定义为 @Bean 方法。要做到这一点,在你的测试配置类中添加 @ImportTestcontainers 注解:

Java
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.context.ImportTestcontainers;

@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers.class)
public class MyContainersConfiguration {

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.context.ImportTestcontainers

@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers::class)
class MyContainersConfiguration {

}
你可以在 Container 字段上使用 @ServiceConnection 注解来建立服务连接。你也可以在你的声明类中添加 @DynamicPropertySource 注解的方法
在开发时使用DevTools与Testcontainers

在使用 devtools 时,你可以用 @RestartScope 来注解Bean和Bean方法。当devtools重新启动应用程序时,这些Bean不会被重新创建。这对 Testcontainer Container Bean特别有用,因为它们在应用程序重启时仍能保持其状态。

Java
import org.testcontainers.containers.MongoDBContainer;

import org.springframework.boot.devtools.restart.RestartScope;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {

    @Bean
    @RestartScope
    public MongoDBContainer monogDbContainer() {
        return new MongoDBContainer("mongo:5.0");
    }

}
Kotlin
import org.springframework.boot.devtools.restart.RestartScope
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.MongoDBContainer

@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 提供的全套功能时,你可以使用它,如下面的例子所示。

Java
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {

    // ...

}
Kotlin
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
import org.springframework.test.context.ContextConfiguration

@ContextConfiguration(classes = [Config::class], initializers = [ConfigDataApplicationContextInitializer::class])
class MyConfigFileTests {

    // ...

}
单独使用 ConfigDataApplicationContextInitializer 并不提供对 @Value("${…​}") 的注入支持。 它唯一的工作是确保 application.properties 文件被加载到Spring的 Environment。 对于 @Value 支持,你需要额外配置一个 PropertySourcesPlaceholderConfigurer 或使用 @SpringBootTest,它可以为你自动配置一个。

8.5.2. TestPropertyValues

TestPropertyValues 让你快速添加属性到 ConfigurableEnvironmentConfigurableApplicationContext。 你可以用 key=value 字符串来调用它,如下所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;

import static org.assertj.core.api.Assertions.assertThat;

class MyEnvironmentTests {

    @Test
    void testPropertySources() {
        MockEnvironment environment = new MockEnvironment();
        TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
        assertThat(environment.getProperty("name")).isEqualTo("Boot");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.mock.env.MockEnvironment

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.outSystem.err 输出。 要使用它,请添加 @ExtendWith(OutputCaptureExtension.class) 并将 CapturedOutput 作为参数注入你的测试类构造函数或测试方法,如下所示。

Java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {

    @Test
    void testName(CapturedOutput output) {
        System.out.println("Hello World!");
        assertThat(output).contains("World");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension

@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 可以在你的集成测试中直接实例化,如下面的例子所示。

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;

class MyTests {

    private 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");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.web.client.TestRestTemplate

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_PORTWebEnvironment.DEFINED_PORT,你可以注入一个完整配置的 TestRestTemplate 并开始使用它。 如果有必要,可以通过 RestTemplateBuilder Bean 应用额外的定制功能。 任何没有指定主机和端口的URL都会自动连接到嵌入式服务器,如以下例子所示。

Java
import java.time.Duration;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests {

    @Autowired
    private TestRestTemplate template;

    @Test
    void testRequest() {
        HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
        assertThat(headers.getLocation()).hasHost("other.example.com");
    }

    @TestConfiguration(proxyBeanMethods = false)
    static class RestTemplateBuilderConfiguration {

        @Bean
        RestTemplateBuilder restTemplateBuilder() {
            return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
                .setReadTimeout(Duration.ofSeconds(1));
        }

    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import java.time.Duration

@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的列表所示:

Maven
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-docker-compose</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
Gradle
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}

当这个模块作为依赖被包含时,Spring Boot 将做以下工作:

  • 在你的应用程序目录中搜索 compose.yml 和其他常见的 compose 文件名

  • 用发现的 compose.yml 来调用 docker compose up

  • 为每个支持的容器创建服务连接bean。

  • 当应用程序关闭时调用 docker compose stop

docker composedocker-compose CLI应用程序需要在你的路径上,以便Spring Boot 的支持能够正常工作。

9.1. 服务连接

服务连接是与任何远程服务的连接。Spring Boot的自动配置可以消费服务连接的细节,并使用它们来建立与远程服务的连接。这样做的时候,连接细节优先于任何与连接相关的配置属性。

当使用Spring Boot的Docker Compose支持时,服务连接被建立到容器映射的端口。

Docker compose 的使用方式通常是将容器内的端口映射到你电脑上的短暂端口。例如,一个Postgres服务器可能在容器内使用5432端口运行,但在本地被映射到一个完全不同的端口。服务连接将始终发现并使用本地映射的端口。

服务连接是通过使用容器的镜像名称建立的。目前支持以下服务连接:

Connection Details 匹配于

CassandraConnectionDetails

命名为 "cassandra" 的容器。

ElasticsearchConnectionDetails

命名为 "elasticsearch" 的容器。

JdbcConnectionDetails

命名为 "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql" 或 "postgres" 的容器。

MongoConnectionDetails

命名为 "mongo" 的容器。

R2dbcConnectionDetails

命名为 "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql" 或 "postgres" 的容器。

RabbitConnectionDetails

命名为 "rabbitmq" 的容器。

RedisConnectionDetails

命名为 "redis" 的容器。

ZipkinConnectionDetails

命名为 "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.propertiesapplication.yaml 中使用 spring.docker.compose.file 来指向一个不同的文件。Properties 可以被定义为准确的路径或与你的应用程序相对的路径。

例如:

Properties
spring.docker.compose.file=../my-compose.yml
Yaml
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.propertiesapplication.yaml 文件中改变超时值:

Properties
spring.docker.compose.readiness.tcp.connect-timeout=10s
spring.docker.compose.readiness.tcp.read-timeout=5s
Yaml
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 startspring.docker.compose.stop.command 允许你配置是否使用 docker compose downdocker compose stop

下面的例子显示了如何配置生命周期管理:

Properties
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
Yaml
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.propertiesapplication.yaml 文件中使用 spring.docker.compose.profiles.active 属性:

Properties
spring.docker.compose.profiles.active=myprofile
Yaml
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 注解上的 beforebeforeNameafterafterName 属性或专用的 @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 类来隔离条件,如下面的例子所示。

Java
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

    }

}
Kotlin
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
// Some conditions ...
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 方法上时,目标类型默认为该方法的返回类型,如下面的例子所示。

Java
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }

}
Kotlin
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@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 被包含。 使用 prefixname 属性来指定应该被检查的属性。 默认情况下,任何存在且不等于 false 的属性都被匹配。 你也可以通过使用 havingValuematchIfMissing 属性创建更高级的检查。

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 总是被调用。

Java
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
Kotlin
val contextRunner = ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
如果必须定义多个自动配置,则不需要对它们的声明进行排序,因为它们被调用的顺序与运行应用程序时完全相同。

每个测试都可以使用runner来代表一个特定的用例。 例如,下面的例子调用了一个用户配置(UserConfiguration),并检查自动配置是否正常“退缩”。 调用 run 提供一个回调上下文,可与 AssertJ 一起使用。

Java
@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");
    }

}
Kotlin
@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,如下面的例子所示。

Java
@Test
void serviceNameCanBeConfigured() {
    this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
    });
}
Kotlin
@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。 报告可以在 INFODEBUG 级别打印。 下面的例子显示了如何使用 ConditionEvaluationReportLoggingListener 来打印自动配置测试的报告。

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

class MyConditionEvaluationReportingTests {

    @Test
    void autoConfigTest() {
        new ApplicationContextRunner()
            .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
            .run((context) -> {
                // Test something...
            });
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
import org.springframework.boot.logging.LogLevel
import org.springframework.boot.test.context.assertj.AssertableApplicationContext
import org.springframework.boot.test.context.runner.ApplicationContextRunner

class MyConditionEvaluationReportingTests {

    @Test
    fun autoConfigTest() {
        ApplicationContextRunner()
            .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
            .run { context: AssertableApplicationContext? -> }
    }

}

10.4.1. 模拟web上下文(Context)

如果你需要测试一个只在Servlet或响应式Web应用上下文中操作的自动配置,请分别使用 WebApplicationContextRunnerReactiveWebApplicationContextRunner

10.4.2. 覆盖Classpath

也可以测试当某个特定的class 和/或 package在运行时不存在时会发生什么。 Spring Boot提供了一个 FilteredClassLoader,runner可以轻松使用。 在下面的例子中,我们断言,如果 MyService 不存在,自动配置就会被正确地禁用。

Java
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
    this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
        .run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
Kotlin
@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使用的命名空间中(如 servermanagementspring 等)。 如果你使用相同的命名空间,我们可能会在未来修改这些命名空间,从而破坏你的模块。 作为一个经验法则,用你自己的命名空间作为你所有键的前缀(例如 acme)。

通过为每个属性添加字段javadoc,确保配置key被记录下来,如下面的例子中所示。

Java
import java.time.Duration;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("acme")
public class AcmeProperties {

    /**
     * Whether to check the location of acme resources.
     */
    private boolean checkLocation = true;

    /**
     * Timeout for establishing a connection to the acme server.
     */
    private Duration loginTimeout = Duration.ofSeconds(3);

    // getters/setters ...

    public boolean isCheckLocation() {
        return this.checkLocation;
    }

    public void setCheckLocation(boolean checkLocation) {
        this.checkLocation = checkLocation;
    }

    public Duration getLoginTimeout() {
        return this.loginTimeout;
    }

    public void setLoginTimeout(Duration loginTimeout) {
        this.loginTimeout = loginTimeout;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.Duration

@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的支持

Kotlin 是一种静态类型的语言,以JVM(和其他平台)为目标,可以编写简洁和优雅的代码,同时提供与现有的Java库的 互操作性

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-stdliborg.jetbrains.kotlin:kotlin-reflect 必须出现在classpath上。 也可以使用 kotlin-stdlib`变种 `kotlin-stdlib-jdk7kotlin-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声明可能会发生变化,甚至在小版本之间,未来可能会添加更多检查。

目前还不支持泛型参数、var args和数组元素的nullability。请参阅 SPR-15942 了解最新信息。还要注意的是,Spring Boot自己的API还 没有被注解

11.3. Kotlin API

11.3.1. runApplication

Spring Boot提供了一种习惯性的方法来运行应用程序,即 runApplication<MyApplication>(*args),如下例所示。

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

这是 SpringApplication.run(MyApplication::class.java, *args) 的一个直接替换。 它还允许对应用程序进行定制,如下面的例子所示。

runApplication<MyApplication>(*args) {
    setBannerMode(OFF)
}

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.2. 例子

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,如本例中所示:

Properties
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
Yaml
spring:
  ssl:
    bundle:
      jks:
        mybundle:
          key:
            alias: "application"
          keystore:
            location: "classpath:application.p12"
            password: "secret"
            type: "PKCS12"

当用来保证客户端连接的安全时,truststore 通常被配置为一个包含服务器证书的 Java KeyStore,如本例所示:

Properties
spring.ssl.bundle.jks.mybundle.truststore.location=classpath:server.p12
spring.ssl.bundle.jks.mybundle.truststore.password=secret
Yaml
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 通常配置有证书和私钥,如本例中所示:

Properties
spring.ssl.bundle.pem.mybundle.keystore.certificate=classpath:application.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=classpath:application.key
Yaml
spring:
  ssl:
    bundle:
      pem:
        mybundle:
          keystore:
            certificate: "classpath:application.crt"
            private-key: "classpath:application.key"

当用于保护客户端连接时,通常用服务器证书配置 truststore ,如本例中所示:

Properties
spring.ssl.bundle.pem.mybundle.truststore.certificate=classpath:server.crt
Yaml
spring:
  ssl:
    bundle:
      pem:
        mybundle:
          truststore:
            certificate: "classpath:server.crt"

参见 PemSslBundleProperties 以了解全部支持的属性。

12.3. 应用 SSL Bundle

一旦使用 properties 进行配置,SSL bundle 就可以在Spring Boot自动配置的各种类型的连接的配置属性中以名称来引用。更多信息请参见关于 嵌入式Web服务器数据技术REST 客户端 的章节。

12.4. 使用 SSL Bundle

Spring Boot 自动配置了一个 SslBundles 类型的Bean,提供对使用 spring.ssl.bundle 属性配置的每个命名的 bundle 的访问。

SslBundle 可以从自动配置的 SslBundles bean 中获取,并用于创建用于配置客户端库中 SSL 连接的对象。SslBundle 提供了一个获取这些SSL对象的分层方法:

  • getStores() 提供了对 key store 和 trust store java.security.KeyStore 实例以及任何所需 key store 密码的访问。

  • getManagers() 提供了对 java.net.ssl.KeyManagerFactoryjava.net.ssl.TrustManagerFactory 实例以及它们所创建的 java.net.ssl.KeyManagerjava.net.ssl.TrustManager 数组的访问。

  • createSslContext() 提供了一个方便的方法来获得一个新的 java.net.ssl.SSLContext 实例。

此外,SslBundle 还提供了关于正在使用的key、要使用的协议和任何应该应用于SSL引擎的选项的详细信息。

下面的例子显示了检索一个 SslBundle 并使用它来创建一个 SSLContext

Java
import javax.net.ssl.SSLContext;

import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {

    public MyComponent(SslBundles sslBundles) {
        SslBundle sslBundle = sslBundles.getBundle("mybundle");
        SSLContext sslContext = sslBundle.createSslContext();
        // do something with the created sslContext
    }

}
Kotlin
import org.springframework.boot.ssl.SslBundles
import org.springframework.stereotype.Component

@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的核心功能感到满意,你可以继续阅读有关生产就绪的功能。