7. 核心功能(Core Features)

7. 核心功能(Core Features)

本章节深入介绍 Spring Boot 的详细信息。在这里,你可以了解想要使用的和自定义的主要功能。如果您还没有阅读 “Getting Started” 和 “Developing with Spring Boot” 这两节内容,不放先去阅读这两节内容,以便对基础知识有一个很好的了解。

7.1. SpringApplication

SpringApplication 类为 Spring 应用程序提供了一种方便的引导方法,从 main() 方法启动。在很多情况下,你可以调用静态 SpringApplication.run 方法,如下所示:

@SpringBootApplication
public class MyApplication {

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

}

当您的应用程序启动时,您应该看到类似于以下输出的内容:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::      (v2.7.18-SNAPSHOT)

2023-11-22 15:39:20.275  INFO 4032 --- [           main] o.s.b.d.f.logexample.MyApplication       : Starting MyApplication using Java 1.8.0_392 on myhost with PID 4032 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-11-22 15:39:20.281  INFO 4032 --- [           main] o.s.b.d.f.logexample.MyApplication       : No active profile set, falling back to 1 default profile: "default"
2023-11-22 15:39:21.636  INFO 4032 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-11-22 15:39:21.649  INFO 4032 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-11-22 15:39:21.650  INFO 4032 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.83]
2023-11-22 15:39:21.735  INFO 4032 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-11-22 15:39:21.735  INFO 4032 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1401 ms
2023-11-22 15:39:22.214  INFO 4032 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-11-22 15:39:22.228  INFO 4032 --- [           main] o.s.b.d.f.logexample.MyApplication       : Started MyApplication in 2.437 seconds (JVM running for 2.834)

默认情况下,会显示 INFO 日志信息,包括一些相关的启动详细信息,如启动应用程序的用户。如果需要除 INFO 以外的日志级别,可以按照 Log Levels 中的说明进行设置。应用程序版本由主应用程序类包中的实现版本决定。将 spring.main.log-startup-info 设为 false,可以关闭启动信息日志记录。这也将关闭应用程序profiles配置文件的日志记录。

::: tip 提示
要在启动过程中添加额外的日志记录,可以重写 SpringApplication 子类中的logStartupInfo(boolean)方法。
:::

7.1.1 启动失败(Startup Failure)

如果应用程序启动失败,已注册的 "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.

::: tip 备注
Spring Boot 提供了大量 FailureAnalyzer 实现,你也可以 添加自己的实现
:::

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

例如,如果您使用 java -jar 运行应用程序,您可以按如下方法启用 debug 属性:

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

7.1.2 懒加载(Lazy Initialization)

SpringApplication 允许对应用程序进行懒加载。启动懒加载之后,应用程序将在需要时创建 bean,而不是在启动时创建。因此,启用懒加载可以缩短应用程序的启动时间。在Web应用程序中,启用懒加载将导致许多与Web相关的 Bean 在接收到 HTTP 请求之前不会被初始化。

懒加载的一个缺点是,它可能导致延迟发现应用程序的问题。如果对配置错误的 Bean 进行了懒加载,那么在启动过程中就不会再发生故障,而只有在初始化 Bean 时问题才会显现出来。此外,还必须注意确保 JVM 有足够的内存来容纳应用程序的所有 Bean,而不仅仅是那些在启动过程中初始化的 Bean。因此,默认情况下不会启用懒加载,建议在启用懒加载之前对 JVM 的堆大小进行微调。

可以使用SpringApplicationBuilder上的lazyInitialization方法或SpringApplication上的setLazyInitialization方法,以编程方式启用懒加载。或者,也可以使用spring.main.lazy-initialization属性启用,如下例所示:

spring:
  main:
    lazy-initialization: true

::: tip 提示
如果您想对某些 Bean 禁用懒加载,同时对应用程序的其他部分使用懒加载,您可以使用 @Lazy(false) 注解显式地将它们的 lazy 属性设置为 false。
:::

7.1.3 自定义Banner(Customizing the Banner)

启动时打印的 banner 可以通过在类路径中添加 "banner.txt "文件或将 "spring.banner.location "属性设置为该文件的位置来更改。如果文件的编码不是 UTF-8,可以设置 spring.banner.charset 属性。除文本文件外,还可以在类路径中添加 banner.gifbanner.jpgbanner.png图像文件,或设置 spring.banner.image.location 属性。图像会转换成 ASCII 艺术表现形式,并打印在任何文字 banner 之上。

banner.txt文件中,您可以使用Environment中的任何可用键以及以下任何占位符:

VariableDescription
${application.version}MANIFEST.MF 中声明的应用程序版本号。例如 Implementation-Version: 1.0 打印为 1.0
${application.formatted-version}应用程序的版本号,在 MANIFEST.MF 中声明并格式化显示(用括号包围并以 v 为前缀)。例如 (v1.0)
${spring-boot.version}您使用的 Spring Boot 版本。例如 2.7.18-SNAPSHOT
${spring-boot.formatted-version}您正在使用的 Spring Boot 版本,显示格式为(用括号包围,前缀为 v)。例如 (v2.7.18-SNAPSHOT)
${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME})其中NAME是ANSI转义码的名称。详情 AnsiPropertySource请见。
${application.title}MANIFEST.MF 中声明的应用程序标题。例如,Implementation-Title: MyApp 打印为 MyApp

::: tip 提示

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

:::

您还可以使用spring.main.banner-mode属性来决定是否要将banner打印到System.out (console)、发送到配置的日志记录器 (log),或者根本不打印 (off)。

打印的banner将作为单例 Bean 注册,名称如下:springBootBanner

::: tip 备注

application.title, application.version, 和 application.formatted-version 属性仅在使用 Spring Boot 启动程序的 java -jarjava -cp 命令时可用。如果运行未打包的 jar 并使用 java -cp <classpath> <mainclass> 启动,这些值将不会被解析。要使用 application. 配置,使用java -jar运行jar包或者使用 java org.springframework.boot.loader.JarLauncher运行非jar。这将在构建 classpath 和启动应用程序之前初始化 application. banner 属性

::: tip

7.1.4 自定义SpringApplication(Customizing SpringApplication)

如果 "SpringApplication "的默认设置不符合你的需求,你可以创建一个本地实例并对其进行自定义。例如,要关闭横banner,如下所示:

@SpringBootApplication
public class MyApplication {

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

}

::: tip 提示

传递给SpringApplication的构造函数参数是Spring Bean的配置源。大多数情况下,这些参数是对 @Configuration 类的引用,但也可能是对 @Component 类的直接引用。

::: tip

也可以使用 "application.properties "文件配置 “SpringApplication”。详情参阅 Externalized Configuration

有关配置选项的完整列表,请参阅 SpringApplication Javadoc

7.1.5 Fluent Builder API

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

如下面的例子所示,SpringApplicationBuilder可以将多个方法调用串联起来,其中的parentchild方法可以让你创建一个层次结构:

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

::: tip 备注

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

::: tip

7.1.6 应用可用性(Application Availability)

在平台上部署应用程序时,应用程序可以使用Kubernetes Probes等基础设施向平台提供有关其可用性的信息。Spring Boot 包含对常用的 "liveness "和 "readiness "可用性状态的支持,开箱即用。如果您使用的是 Spring Boot 是 "actuator "支持,那么这些状态就会作为健康端点组暴露出来。

此外,您还可以通过实现 ApplicationAvailability 接口注入自己的bean来获取可用性状态。

运行状态(Liveness State)

应用程序的 "运行 "状态说明其内部状态是否允许其正常工作,或在当前失败的情况下自行恢复。损坏的 "运行 "状态意味着应用程序处于无法恢复的状态,基础架构应重新启动应用程序。

::: tip 备注

一般来说,“运行”状态不应该基于外部检测,例如 健康检测。如果这样做,外部系统(数据库、Web API、外部缓存)的故障就会在整个平台上引发大规模的重启和连锁故障。

:::

Spring Boot 应用程序的内部状态主要由 Spring ApplicationContext 表示。如果应用程序上下文已成功启动,Spring Boot 就认为应用程序处于有效状态。一旦上下文被刷新,应用程序即被视为已激活,详情参阅 Spring Boot 应用程序生命周期和相关应用程序事件

就绪状态(Readiness State)

应用程序的 "就绪 "状态说明应用程序是否已准备好处理请求流量。错误的 "就绪 "状态会告诉平台暂时不应将请求转发到应用程序。这种情况通常发生在启动过程中、CommandLineRunnerApplicationRunner 组件正在处理过程中时,或者在应用程序认为太忙无法处理额外请求的时候。

一旦应用程序和命令行运行程序被调用,应用程序即被视为准备就绪,请参阅 Spring Boot 应用程序生命周期和相关应用程序事件

::: tip 提示

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

:::

管理应用可用性状态(Managing the Application Availability State)

应用程序组件可以通过注入 ApplicationAvailability 接口并调用其上的方法,随时检索当前的可用性状态。更常见的情况是,应用程序希望监听状态更新或更新应用程序的状态。

例如,我们可以将应用程序的 "就绪 "状态导出到一个文件,这样 Kubernetes 的 "执行探针 "就可以查看该文件:

@Component
public class MyReadinessStateExporter {

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

}

当应用程序发生故障而无法恢复时,我们还可以更新应用程序的状态:

@Component
public class MyLocalCacheVerifier {

    private final ApplicationEventPublisher eventPublisher;

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

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

}

Spring Boot 提供 利用 Actuator Health Endpoint 对 Kubernetes HTTP 进行 "Liveness "和 "Readiness "探测.。你可以在 在 Kubernetes 上部署 Spring Boot 应用程序的专门部分获取更多的指定。

7.1.7 应用程序事件和监听器(Application Events and Listeners)

除了常用的 Spring Framework 事件外,例如 ContextRefreshedEventSpringApplication 还会发送一些额外的应用程序事件。

::: tip 备注

有些事件实际上是在创建应用程序上下文 ApplicationContext 之前触发的,因此你不能使用 @Bean来注册这些事件的监听器。你可以使用SpringApplication.addListeners(…) 方法或者 SpringApplicationBuilder.listeners(…) 方法来进行注册。 如果你想让这些监听器自动注册,而不管应用程序是如何创建的,你可以在项目中添加一个 META-INF/spring.factories 文件,并使用 org.springframework.context.ApplicationListener 作为key来引用监听器,例如: org.springframework.context.ApplicationListener=com.example.project.MyListener

:::

应用程序运行时,应按以下顺序发送应用程序事件:

  1. 除了注册监听器和初始化程序之外,在运行开始时且任何处理之前发送应用程序启动事件(ApplicationStartingEvent)。
  2. 当在上下文中使用的Environment已知时,且在创建上下文之前,将发送应用环境准备事件ApplicationEnvironmentPreparedEvent
  3. ApplicationContext 已经准备就绪且 ApplicationContextInitializers 已经被调用,但是尚未加载任何定义的 Bean 时,将发送ApplicationContextInitializedEvent
  4. 在刷新开始之前,但是在加载定义的 Bean 之后,将发送 ApplicationPreparedEvent
  5. 在上下文刷新之后,但是在调用任何应用程序和命令行运行程序之前,会发送 ApplicationStartedEvent
  6. 紧接着会发生一个 AvailabilityChangeEvent (可用性更改事件),并带有 LivenessState.CORRECT,以表示应用程序是处于运行状态的。
  7. 在调用任何 应用程序和命令行运行程序 之后,将发送 ApplicationReadyEvent
  8. 紧接着会发送 AvailabilityChangeEvent(可用性更改事件),并带有 ReadinessState.ACCEPTING_TRAFFIC ,表示应用程序已经为请求提供服务做好准备。
  9. 如果启动时出现异常,则会发送 ApplicationFailedEvent

上述列表只包括SpringApplication绑定的 SpringApplicationEvent。除此之外,以下事件也会在 ApplicationPreparedEvent 之后、ApplicationStartedEvent之前发布:

  • WebServer 准备就绪后,会发送 WebServerInitializedEventServletWebServerInitializedEventReactiveWebServerInitializedEvent分别是 servlet 和 reactive 的变体。
  • 当刷新ApplicationContext时,会发送 ContextRefreshedEvent

::: tip 提示

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

:::

::: tip 备注

事件监听器不应运行潜在的冗长任务,因为它们默认在同一线程中执行。可以考虑使用 application and command-line runners 来代替。

:::

应用程序事件是通过 Spring Framework 的事件发布机制发送的。该机制的部分功能是确保向子上下文中的监听器发布的事件也会向任何祖先上下文中的监听器发布。因此,如果您的应用程序使用SpringApplication 实例的层次结构,监听器可能会接收到同一类型应用程序事件的多个实例。

为使监听器能够区分其上下文的事件和后代上下文的事件,监听器应请求注入其应用程序上下文,然后将注入的上下文与事件的上下文进行比较。可以通过实现 ApplicationContextAware 来注入上下文,如果监听器是一个 Bean,则可以通过使用 @Autowired来注入上下文。

7.1.8 Web环境(Web Environment)

SpringApplication 会尝试代表你创建正确的ApplicationContext。用于确定WebApplicationType的算法非常简单:

  • 如果 Spring MVC 存在,则使用 AnnotationConfigServletWebServerApplicationContext
  • 如果 Spring MVC 不存在但是 Spring WebFlux 存在,则使用 AnnotationConfigReactiveWebServerApplicationContext
  • 否则,使用 AnnotationConfigApplicationContext

这意味着,如果在同一应用程序中使用 Spring MVC 和 Spring WebFlux 的 WebClient,则默认使用 Spring MVC。您可以通过调用 setWebApplicationType(WebApplicationType) 方法轻松覆盖该功能。

通过调用 setApplicationContextFactory(...),还可以完全控制使用的 ApplicationContext 类型。

::: tip 提示

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

:::

7.1.9 访问应用程序参数(Accessing Application Arguments)

如果需要访问传递给SpringApplication.run(...)的应用程序参数,可以注入一个org.springframework.boot.ApplicationArguments bean。如下所示,ApplicationArguments接口既能访问原始的String[]参数,也能访问解析后的 optionnon-option参数:

@Component
public class MyBean {

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

}

::: tip 提示

Spring Boot 还会在 Spring Environment 中注册 CommandLinePropertySource 。通过使用 @Value 注解,您还可以注入单个应用程序参数。

:::

7.1.10 使用ApplicationRunner或CommandLineRunner(Using the ApplicationRunner or CommandLineRunner)

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

::: tip 备注

这种约定非常适合在应用程序启动后、开始接受请求前运行的任务。

:::

CommandLineRunner 接口以字符串数组的形式访问应用程序参数,而 ApplicationRunner 则使用 ApplicationArguments 接口作为参数访问应用程序参数。如下所示,展示了 CommandLineRunnerrun 方法:

@Component
public class MyCommandLineRunner implements CommandLineRunner {

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

}

如果定义了多个必须按特定顺序调用的 CommandLineRunnerApplicationRunner Bean,您可以额外实现 org.springframework.core.Ordered 接口或使用 org.springframework.core.annotation.Order 注解。

7.1.11 应用退出(Application Exit)

每个 SpringApplication 都会向 JVM 注册一个 shutdown hook,以确保 ApplicationContext 在退出时可以正常关闭。所有的标准 Spring 生命周期回调(如DisposableBean 接口或 @PreDestroy 注解)都可以使用。

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

@SpringBootApplication
public class MyApplication {

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

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

}

此外,ExitCodeGenerator接口可由exceptions实现。遇到此类exception时,Spring Boot 会返回由已实现的 getExitCode() 方法提供的退出代码。

如果有多个 ExitCodeGenerator 方法,则使用最先生成的非零退出代码。要控制生成器的调用顺序,可额外实现 org.springframework.core.Ordered 接口或使用 org.springframework.core.annotation.Order 注解。

7.1.12 管理员功能(Admin Features)

通过指定spring.application.admin.enabled 属性,可以启用应用程序的管理相关功能。将在平台 MBeanServer 上公开 SpringApplicationAdminMXBean。 您可以使用此功能远程管理 Spring Boot 应用程序。该功能对于任何服务包装器的实现也很有用。

::: tip 提示

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

:::

7.1.13 应用程序启动跟踪(Application Startup tracking)

在应用程序启动期间,SpringApplicationApplicationContext会执行许多与应用程序生命周期、Bean生命周期甚至处理应用程序事件相关的任务。通过 ApplicationStartup,Spring Framework 允许你使用 StartupStep 对象跟踪应用程序的启动顺序。收集这些数据的目的是用于剖析,或者为了更好地理解应用程序的启动过程。

当设置 SpringApplication 实例时,你可以选择一个 ApplicationStartup 实现。例如,如果需要使用 BufferingApplicationStartup,代码如下所示:

@SpringBootApplication
public class MyApplication {

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

}

Spring Framework 提供了第一个可用的实现 FlightRecorderApplicationStartup。它将 Spring 特有的启动事件添加到 Java Flight Recorder 会话中,用于剖析应用程序,并将其 Spring 上下文生命周期与 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文档的形式提供信息。

7.2 外部配置(Externalized Configuration)

Spring Boot 允许您使用外部化配置,以便您可以在不同的环境中使用相同的应用程序代码。你可以使用属性文件、YAML文件、环境变量、命令行参数来外部化配置。

Property 值可以通过 @Value 注解直接注入到Bean中,通过 Spring 的 Environment 抽象访问,或者通过@ConfigurationProperties 绑定到结构化对象 中。

Spring Boot 使用一种非常特殊的 PropertySource 顺序,其目的允许对值进行合理的覆盖。后面的配置属性源可以覆盖前面的配置属性源中定义的值。配置属性源按以下顺序加载:

  1. 默认属性(通过设置 SpringApplication.setDefaultProperties指定)。
  2. @Configuration 类上添加@PropertySource 注解。请注意,在应用程序上下文刷新之前,此类属性源不会加载到 Environment 中。这对于某些配置属性(例如在刷新开始之前读取的logging.*spring.main.* )来说为时已晚。
  3. 配置数据(如application.properties文件)。
  4. RandomValuePropertySource 仅在 random.* 类型配置中使用。
  5. OS 环境变量。
  6. Java 系统属性 (System.getProperties())。
  7. 来之 java:comp/env 的 JNDI。
  8. ServletContext init 参数。
  9. ServletConfig init 参数。
  10. 来自 SPRING_APPLICATION_JSON 的属性(嵌入在环境变量或系统变量中的内联JSON)。
  11. 命令行参数。
  12. 测试中的properties。测试注解 @SpringBootTest 用于测试应用程序的特定片段 可用。
  13. 测试中的注解@DynamicPropertySource
  14. 测试中的注解@TestPropertySource
  15. 当 devtools 激活时,位于 $HOME/.config/spring-boot 目录的Devtools 全局配置属性.

配置文件按以下顺序加载:

  1. 打包在 jar 中的 应用程序配置application.properties 和 YAML 配置文件)。
  2. 打包在 jar 中的 特定 Profile 的应用程序配置application-{profile}.properties 和 YAML配置)。
  3. jar之外的 应用程序配置application.properties 和 YAML配置)。
  4. jar之外的 特定 Profile 的应用程序配置application-{profile}.properties 和 YAML配置)。

::: tip 备注

建议在整个应用程序中只使用一种格式。如果在同一位置同时使用 .properties 和 YAML 格式的配置文件,则 .properties 优先。

:::

::: tip 备注

如果使用环境变量而不是系统属性,大多数操作系统不允许使用以句点分隔的键名,但可以使用下划线代替(例如,用 SPRING_CONFIG_NAME 代替 spring.config.name )。详情参阅 Binding From Environment Variables

:::

::: tip 备注

如果您的应用程序在 servlet 容器或应用程序服务器中运行,则可以使用 JNDI 属性(在 java:comp/env 中)或 servlet 上下文初始化参数来代替,与环境变量和系统属性一样使用。

:::

如下例所示:

@Component
public class MyBean {

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

    // ...

}

在应用程序的 classpath 中(例如,在 jar 中),可以有一个 application.properties 文件,为 name 提供合理的默认属性值。在新环境中运行时,可以在 jar 之外提供一个application.properties文件来覆盖name。对于一次性测试,可以使用特定的命令行开关启动(例如,java -jar app.jar --name="Spring" )。

::: tip 提示

envconfigprops 端点有助于确定属性具有特定值的原因。您可以使用这两个端点来诊断意外的属性值。详见"生产就绪功能 "部分。

:::

7.2.1 访问命令行属性(Accessing Command Line Properties)

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

如果不想将命令行属性添加到 Environment,可以使用SpringApplication.setAddCommandLineProperties(false)

7.2.2 JSON格式应用程序属性(JSON Application Properties)

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

当应用程序启动时,任何spring.application.jsonSPRING_APPLICATION_JSON属性都将被解析并添加到 Environment

例如,可以在UN*X shell的命令行中将 SPRING_APPLICATION_JSON 属性作为环境变量使用:

$ 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 变量。

::: tip 备注

虽然,JSON格式中的 null 值将被添加到生成的属性中,但是 PropertySourcesPropertyResolvernull 属性视为缺失值。这意味着JSON无法使用 null 值覆盖优先级比较低的属性源中的属性。

:::

7.2.3 外部应用程序属性(External Application Properties)

Spring Boot 会从应用程序启动时自动从以下位置查找并加载 application.propertiesapplication.yaml 文件:

  1. 从类路径加载
    1. 类路径根目录
    2. 类路径下的 /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

::: tip 提示

如果位置是可选的,且您不介意它们不存在,可以使用前缀 optional:

:::

::: warning 警告

spring.config.name, spring.config.location, 和 spring.config.additional-location很早就用于确定哪些文件需要加载。它们必须定义为环境属性(通常是操作系统环境变量、系统属性或命令行参数)。

:::

如果spring.config.location 包含目录(而不是文件),则应该以 / 结尾。在运行时,它们会在加载前被附加上 spring.config.name 配置的文件名称。在 spring.config.location 中 特定 profile 配置文件会被直接使用。

::: tip 提示

目录和文件位置值还会进行扩展,以检查 特定 profile 文件,例如,如果spring.config.locationclasspath:myconfig.properties,则还会加载classpath:myconfig-<profile>.properties文件。

:::

在大多数情况下,你添加的每个 spring.config.location 项都会引用一个文件或目录。位置会按照定义的顺序进行处理,后面的位置可以覆盖前面位置的值。

如果您有复杂的位置配置,并且使用了特定profile的配置文件,您可能需要提供进一步的提示,以便 Spring Boot 知道应如何对它们进行分组。位置组是在同一级别考虑的位置的集合。例如,您可能想分组所有 classpath 位置,然后是所有外部位置。位置组内的项目应以"; "分隔。更多详情,请参阅"配置文件特定文件 "部分的示例。

使用 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.additional-location 。从附加位置加载的属性可以覆盖默认位置中的属性。例如,如果 spring.config.additional-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配置的文件名)中为应用程序提供默认值。这些默认值可以在运行时,使用自定义位置上的不同文件覆盖。

可选位置(Optional Locations)

默认情况下,如果指定的配置数据位置不存在,Spring Boot 将抛出 ConfigDataLocationNotFoundException 异常,应用程序将无法启动。

如果您想指定一个位置,但又不介意它可能不存在,可以使用 optional: 前缀。你可以在 spring.config.locationspring.config.additional-location 属性以及 spring.config.import 声明中使用该前缀。

例如,spring.config.import的值为optional:file:./myconfig.properties,如果myconfig.properties并不存在,则仍然允许应用程序启动。

如果想忽略所有 ConfigDataLocationNotFoundException 并始终继续启动应用程序,可以使用 spring.config.on-not-found 属性。使用 SpringApplication.setDefaultProperties(...)或系统/环境变量将值设为ignore

通配符位置(Wildcard Locations)

如果配置文件位置的最后一个路径段包含*字符,则视为通配符位置。通配符会在加载配置时展开,因此直接子目录也会被检查。在 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.additional-location 属性上使用通配符位置。

::: tip 备注

通配符位置必须只包含一个 *,并且在搜索目录位置时以 */ 结尾,在搜索文件位置时以 */<filename> 结尾。带有通配符的位置会根据文件名的绝对路径按字母顺序排序。

:::

::: tip 提示

通配符位置只适用于外部目录。不能在 classpath: 位置中使用通配符。

:::

特定Profile配置文件(Profile Specific Files)

除了 application 配置文件外,Spring Boot 还会尝试加载命名为 application-{profile}的特定 profile 的配置文件。例如,如果您的应用程序配置了profile 为 prod 并使用 YAML 文件,那么 application.yamlapplication-prod.yaml 都将会被查找。

特定 profile 的配置文件和标准的 application.properties从相同位置加载,特定 profile 的配置文件总是优于非特定的配置文件。如果指定了多个配置文件,则采用last-wins策略。例如,如果通过spring.profiles.active属性指定了 prod,live 配置文件,则 application-prod.properties 中的值可能会被 application-live.properties中的值覆盖。

::: tip 备注

last-wins策略适用于 location group 级别。spring.config.location 配置的 classpath:/cfg/,classpath:/ext/classpath:/cfg/;classpath:/ext/的覆盖规则不同。

例如,继续上面的 prod,live 示例,我们可能有以下文件:

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

spring.config.location 属性值为 classpath:/cfg/,classpath:/ext/ 时,我们会先处理 /cfg 下面的文件,然后在处理 /ext 下面的文件:

  1. /cfg/application-live.properties
  2. /ext/application-prod.properties
  3. /ext/application-live.properties

当我们使用 classpath:/cfg/;classpath:/ext/ 时(带有 ; 分隔符),我们会在同一级别处理 /cfg/ext,按照上面示例中spring.profiles.active=prod,live 处理:

  1. /ext/application-prod.properties

  2. /cfg/application-live.properties

  3. /ext/application-live.properties

:::

Environment 有一组默认 profiles 的配置文件(默认为 [default]),如果没有设置激活的 profiles 配置。就会使用这些配置文件。换句话说,如果没有显式激活配置文件,则会考虑使用 application-default.properties 的属性。

::: tip 备注

属性文件只加载一次。如果已经直接 导入了某个特定 profile 配置文件吗,则不会再导入第二次。

:::

导入其他数据(Importing Additional Data)

应用程序可以使用spring.config.import属性从其它位置导入更多的配置数据。 导入文件配置在被应用程序检测到时就会被处理,并作为紧接在声明导入文件配置的文件下面插入导入文件数据。

例如,你的类路径下的 application.properties 可能有以下内容:

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

这将触发导入当前目录下的 dev.properties 文件(如果存在此类文件)。导入的 dev.properties 文件中属性值将由于声明导入配置的application.properties文件。在上例中,dev.propertiesspring.application.name重新定义为不同的值。

无论声明多少次,导入都只会被导入一次。在 properties/yaml 文件的单个文档中定义导入的顺序并不重要。例如,下面两个示例产生的结果是一样的:

spring:
  config:
    import: "my.properties"
my:
  property: "value"
my:
  property: "value"
spring:
  config:
    import: "my.properties"

在上述两个示例中,my.properties文件中的属性值将优先于声明导入配置的文件。

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

::: tip 备注

适当的时候, 特定 Profile 配置文件 也可能会被导入。上述示例将同时导入 my.properties 和任何 my-<profile>.properties

:::

::: tip 备注

Spring Boot 包括可插拔的API,允许支持各种不同位置地址。默认情况下,您可以导入 Java 属性、YAML 和 “配置树”。第三方jar可以提供对其它技术的支持(不要求文件是本地的)。例如,您可以假定配置数据来自外部存储,例如 Consul、Apache ZooKeeper 或 Netflix Archaius。如果你想支持自己的位置地址,请参阅org.springframework.boot.context.config包中的 ConfigDataLocationResolver 类和 ConfigDataLoader 类。

:::

导入非扩展文件(Importing Extensionless Files)

某些云平台无法为挂载卷的文件添加文件扩展名。要导入这些无扩展名的文件,需要给 Spring Boot 一个提示,以便它指定如何加载这文件。为此,您可以将扩展名放到方括号里面。

例如,假设您有一个 /etc/config/myconfig 文件,希望以 yaml 格式导入。在application.properties的配置如下所示:

spring:
  config:
    import: "file:/etc/config/myconfig[.yaml]"
使用配置树(Using Configuration Trees)

在云平台(如 Kubernetes)上运行应用程序时,您经常需要读取平台提供的配置值。为此目的而使用环境变量的情况并不少见,但这样做也有缺点,尤其是当配置值需要保密时。

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

有两种常见的挂载卷模式可供选择:

  1. 包含一整套属性的单个文件(通常写成 YAML)。
  2. 多个文件被写入一个目录树,文件名成为 “键”,内容成为 “值”。

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

举个例子,假设 Kubernetes 挂载了以下卷:

etc/
  config/
    myapp/
      username
      password

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

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

spring:
  config:
    import: "optional:configtree:/etc/config/"

然后,你就可以从 Environment 中访问或注入 myapp.usernamemyapp.password 属性。

::: tip 提示

配置树下的文件夹和文件名构成了属性名称。在上面示例中,若要以 usernamepassword 的形式访问属性,可将 spring.config.import 设置为 optional:configtree:/etc/config/myapp

:::

::: tip 备注

使用点符号的文件名也能正确映射。例如,在上例中,/etc/config中名为myapp.username的文件将会映射到Environment中的myapp.username属性。

:::

::: tip 提示

配置树值可根据预期内容绑定到 Stringbyte[] 类型。

:::

如果要从同一父文件夹导入多个配置树,可以使用通配符。任何以/*/结尾的 configtree: 位置都会将所有的直接子文件夹导入成配置树。与非通配符导入一样,每个配置树下的文件夹和文件名构成了属性名。

例如,给定以下挂载卷:

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

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

spring:
  config:
    import: "optional:configtree:/etc/config/*/"

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

::: tip 提示

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

:::

配置树可以用于 Docker secrets。当 Docker swarm 服务允许获取某个 secret 时,这个 secret 就会被挂载到容器中。例如,如果一个名为 db.password 的secret 被挂载到 /run/secrets/ 中,你就可以使用以下方法将 db.password 提供给 Spring 环境:

spring:
  config:
    import: "optional:configtree:/run/secrets/"
属性占位符(Property Placeholders)

application.propertiesapplication.yaml 中的值在使用时会根据现有 Environment 进行过滤,因此,您可以引用以前定义的值(例如,从系统属性或环境变量)。标准的 ${name} 属性占位符语法可以用于值的任何位置。属性占位符还可以指定默认值,使用 : 分隔默认值和属性名,例如 ${name:default}

有默认值和无默认值占位符的使用方法见下例:

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.

::: tip 备注

您应始终使用其规范形式(kebab-case,仅使用小写字母)来引用占位符中的属性名称。这将允许Spring Boot 使用与 relaxed binding @ConfigurationProperties 时相同的逻辑。例如, ${demo.item-price} 将从application.properties文件中获取 demo.item-pricedemo.itemPrice 形式的数据,并从环境变量中获取 DEMO_ITEMPRICE
。如果使用 ${demo.itemPrice}demo.item-priceDEMO_ITEMPRICE 将不会生效。

:::

::: tip 提示

您还可以使用此技术创建现有 Spring Boot 属性的 "简短 "变量。详见 Use ‘Short’ Command Line Arguments

:::

处理多文档文件(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

::: tip 备注

配置文件分隔符不能有任何前导空白,必须有三个连字符。分隔符前后的行不能是相同的注释前缀。

:::

::: tip 提示

多文档配置文件通常与 spring.config.activate.on-profile 等激活属性结合使用。详见 下一节

:::

::: warning 警告

使用 @PropertySource@TestPropertySource 注解无法加载多文档配置文件。

:::

激活特性(Activation Properties)

有时,只有在满足特定条件时才激活特定的属性集是非常有用的。例如,可能有一些属性只有在特定的配置文件处于激活状态时才相关联。

你可以使用 spring.config.activate.* 有条件地激活配置属性文件。

以下激活配置可用:

PropertyNote
on-profile配置文件表达式,必须匹配该表达式,文档才能处于活动状态。
on-cloud-platform文档激活时必须检测的 CloudPlatform

例如,下面示例中的第二个逻辑文档只有在 Kubernetes 上运行时,而且只有在spring.profiles.active或者spring.profiles.include为 "prod "或 "staging "时才会激活:

myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

7.2.4 加密属性(Encrypting Properties)

Spring Boot 不提供任何用于加密属性值的内置支持,但是,它提供了修改 Spring Environment 中包含的值所用的钩子点。EnvironmentPostProcessor 接口允许您在应用程序启动前操作 Environment 。详情参阅 Customize the Environment or ApplicationContext Before It Starts

如果您需要一种安全的方式来存储凭证和密码, Spring Cloud Vault x项目支持在 HashiCorp Vault 中存储外部化配置。

7.2.5 使用YAML(Working With YAML)

YAML 是JSON的超集,因此是指定分层配置数据的便捷格式。只要你的类路径上有 SnakeYAMLSpringApplication 类就会自动支持 YAML。

::: tip 备注

如果您使用“Starters”,则spring-boot-starter会自动提供SnakeYAML。

:::

将 YAML 映射到属性(Mapping YAML to 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 列表也需要扁平化。YAML 列表被表示为带有[index]索引的属性键。例如,请看下面的 YAML:

my:
  servers:
    - "dev.example.com"
    - "another.example.com"

前面的示例将转换为这些属性:

my.servers[0]=dev.example.com
my.servers[1]=another.example.com

::: tip 提示

使用 [index] 符号的属性可以通过 Spring Boot 的Binder类绑定到 Java ListSet 对象。有关详情请参阅 “Type-safe Configuration Properties” 部分。

:::

::: warning 警告

使用 @PropertySource@TestPropertySource 注解无法加载 YAML 文件。因此,如果需要以这种方式加载值,则需要使用 properties 文件。

:::

直接加载 YAML(Directly Loading YAML)

Spring Framework 提供了两个方便的类,可用于加载 YAML 文档。YamlPropertiesFactoryBeanProperties 的形式加载 YAML,而 YamlMapFactoryBean 则以 Map 的形式加载YAML。

如果想以Spring PropertySource 的形式加载 YAML,还可以使用 YamlPropertySourceLoader 类。

7.2.6 配置随机值(Configuring Random Values)

RandomValuePropertySource 对于注入随机值(例如,secrets或测试用例)非常有用。它可以生成 integers、 longs、uuid或 string, 如以下示例所示:

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]}"

  # number-in-range: "${random.int(1024,65536)}"
  # number-in-range: "${random.int-1024,65536-}"

random.int* 的语法为 OPEN value (,max) CLOSE ,其中 OPEN,CLOSE 为任意字符,value,max 为整数。如果只提供 max,那么 value 是最小值,max 是最大值(不包括)。

7.2.7 配置系统环境属性(Configuring System Environment Properties)

Spring Boot 支持为环境属性设置前缀。如果具有不同配置要求的多个 Spring Boot 应用程序共享系统环境,这将非常有用。系统环境属性的前缀可以直接在 SpringApplicationsetEnvironmentPrefix方法1上设置。

例如,如果将前缀设置为 input,那么 remote.timeout 等属性也将会在系统环境中解析为 input.remote.timeout

7.2.8 类型安全配置属性(Type-safe Configuration Properties)

使用 @Value("${property}") 注解来注入配置属性有时候会很麻烦,尤其是在处理多个属性或数据具有层次性的情况下。Spring Boot 提供了另外一种处理属性的方法,该方法允许强类型beans管理和验证应用程序的配置。

::: tip 提示

参阅 @Value 和类型安全配置属性之间的差异

:::

JavaBean 属性绑定(JavaBean Properties Binding)

可以绑定声明了标准 JavaBean 属性的 Bean,如下例所示:

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

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public static class Security {

        private String username;

        private String password;

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

        // getters / setters...

    }

}

前面的 POJO 定义了以下属性:

  • my.service.enabled,默认值为false
  • my.service.remote-address,其类型可以从 String 类型进行强制转换。
  • my.service.security.username,带有嵌套的“Security”对象,其名称由对象属性名决定。特别要指出的是,这里根本没有使用返回类型,可能是SecurityProperties
  • my.service.security.password
  • my.service.security.roles,默认值为 USERString集合。

::: tip 备注

映射到 Spring Boot 中可用的 @ConfigurationProperties 类的属性(通过properties 文件、YAML文件、环境变量和其它机制进行配置)是公共API,但类本身的访问器(getters/setters)不能直接使用。

:::

::: tip 备注

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

  • Maps,只要它们被初始化,就需要一个 getter 但是不一定需要一个 setter,因为他们可以被 binder 更改。
  • 可以通过索引(YAML文件)或使用单个逗号分隔值(properties文件)访问集合和数组。在后一种情况下,必须使用setter。我们建议始终为此类型添加setter。如果初始化集合,请确保它不是不可变的(如上例所示)。
  • 如果嵌套的 POJO 属性被初始化(如上例中的 Security 字段),则不需要 setter。如果你希望 binder 通过使用器默认构造函数动态创建实例,则需要一个 setter。

有些人使用项目插件 Lombok 自动添加 getters 和 setters。请确保 Lombok 不会为该类型生成任何特定的构造函数,因为容器会自动退使用它来实例化对象。

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

:::

构造函数绑定(Constructor Binding)

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

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

    // fields...

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

    // getters...

    public static class Security {

        // fields...

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

        // getters...

    }

}

在此设置中,@ConstructorBinding 注解用于表示使用构造函数绑定。这意味着绑定器将期望找到一个带有您希望绑定的参数的构造函数。如果您使用的是 Java 16 或者更高版本,构造函数绑定可与record关键字一起使用。在这种情况下,除非您的record修饰类有多个构造函数,否则无需使用 @ConstructorBinding

@ConstructorBinding 注解修饰的类的嵌套成员(如上例中的 Security)也将通过其构造函数绑定。

可以在构造函数参数上使用 @DefaultValue 指定默认值,或者在使用 Java 16 或者更高版本时使用record component指定默认值。转换服务用于将 String 值强制转换为缺失属性的目标类型。

参考前面的示例,如果没有任何属性绑定到 SecurityMyProperties 实例将包含一个 securitynull 值。要使其包含一个非空的 Security 实例,即使没有绑定任何属性(在使用 Kotlin 时,这需要将 Securityusernamepassword 参数声明为可空,因为它们没有默认值),请使用空的 @DefaultValue 注解:

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

::: tip 备注

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

:::

::: tip 提示

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

:::

::: tip 备注

不建议在 @ConfigurationProperties 中使用 java.util.Optional,因为它主要是用作返回类型。因此,它并不适合配置属性注入。为了与其他类型的属性保持一致,如果您声明了一个 Optional 属性,但它没有值,那么将绑定 null 而不是空的 Optional

:::

启用@ConfigurationProperties注解类型(Enabling @ConfigurationProperties-annotated Types)

Spring Boot 提供了@ConfigurationProperties注解类型绑定到类上并将其注册为Bean的基础功能。您可以逐类启用配置属性,也可以启用配置属性扫描,其工作方式与组件扫描类似。

有时,注解为 @ConfigurationProperties 的类可能不适合扫描,例如,如果您正在开发自己的自动配置或者您想在某些条件下启用它们。在这种情况下,可以使用 @EnableConfigurationProperties 注解指定类类型。这可以在任何 @Configuration 类上完成,如下所示:

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

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

}

如果要使用配置属性扫描,请在应用程序中添加 @ConfigurationPropertiesScan 注解。通常情况下,它会被添加到注解为 @SpringBootApplication 的主应用程序类中,但是可以可以添加到其它任何 @Configuration 类中。默认情况下,扫描将从声明注解的类的所在包中进行。如果想要定义要扫描的特定包,可以按如下所示:

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

}

::: tip 备注

当使用配置属性扫描或者通过@EnableConfigurationProperties注册 @ConfigurationProperties bean 时,Bean 具有一个常规的名称: <prefix>-<fqn>,其中 <prefix> 是在注解@ConfigurationProperties 中指定的前缀, <fqn> 是bean的完整限定名称。如果注解未提供任何前缀,则只使用Bean的完整限定名称。假设它位于 com.example.app 包中,则上述 SomeProperties 示例的Bean名称为 some.properties-com.example.app.SomeProperties

:::

我们建议 @ConfigurationProperties 只处理环境,尤其不要注入上下文中的其他 Bean。在某些情况下,可以使用设置器注入或框架提供的任何 *Aware 接口(如需要访问 Environment 时使用 EnvironmentAware)。如果您还想使用构造函数注入其他 Bean,则必须用 @Component 对配置属性 Bean 进行注解,并使用基于 JavaBean 的属性绑定。

使用@ConfigurationProperties注解类型(Using @ConfigurationProperties-annotated Types)

这种配置方式与 SpringApplication 外部 YAML 配置配合使用效果尤佳,如下例所示:

my:
  service:
    remote-address: 192.168.1.1
    security:
      username: "admin"
      roles:
        - "USER"
        - "ADMIN"

要使用 @ConfigurationProperties Bean,您可以像使用其他 Bean 一样注入它们,如下例所示:

@ConfigurationProperties("my.service")
public calsss MyProperties{
	private String remoteAddress;
    
    private Security security;
}

public calsss Security{
	private String username;
    
    private List<String> roles;
}
@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();
        // ...
    }

    // ...

}

::: tip 备注

使用 @ConfigurationProperties 还可以生成元数据文件,供集成开发环境使用,为您自己的键提供自动完成功能。详见 附录

:::

第三方配置(Third-party Configuration)

除了使用 @ConfigurationProperties 对类进行注解外,您还可以在公共 @Bean 方法中使用它。 您想将属性绑定到您无法控制的第三方组件时,这样做会特别有用。

如果要使用 Environment 属性来映射bean,请在bean注册时添加 @ConfigurationProperties 注解,如下所示:

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

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

}

任何以 another 前缀定义的 JavaBean 属性都将映射到该 AnotherComponent Bean 上,映射方式与前面的 SomeProperties 示例类似。

松弛结合(Relaxed Binding)

Spring Boot 使用一些宽松的规则将 Environment 属性绑定到 @ConfigurationProperties bean,因此在 Environment 属性名称和 bean 属性名称之间无需精确匹配。常见的例子包括以破折号分隔的环境变量(例如,context-path 绑定到 contextPath)和大写的环境属性(例如, PORT 绑定到 port)。

举个例子,请看下面的 @ConfigurationProperties 类:

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

    private String firstName;

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

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

}

在前面的示例中,可以使用以下属性名称:

PropertyNote
my.main-project.person.first-nameKebab 规则,建议在 .properties.yml文件中使用。
my.main-project.person.firstName标准的驼峰语法
my.main-project.person.first_name下划线表示法,这是在.properties.yml文件中使用的替代格式。
MY_MAINPROJECT_PERSON_FIRSTNAME大写格式,使用系统环境变量时建议使用。

::: tip 备注

注解中的 prefix必须是 kebab 规则(小写并且使用-分隔,如 my.main-project.person)。

:::

Property 来源简单属性List
.properties 文件驼峰语法,Kebab 规则或下划线表示法使用 [ ] 或逗号分隔值的标准列表语法
.yml文件驼峰语法,Kebab 规则或下划线表示法标准YAML列表语法或逗号分隔值
环境变量大写格式,下划线作为分隔符。 (参阅Binding From Environment Variables)。由下划线包围的数字值(参阅 Binding From Environment Variables
系统变量驼峰语法,Kebab 规则或下划线表示法使用 [ ] 或逗号分隔值的标准列表语法

::: tip 提示

我们建议,在可能的情况下,属性使用 lower-case kebab 格式,例如 my.person.first-name=Rod

:::

绑定Map属性(Binding Maps)

绑定到 Map 属性时,可能需要使用特殊的括号符号,以便保留原始的 key 值。如果键值没有被 []包裹,任何除了字母数字、 -. 以外的符号都会被移除。

例如,将以下属性绑定到一个 Map<String,String>

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

::: tip 备注

对于 YAML 文件,括号需要用引号包围,这样才能正确解析键值。

:::

上述属性将绑定到一个 Map 中, /key1, /key2key3 是map的key。由于 key3 没有使用[]包裹,因此会删除/key3 中的斜线。

绑定到标量值时,包含 . 的键无需用 []包裹。标量值包括枚举和 java.lang 包中除 Object 以外的所有类型。将 a.b=c 绑定到 Map<String, String> 将保留键中的 .,并返回一个包含 {"a.b"="c"} 的Map。对于其它类型,如果key包含.,则需要使用括号符号。例如,将 a.b=c 绑定到 Map<String, Object> 将返回一个包含条目 {"a"={"b"="c"}} 的 Map。
[a.b]=c 将会返回一个包含 {"a.b"="c"} 的Map。

从环境变量中绑定(Binding From Environment Variables)

大多数操作系统对环境变量的名称都有严格规定。例如,Linux shell 变量只能包含字母(azAZ)、数字(09)或下划线字符 (_)。按照惯例,Unix shell 变量的名称也是大写的。

Spring Boot 宽松的绑定规则尽可能与这些命名限制兼容。

要将规范格式的属性名称转换为环境变量名称,可以遵循以下规则:

  • 用下划线 (_)代替点 (.) 。
  • 删除任何破折号 (-)。
  • 转换为大写字母。

例如,配置属性 spring.main.log-startup-info 就是一个名为 SRING_MAIN_LOGSTARTUPINFO的环境变量。

在绑定到对象列表时,也可以使用环境变量。要绑定到 List,应在变量名中用下划线括起元素编号。

例如,配置属性 my.service[0].other 将使用名为 MY_SERVICE_0_OTHER的环境变量。

缓存(Caching)

松弛绑定使用缓存来提高性能。默认情况下,缓存仅适用于不可变属性源。要自定义此行为,例如为可变属性源启用缓存,请使用 ConfigurationPropertyCaching

合并负责类型(Merging Complex Types)

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

例如,假设一个 MyPojo 对象,其 namedescription 属性默认为 null。以下示例展示了MyProperties类中的MyPojo对象列表:

@ConfigurationProperties("my")
public class MyProperties {

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

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

}

配置如下:

my:
  list:
    - name: "my name"
      description: "my description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
    - name: "my another name"

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

如果在多个profiles配置文件中指定了 List ,则使用优先级最高的配置文件(也仅使用该配置文件)。如下所示:

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 profile 配置文件,MyProperties.list 就会包含一个MyPojo 对象(name为 my another name ,description 为 null)。对于 YAML,逗号分隔列表和 YAML 列表都可用于完全覆盖列表内容。

对于 Map 属性,可以绑定从多个来源获取属性值。不过,对于多个来源中的同一属性,将使用优先级最高的哪个。如下所示:

@ConfigurationProperties("my")
public class MyProperties {

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

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

}

配置如下:

my:
  map:
    key1:
      name: "my name 1"
      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 profile 配置文件,MyProperties.map 包含一个键为 key1 的对象(name 为 my name 1 和 description 为 my description 1)。但是,如果启用了 dev profile 配置文件, map 就会包含两个对象,key为 key1 (name 为 dev name 1 ,description 为 my description 1) 和 key2 (name 为 dev name 2 和description 为 dev description 2)。

::: tip 备注

上述合并规则适用于所有属性源的属性,而不仅仅是YMAL文件。

:::

属性转换(Properties Conversion)

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

::: tip 备注

由于该 bean 在应用程序生命周期的早期就会被请求,因此请确保限制 ConversionService 使用的依赖关系。通常情况下,您需要的任何依赖关系在创建时可能尚未完全初始化。如果您的自定义 ConversionService 不是配置键强制所必需的,您可能需要重命名它,并只依赖于使用 @ConfigurationPropertiesBinding 限定的自定义转换器。

:::

转换持续时间(Converting Durations)

Spring Boot 为表达持续时间提供了专门支持,如果您使用 java.time.Duration 属性,应用程序属性中的以下格式将可用:

  • 常规的 long 格式表示(使用毫秒最为默认单位,除非指定了 @DurationUnit
  • 标准 ISO-8601 格式,java.time.Duration使用
  • 可读性更强的格式,其中值和单位是耦合的(10s 表示 10 秒)

请看下面的示例:

@ConfigurationProperties("my")
public class MyProperties {

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

    private Duration readTimeout = Duration.ofMillis(1000);

    // getters / setters...

}

To specify a session timeout of 30 seconds, 30, PT30S and 30s are all equivalent. A read timeout of 500ms can be specified in any of the following form: 500, PT0.5S and 500ms.

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

您也可以使用任何支持的单位。这些单位是:

  • ns 表示纳秒
  • us 表示微秒
  • ms 表示毫秒
  • s 表示秒
  • m 表示分钟
  • h 表示小时
  • d表示天

默认单位是毫秒,可使用 @DurationUnit 改写,如上例所示。

如果您更喜欢使用构造函数绑定,则可以暴露相同的属性,如下例所示:

@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {

    // fields...

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

    // getters...

}

::: tip 提示

如果要从仅使用Long来表示持续时间的先前版本升级,请确保定义单位(使用@DurationUnit),如果它不是切换到Duration旁边的毫秒。这样做可以提供透明的升级路径,同时支持更丰富的格式。

:::

转换周期(Converting Periods)

除了持续时间,Spring Boot 还可以使用 java.time.Period 类型。应用程序属性中可使用以下格式:

  • 常规的 int 表示法(使用天数作为默认单位,除非指定了 @PeriodUnit)
  • 标准 ISO-8601 格式java.time.Period 使用
  • 一种更简单的格式,其中值和单位对是耦合的(1y3d表示 1 年零 3 天)

您也可以使用任何支持的单位。这些是:

  • y 表示年
  • m 表示月
  • w 表示周
  • d 表示天

::: tip 备注

java.time.Period类型实际上并不存储周数,它只是一个快捷方式,表示 “7 天”。

:::

转换数据大小(Converting Data Sizes)

Spring Framework 有一种以字节为单位表示大小的 DataSize 值类型。如果公开了 DataSize 属性,应用程序属性中就会出现以下格式:

  • 常规的 long 表示法(使用字节作为默认单位,除非指定了 @DataSizeUnit)
  • 可读性更强的格式,其中值和单位是耦合的(10MB表示 10 兆字节)

请看下面的示例:

@ConfigurationProperties("my")
public class MyProperties {

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

    private DataSize sizeThreshold = DataSize.ofBytes(512);

    // getters/setters...

}

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

您也可以使用任何支持的单位。这些单位是

  • B 表示字节for bytes
  • KB 表示千字节
  • MB 表示兆字节
  • GB 表示千兆字节
  • TB 表示太字节

默认单位是字节,可使用 @DataSizeUnit 改写,如上例所示。

如果您喜欢使用构造函数绑定,也可以公开相同的属性,如下例所示:

@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {

    // fields...

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

    // getters...

}

::: tip 备注

如果要从仅使用Long来表示大小的先前版本升级,请确保定义单位(使用@DataSizeUnit),如果它不是切换到DataSize旁边的字节。这样做可以提供透明的升级路径,同时支持更丰富的格式。

:::

@ConfigurationProperties 验证(@ConfigurationProperties Validation)

只要 @ConfigurationProperties 类使用了 Spring 的 @Validated 注解,Spring Boot 就会尝试验证这些类。您可以在配置类上直接使用 JSR-303 javax.validation 约束注解。为此,请确保在类路径上有符合 JSR-303 的实现,然后在字段中添加约束注解,如下例所示:

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

    @NotNull
    private InetAddress remoteAddress;

    // getters/setters...

}

::: tip 提示

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

:::

为确保嵌套属性始终触发验证,即使没有找到任何属性,也必须用 @Valid 对相关字段进行注解。下面的示例以前面的 MyProperties 示例为基础:

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

    @NotNull
    private InetAddress remoteAddress;

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

    // getters/setters...

    public static class Security {

        @NotEmpty
        private String username;

        // getters/setters...

    }

}

您还可以通过创建名为 configurationPropertiesValidator 的 Bean 定义来添加自定义 Spring Validator 。应声明 @Bean 方法为 static。 配置属性验证器会在应用程序生命周期的早期创建, ,因此将 @Bean 方法声明为静态方法可创建 Bean,而无需实例化 @Configuration 类。这样做可以避免提前实例化可能导致的任何问题。

::: tip 提示

spring-boot-actuator 模块包含一个可公开所有 @ConfigurationProperties beans。使用浏览器打开 /actuator/configprops 或者使用相应的JMX端点。详情参阅 “Production ready features”。

:::

@ConfigurationProperties 和 @Value(@ConfigurationProperties vs. @Value)

@Value 注解是核心容器功能,它不提供与类型安全配置属性相同的功能。 下表总结了 @ConfigurationProperties@Value支持的功能

功能@ConfigurationProperties@Value
松弛结合Yes有限 (参阅 note below)
支持元数据YesNo
SpEL evaluatedNoYes

::: tip 备注

如果您确实想使用 @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 中。

在解析应用程序属性文件并填充到环境中时,不会处理这些文件中的SpEL表达式。不过,您可以在 @Value 中编写SpEL表达式。如果应用程序属性文件中的属性值是一个 SpEL 表达式,则在通过 @Value 读取时将对其进行评估。

7.3 Profiles

Spring Profiles 提供了一种方法来隔离应用程序的部分配置,使其仅在特定环境中可用。任何 @Component, @Configuration@ConfigurationProperties 都可以被标记为 @Profile 以限制何时加载,如下所示:

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

    // ...

}

::: tip 备注

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

:::

你可以使用 spring.profiles.active Environment 属性来指定哪个profile 配置文件处于启用状态。你可以用本章前面描述的任何方式指定该属性。例如,可以将其包含在application.properties中,如下例所示:

spring:
  profiles:
    active: "dev,hsqldb"

也可以使用以下命令在命令行中指定:-spring.profiles.active=dev,hsqldb

If no profile is active, a default profile is enabled. The name of the default profile is default and it can be tuned using the spring.profiles.default Environment property, as shown in the following example:

如果没有启用profile 配置文件,则会启用默认profile 配置文件。默认配置文件的名称是 “default”,可以使用Environment属性spring.profiles.default对其进行调整,如下例所示:

spring:
  profiles:
    default: "none"

spring.profiles.activespring.profiles.default 只能在非profile 配置文件中使用,这意外着他们不能包含在 profile 配置文件spring.config.activate.on-profile启用文档 中。

例如,第二个文件配置无效:

# this document is valid
spring:
  profiles:
    active: "prod"
---
# this document is invalid
spring:
  config:
    activate:
      on-profile: "prod"
  profiles:
    active: "metrics"

7.3.1 添加激活Profiles(Adding Active Profiles)

spring.profiles.active 属性遵循与其他属性相同的排序规则:使用优先级最高的 数据源。 这意味着你可以在 application.properties 中指定活动配置文件,然后使用命令行开关替换它们。

有时,添加比替换 profiles 属性会更有用。 spring.profiles.include 属性可以用于由 spring.profiles.active 属性激活的profiles基础上添加新profiles. SpringApplication 对象中有一个用户设置其它配置文件的Java API。参阅SpringApplicationsetAdditionalProfiles() 方法。

例如,当运行带有以下属性的应用程序时,即使使用 --spring.profiles.active 开关,common 和 local 配置文件也会被激活:

spring:
  profiles:
    include:
      - "common"
      - "local"

::: warning 警告

spring.profiles.active类似,spring.profiles.include 只能在非特定profile配置文件中使用。这意味着它不能包含在 特定 profile 文件 或者spring.config.activate.on-profile激活的 文件 中。

:::

如果某个配置文件处于活动状态,也可以使用下一节 中描述的Profile组来添加活动配置文件。

7.3.2 Profile组(Profile Groups)

有时,您在应用程序中定义和使用的配置文件细粒度过于细化,使用起来很麻烦。例如,您可能有 proddbprodmq profiles 配置文件,用于独立启用数据库和消息传递功能。

为了解决这个问题,Spring Boot 允许您定义profile组。profile组允许您为相关的profile组定义一个逻辑名称。

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

spring:
  profiles:
    group:
      production:
        - "proddb"
        - "prodmq"

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

7.3.3 以编程方式设置配置文件(Programmatically Setting Profiles)

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

7.3.4 特定于Profile的配置文件(Profile-specific Configuration Files)

application.properties (或 application.yaml) 的特定Profile 变量和通过@ConfigurationProperties引用的文件都会被视为文件并加载。详情参阅 “特定 Profile 文件”。

7.4 日志(Logging)

Spring Boot 使用 Commons Logging 记录所有内部日志,但是保留底层日志实现。为 Java Util Logging, Log4J2, 和 Logback 提供了默认配置。在每种情况下,日志记录器都预先配置为使用控制台输出,也可选择文件输出。

默认情况下,如果使用 “Starters”,则使用 Logback 进行日志记录。还包括相应的Logback routing标签,以确保使用 Java Util Logging、Commons Logging、Log4J 或 SLF4J 的依赖库都能正常工作。

::: tip 提示

有很多适用于 Java 的日志框架。如果上述列表看起来很混乱,请不要担心。一般来说,您不需要更改日志记录依赖关系,Spring Boot 的默认设置就可以正常工作。

:::

::: tip 提示

当您将应用程序部署到 servlet 容器或应用程序服务器时,使用 Java Util Logging API 执行的日志记录不会被路由到您的应用程序的日志中。这样,容器或部署到容器中的其他应用程序执行的日志记录就不会出现在您的应用程序的日志中。

:::

7.4.1 日志格式(Log Format)

Spring Boot的默认日志输出类似于以下示例:

2023-11-22 15:39:17.168  INFO 3964 --- [           main] o.s.b.d.f.logexample.MyApplication       : Starting MyApplication using Java 1.8.0_392 on myhost with PID 3964 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-11-22 15:39:17.174  INFO 3964 --- [           main] o.s.b.d.f.logexample.MyApplication       : No active profile set, falling back to 1 default profile: "default"
2023-11-22 15:39:18.565  INFO 3964 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-11-22 15:39:18.581  INFO 3964 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-11-22 15:39:18.582  INFO 3964 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.83]
2023-11-22 15:39:18.728  INFO 3964 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-11-22 15:39:18.729  INFO 3964 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1484 ms
2023-11-22 15:39:19.345  INFO 3964 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-11-22 15:39:19.365  INFO 3964 --- [           main] o.s.b.d.f.logexample.MyApplication       : Started MyApplication in 2.792 seconds (JVM running for 3.295)

输出以下项目:

  • 日期和时间:毫秒精度,易于排序。
  • 日志级别:ERROR, WARN, INFO, DEBUG, 或 TRACE
  • 进程ID
  • 一个---分隔符,用于区分实际日志消息的开头。
  • 线程名称:括在方括号中(可能会截断控制台输出)。
  • 记录器名称:这通常是源类名称(通常缩写)。
  • 日志消息。

::: tip 备注

Logback没有FATAL级别。它被映射到ERROR

:::

7.4.2 控制台输出(Console Output)

默认日志配置会在信息写入时将其回显到控制台。默认情况下,ERROR级别、WARN级别和INFO级别消息都会被记录。你也可以通过使用 --debug 标志启动应用程序来启用 "调试 "模式。

$ java -jar myapp.jar --debug

::: tip 备注

你也可以在你的 application.properties 中指定 debug=true

:::

启用调试模式后,选定的核心日志记录器(嵌入式容器、Hibernate 和 Spring Boot)将被配置为输出更多信息。启用调试模式并没有配置应用程序记录所有DEBUG级别的日志。

Alternatively, you can enable a “trace” mode by starting your application with a --trace flag (or in your application.properties). Doing so enables trace logging for a selection of core loggers (embedded container, Hibernate schema generation, and the whole Spring portfolio).

另外,您也可以在启动应用程序时使用--trace标记(或在application.properties中使用trace=true)来启用 "跟踪 "模式。这样做可以为选定的核心日志记录器(嵌入式容器、Hibernate 模式生成和整个 Spring 组合)启用跟踪日志记录。

彩色编码输出(Color-coded Output)

如果您的终端支持 ANSI,则会使用彩色输出来帮助阅读。您可以将 spring.output.ansi.enabled 设置为 支持值,以覆盖自动检测功能。

颜色编码通过使用 %clr 转换词进行配置。在最简单的形式下,转换器会根据日志级别为输出着色,如下例所示:

%clr(%5p)

下表描述了日志级别与颜色的映射:

LevelColor
FATALRed
ERRORRed
WARNYellow
INFOGreen
DEBUGGreen
TRACEGreen

或者,您也可以将颜色或样式作为转换的一个选项来指定应使用的颜色或样式。例如,要使文字变为黄色,请使用以下设置:

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

支持以下颜色和样式:

  • blue
  • cyan
  • faint
  • green
  • magenta
  • red
  • yellow

7.4.3 文件输出(File Output)

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

The following table shows how the logging.* properties can be used together:

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

logging.file.namelogging.file.path示例描述
(没有)(没有)仅控制台记录
具体文件(没有)my.log写入指定的日志文件。名称可以是精确位置或相对于当前目录。
(没有)具体目录/var/logspring.log写入指定的目录。名称可以是精确位置或相对于当前目录。

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

::: tip 提示

日志属性独立于实际日志记录基础架构。因此,特定的配置键(如 Logback 的 logback.configurationFile)不受 sSpring Boot 管理。

:::

7.4.4 文件切换(File Rotation)

如果您使用的是 Logback,则可以在application.propertiesapplication.yaml文件对文件切换设置进行调整。对于所有其他日志系统,您需要自己直接配置rotation 设置(例如,如果使用 Log4J2,您可以添加一个 log4j2.xmllog4j2-spring.xml 文件)

支持以下rotation 策略属性:

NameDescription
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)。

7.4.5 日志级别(Log Levels)

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

下面的示例显示了 application.properties 中的日志记录设置:

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

还可以使用环境变量设置日志记录级别。例如, LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG 将把 org.springframework.web 设置为 DEBUG

::: tip 备注

上述方法仅适用于软件包级日志记录。由于松弛绑定总是将环境变量转换为小写,因此无法用这种方法为单个类配置日志记录。如果需要为某个类配置日志记录,可以使用SRING_APPLICATION_JSON 变量。

:::

7.4.6 日志组(Log Groups)

将相关的日志记录器分组,以便同时对它们进行配置,通常是非常有用的。例如,您可能会经常更改与 Tomcat 相关的所有日志记录器的日志记录级别,但却不容易记住顶级包路径。

为了解决这个问题,Spring Boot 允许你在 Spring 环境中定义日志记录组。例如,您可以通过将定义的 "tomcat "组添加到 application.properties

logging:
  group:
    tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"

定义后,只需一行即可更改组内所有记录器的级别:

logging:
  level:
    tomcat: "trace"

Spring Boot 包含以下预定义日志组,开箱即可使用:

NameLoggers
weborg.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sqlorg.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

7.4.7 使用日志的Shutdown Hook(Using a Log Shutdown Hook)

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

logging:
  register-shutdown-hook: false

7.4.8 自定义日志配置(Custom Log Configuration)

不同的日志系统都可以通过在类路径中包含相应的库来激活,也可通过在类路径根目录或以下Spring Environment属性指定的位置提供合适的配置文件来进一步自定义:logging.config

通过使用 org.springframework.boot.logging.LoggingSystem 系统属性,可以强制 Spring Boot 使用特定的日志系统。该值实现接口LoggingSystem的类的全限定类名。您也可以使用 none 值来完全禁用 Spring Boot 的日志记录配置。

::: tip 备注

由于日志记录器是在 ApplicationContext创建之前初始化的,因此无法通过 Spring @Configuration文件中的@PropertySources来控制日志记录器。更改日志系统或完全禁用日志系统的唯一方法是通过系统属性。

:::

根据日志系统的不同,会加载以下文件:

Logging SystemCustomization
Logbacklogback-spring.xml, logback-spring.groovy, logback.xml, 或logback.groovy
Log4j2log4j2-spring.xmllog4j2.xml
JDK (Java Util Logging)logging.properties

::: tip 备注

在可能的情况下,我们建议您在日志配置中使用 -spring b变量(例如,使用 logback-spring.xml 而不是 logback.xml)。如果使用标准配置位置,Spring 就无法完全控制日志初始化。

:::

::: warning 警告

Java Util Logging 存在已知的类加载问题,在从 "可执行 jar "运行时会造成问题。我们建议您尽可能避免从 "可执行 jar "中运行。

:::

为了帮助进行自定义,一些其他属性也从 Spring 的Environment属性转移到了系统属性。这样,这些属性就能被日志系统配置使用。例如,在 application.properties 中设置 logging.file.name 或将 LOGGING_FILE_NAME 设置为环境变量,都会导致 LOG_FILE 系统属性被设置。下表描述了传输的属性:

Spring EnvironmentSystem PropertyComments
logging.exception-conversion-wordLOG_EXCEPTION_CONVERSION_WORD记录异常时使用的转换词。
logging.file.nameLOG_FILE如果已定义,则在默认日志配置中使用。
logging.file.pathLOG_PATH如果已定义,则在默认日志配置中使用。
logging.pattern.consoleCONSOLE_LOG_PATTERN在控制台(stdout)上使用的日志模式。
logging.pattern.dateformatLOG_DATEFORMAT_PATTERNAppender 日志的日期格式
logging.charset.consoleCONSOLE_LOG_CHARSET控制台日志记录使用的字符集。
logging.pattern.fileFILE_LOG_PATTERN文件中要使用的日志模式(如果启用了 LOG_FILE)。
logging.charset.fileFILE_LOG_CHARSET文件日志记录使用的字符集(如果启用了 LOG_FILE)。
logging.pattern.levelLOG_LEVEL_PATTERN显示日志级别时使用的格式(默认为 %5p)。
PIDPID当前进程 ID(如果可能,且尚未定义为操作系统环境变量,则为已发现的进程 ID)。

如果使用 Logback,还将传输以下属性:

Spring EnvironmentSystem PropertyComments
logging.logback.rollingpolicy.file-name-patternLOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN滚动日志文件名的格式(默认为 ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz)。
logging.logback.rollingpolicy.clean-history-on-startLOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START是否在启动时清理存档日志文件。
logging.logback.rollingpolicy.max-file-sizeLOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE最大日志文件大小。
logging.logback.rollingpolicy.total-size-capLOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP要保存的日志备份的总大小。
logging.logback.rollingpolicy.max-historyLOGBACK_ROLLINGPOLICY_MAX_HISTORY要保留的归档日志文件的最大数量。

所有受支持的日志系统在解析其配置文件时都可以查阅系统属性。有关示例,请参阅 spring-boot.jar 中的默认配置:

::: tip 提示

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

:::

::: tip 提示

只需覆盖 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

MDC(Mapped Diagnostic Contexts)映射诊断上下文,该特征是logback提供的一种方便在多线程条件下的记录日志的功能,某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很麻烦。

MDC正是用于解决上述问题的,MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

通俗点来说:

MDC为每一个请求创建一条独立的可识别的的日志,方便追踪和查看,特别是在分布式系统中,分布式日志追踪往往对于问题诊断是特别重要的。

:::

7.4.9 Logbac扩展(Logback Extensions)

Spring Boot 包括许多Logback 扩展,可以帮助进行高级配置。您可以在 logback-spring.xml 配置文件中使用这些扩展配置。

::: tip 备注

由于标准的 logback.xml 配置文件加载过早,因此无法在其中使用扩展属性。您需要使用 logback-spring.xml 或者定义 logging.config 属性。

:::

::: warning 警告

扩展不能与 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]]
特定于配置文件的配置(Profile-specific Configuration)

<springProfile> 标签允许你根据激活的Spring 配置文件有选择地包含或排除配置部分。 Profile 部分支持在<configuration>元素中的任何位置使用。使用 name 属性可以指定哪个配置文件激活。<springProfile> 标签可以包含profile 名称(例如 staging)或者 profile 表达式。profile 表达式允许表达更复杂的逻辑,例如 production & (eu-central | eu-west)。详情请查阅 reference guide 。下面列出了三个示例:


<springProfile name="staging">
    <!-- 当"staging" profile 处于激活时,配置将会被启用 -->
</springProfile>

<springProfile name="dev | staging">
<!-- 当 "dev" 或 "staging" profiles 处于激活时,配置将会被启用 -->
</springProfile>

<springProfile name="!production">
<!-- 当 "production" profile 没有被激活时,配置将会被启用 -->
</springProfile>
环境属性(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>

::: tip 备注

source 必须是kebab case规则 (比如 my.property-name). 不过,可以使用宽松的规则将属性添加到 Environment中。

:::

7.5 国际化(Internationalization)

Spring Boot 支持本地化消息,因此您的应用程序可以满足不同语言偏好的用户。默认情况下,Spring Boot 会在类路径的根中查找是否存在messages资源包。

::: tip 备注

自动配置适用于已配置资源包的默认属性文件(默认为 messages.properties,英文语言配置为messages_en_US.properties,中文语言配置为messages_zh_CN.properties)。如果资源包只包含特定语言的属性文件,则需要添加默认属性文件。如果找不到与任何已配置基名称匹配的属性文件,则不会自动配置 MessageSource

:::

如以下示例所示,可使用 spring.messages 命名空间配置资源包的基名和其他几个属性:

spring:
  messages:
    basename: "messages,config.i18n.messages"
    fallback-to-system-locale: false

::: tip 提示

spring.messages.basename 支持以逗号分隔的位置列表,可以是软件包限定符,也可以是通过类路径根解析的资源。

:::

更多支持的选项请参阅 MessageSourceProperties

7.6 面向切面编程(Aspect-Oriented Programming)

Spring Boot 为面向切面编程(AOP)提供自动配置。你可以在 Spring Framework 参考文档 中了解有关Spring AOP 的更多信息。

默认情况下,Spring Boot 的自动配置会将 Spring AOP 设置为使用 CGLib 代理。如果要使用 JDK 代理,需要设置 configprop:spring.aop.proxy-target-classfalse

If AspectJ is on the classpath, Spring Boot’s auto-configuration will automatically enable AspectJ auto proxy such that @EnableAspectJAutoProxy is not required.

如果 AspectJ 位于类路径上,Spring Boot 的自动配置功能将自动启用 AspectJ 代理,因此不需要 @EnableAspectJAutoProxy

7.7 JSON

Spring Boot 提供了三个 JSON 库的集成:

  • Gson
  • Jackson
  • JSON-B

Jackson 是首选的默认库。

7.7.1 Jackson

为 Jackson 提供自动配置,Jackson 是 spring-boot-starter-json 的一部分。当 Jackson 位于类路径上时,会自动配置一个 ObjectMapper bean。提供的几个配置属性来自于 customizing the configuration of the ObjectMapper.

自定义序列化和反序列化(Custom Serializers and Deserializers)

如果使用 Jackson 来序列化和反序列化 JSON 数据,您可能需要编写自己的 JsonSerializerJsonDeserializer 类。自定义序列化器通常是 通过模块向 Jackson 注册,但是 Spring Boot 提供了另外一种 @JsonComponent 注解,使得直接注册 Spring Beans 变得更加容易。

你可以在JsonSerializer, JsonDeserializerKeyDeserializer 接口实现中直接使用 @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);
        }

    }

}

ApplicationContext中所有 @JsonComponent beans 都会自动向 Jackson 注册。因为 @JsonComponent 注解中包含 @Component 注解,因此适用于正常的组件扫描规则。

Spring Boot 还提供 JsonObjectSerializerJsonObjectDeserializer 两个基类,在序列化对象时,这些基类提供了标准 Jackson
版本。请参阅Javadoc中的JsonObjectSerializerJsonObjectDeserializer

上面的示例可以重写为使用 JsonObjectSerializer/JsonObjectDeserializer 如下:

@JsonComponent
public class MyJsonComponent {

    public static class Serializer extends JsonObjectSerializer<MyObject> {

        @Override
        protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException {
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
        }

    }

    public static class Deserializer extends JsonObjectDeserializer<MyObject> {

        @Override
        protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
                JsonNode tree) throws IOException {
            String name = nullSafeValue(tree.get("name"), String.class);
            int age = nullSafeValue(tree.get("age"), Integer.class);
            return new MyObject(name, age);
        }

    }

}
混合注解(Mixins)

Jackson 提供了一种混合注解机制(mix-in annotations),允许开发者在不修改原始类的情况下,为其添加或覆盖特定的注解。 Spring Boot 的 Jackson 自动配置功能会扫描应用程序的包,查找带有 @JsonMixin 注解的类并将他们注册到自动配置的 ObjectMapper中。注册功能由 Spring Boot JsonMixinModule 来执行。

@JsonMixin注解源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonMixin {
    @AliasFor("type")
    Class<?>[] value() default {};

    @AliasFor("value")
    Class<?>[] type() default {};
}

Spring Boot工程中,混合类添加@JsonMixin注解,并指定原始类

@JsonMixin(TokenInfo.class) // 需要修改的目标类
public abstract class MixinTokenInfo {

    @JsonIgnore(value = false)
    String username;

    @JsonIgnore
    String password;
    
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "GMT+8")
    Date birthday;
}

单元测试:

@Autowired
ObjectMapper objectMapper;


@Test
void testMixIn() throws JsonProcessingException {

    // 模拟第三包返回令牌对象
    TokenInfo tokenInfo=new TokenInfo();
    tokenInfo.setUsername("王法");
    tokenInfo.setBirthday(new Date());
    tokenInfo.setPassword("123456");

    // 序列化
    String value = objectMapper.writeValueAsString(tokenInfo);
    System.out.println(value);
    //输出结果是 {"username":"王法","birthday":"2024-04-11"}
}

7.7.2 Gson

提供 Gson 的自动配置。当 Gson 位于类路径上时,会自动配置一个 Gson Bean。为自定义配置提供了多个 spring.gson.* 配置属性。要进行更多控制,可以使用一个或多个 GsonBuilderCustomizer Bean。

7.7.3 JSON-B

提供 JSON-B 的自动配置。当 JSON-B API 和实现位于类路径上时,将自动配置一个 Jsonb Bean。首选的 JSON-B 实现是 Apache Johnzon,它提供了依赖关系管理。

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

如果上下文中没有 Executor bean。Spring Boot 会使用合理的默认值自动配置 ThreadPoolTaskExecutor ,这些默认值将会自动与异步任务相互关联 (@EnableAsync) 和 Spring MVC 异步请求调用。

::: tip 提示

如果您在上下文中定义了自定义的 Executor ,常规任务执行(即 @EnableAsync)将透明地使用它,但 Spring MVC 将不支持配置,因为它需要一个 AsyncTaskExecutor 实现(名为 applicationTaskExecutor)。根据您的目标安排,您可以将 Executor 更改为 ThreadPoolTaskExecutor 或同时定义一个 ThreadPoolTaskExecutor 和一个 AsyncConfigurer 来封装您的自定义 Executor

:::

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

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

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

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

spring:
  task:
    scheduling:
      thread-name-prefix: "scheduling-"
      pool:
        size: 2

如果需要创建自定义执行器或调度器,上下文中会提供一个 TaskExecutorBuilder Bean 和一个 TaskSchedulerBuilder Bean。

7.9 测试(Testing)

Spring Boot 提供了大量实用工具和注解,可帮助您测试应用程序。测试支持由两个模块提供: spring-boot-test 包含核心项目,而 spring-boot-test-autoconfigure 则支持测试的自动配置。

大多数开发人员使用 spring-boot-starter-test “Starter”,它同时导入了 Spring Boot 测试模块以及 JUnit Jupiter、AssertJ、Hamcrest 和其他一些有用的库。

::: tip 提示

如果有使用 JUnit 4 的测试,可以使用 JUnit 5 的复古引擎来运行它们。要使用 vintage 引擎,请添加对 junit-vintage-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:hamcrestspring-boot-starter-test 的一部分。

7.9.1 测试范围依赖(Test Scope Dependencies)

spring-boot-starter-test “Starter” (在 test scope 中) 包含以下提供的库:

  • JUnit 5: 单元测试 Java 应用程序的事实标准。
  • Spring Test & Spring Boot Test: Spring Boot 应用程序的实用程序和集成测试支持。
  • AssertJ: 断言库。
  • Hamcrest: 一个匹配器对象库(也称为约束或谓词)。
  • Mockito: 一个 Java 模拟框架。
  • JSONassert: 一个 JSON 断言库。
  • JsonPath: 用于 JSON 的 XPath。

在编写测试时,我们通常会发现这些常用库非常有用。如果这些库不能满足您的需求,您可以自行添加其他测试依赖库。

7.9.2 测试Spring应用程序(Testing Spring Applications)

依赖注入的一个主要优势是,它可以让你的代码更容易进行单元测试。您可以使用 new 操作符实例化对象,而无需涉及 Spring。您还可以使用模拟对象来代替真实的依赖关系。

通常,您需要越过单元测试并开始集成测试(使用 Spring ApplicationContext)。在不需要部署应用程序或连接其他基础设施的情况下执行集成测试是非常有用的。

Spring 框架包含一个专用测试模块,用于此类集成测试。你可以直接向 org.springframework:spring-test 声明依赖关系,或使用 spring-boot-starter-test "Starter "将其临时引入。

如果你以前没有使用过 spring-test 模块,应该先阅读 Spring Framework 参考文档的 相关章节

7.9.3 测试Spring Boot应用程序(Testing Spring Boot Applications)

Spring Boot 应用程序是 Spring ApplicationContext 的一部分,因此除了通常使用vanilla Spring上下文所做的测试之外,没有什么特别的要做。

::: tip 备注

只有在使用 SpringApplication创建上下文时,Spring Boot 的外部属性、日志和其他功能才会默认安装在上下文中。

:::

Spring Boot 提供了一个 @SpringBootTest 注解,当你需要 Spring Boot 功能时,可以用它来替代标准的 spring-test @ContextConfiguration 注解,该注解的工作原理是 通过SpringApplication创建测试中使用ApplicationContext. In addition to 除了 @SpringBootTest 之外,还提供了其他一些
注解还可用于 测试应用程序的更多特定片段

::: tip 提示

如果使用的是 JUnit 4,请不要忘记在测试中添加 @RunWith(SpringRunner.class),否则注释将被忽略。如果使用的是 JUnit 5,则无需添加相应的 @ExtendWith(SpringExtension.class),因为 @SpringBootTest 和其他 @...Test注释已使用了该注释。

:::

默认情况下,@SpringBootTest 不会启动服务器。你可以使用 @SpringBootTestwebEnvironment 属性来进一步完善测试的运行方式:

  • MOCK(默认):加载 web ApplicationContext 并提供一个模拟的网络环境。使用此注解时,不会启动嵌入式服务器。如果classpath 上没有 web h环境。该模式会回退创建常规非 Web ApplicationContext 。它可以与 @AutoConfigureMockMvc@AutoConfigureWebTestClient 结合使用,以对网络应用程序进行基于模拟的测试。
  • RANDOM_PORT:加载 WebServerApplicationContext 并提供一个真实的 web 环境。嵌入式服务器会启动并监听一个随机端口。
  • DEFINED_PORT:加载 WebServerApplicationContext 并提供一个真实的 web 环境. 嵌入式服务器会启动并监听一个定义的端口(来自于 application.properties)或者是默认的8080端口 。
  • NONE:通过使用 SpringApplication 加载 ApplicationContext ,但不提供任何 web环境(mock 或其它)。

::: tip 备注

如果你测试中使用 @Transactional 注解,它默认会在每个测试方法结束时回滚事务。但是,由于使用 RANDOM_PORTDEFINED_PORT 的这种模式,他会提供了一个真正的 servlet 环境,HTTP 客户端和服务器在不同的线程中运行,因此也在不同的事务中运行。在这种情况下,服务器上启动的任何事务都不会回滚。

:::

::: tip 备注

如果应用程序为管理服务器使用了不同的端口,那么使用 webEnvironment = WebEnvironment.RANDOM_PORT@SpringBootTest 也会在一个单独的随机端口上启动管理服务器

:::

检测Web应用程序类型(Detecting Web Application Type)

如果有 Spring MVC,则会配置基于 MVC 的常规应用上下文。如果只有 Spring WebFlux,我们会检测到并配置基于 WebFlux 的应用上下文。

如果两者都存在,则以 Spring MVC 为优先。如果想在这种情况下测试反应式 Web 应用程序,则必须设置 spring.main.web-application-type 属性:

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

    // ...

}
检测测试配置(Detecting Test Configuration)

如果您熟悉 Spring 测试框架,可能会习惯使用 @ContextConfiguration(classes=...) 来指定要加载的 Spring @Configuration。另外,您可能经常在测试中使用嵌套的 @Configuration 类。

在测试 Spring Boot 应用程序时,通常不需要这样做。只要您没有明确定义主要配置,Spring Boot 的 @*Test 注解就会自动搜索主要配置。

搜索算法会从包含测试的包向上搜索,直到找到注释为 @SpringBootApplication@SpringBootConfiguration 的类。只要你的代码结构合理,通常就能找到你的主配置。

::: tip 备注

如果你使用test注解来测试应用程序的一个个的具体切片,你应该避免在main方法的应用程序类上添加针对特定区域的配置设置。@SpringBootApplication的底层组件扫描配置定义了排除过滤器,用来确保代码切片按预期工作。如果你在 @SpringBootApplication 注释的类中使用了明确的 @ComponentScan指令,请注意这些过滤器将被禁用。如果使用了切片,则应重新定义它们。

:::

如果您想自定义主配置,可以使用嵌套的 @TestConfiguration 类。与嵌套的 @Configuration 类不同的是,嵌套的 @TestConfiguration 类是应用程序主配置的补充。

::: tip 备注

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

:::

排除测试配置(Excluding Test Configuration)

如果你的应用程序使用了组件扫描(例如,如果你使用了 @SpringBootApplication@ComponentScan),你可能会发现你只为特定测试创建的顶级配置类不小心被到处收集。

正如我们在前面所见,@TestConfiguration 可用于测试的内部类,以自定义主要配置。也可以在顶层类中使用 @TestConfiguration。这样做表示该类不应被扫描获取。然后,您可以在需要的地方显式导入该类,如下所示
示例:

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

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

}

::: tip 备注

如果直接使用@ComponentScan(即不通过@SpringBootApplication),则需要注册TypeExcludeFilter。详见Javadoc

:::

::: tip 备注

导入的 @TestConfiguration 比内类的 @TestConfiguration 处理要早,而且导入的 @TestConfiguration 将在通过组件扫描发现的任何配置之前被处理。一般来说,这种排序上的差异不会产生明显的影响,但如果您依赖 bean 重载,则需要注意这一点。

:::

使用应用程序参数(Using Application Arguments)

如果你的应用程序希望使用 arguments,你可以让 @SpringBootTest 会用 args 属性注入它们。

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

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

}
使用模拟环境进行测试(Testing With a Mock Environment)

默认情况下,@SpringBootTest 无法启动服务器,而是为测试 Web 端点设置一个模拟环境。

使用 Spring MVC,我们可以使用 MockMvcWebTestClient 查询网络端点,如下例所示:

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

}

::: tip 备注

如果您只想关注网络层,而不想启动完整的 ApplicationContext ,请考虑 使用 @WebMvcTest 代替.

:::

对于 Spring WebFlux 端点,您可以使用 WebTestClient ,如下例所示:

@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {

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

}

::: tip 提示

在模拟环境中进行测试通常比使用完整的 servlet 容器运行更快。例如,Spring Boot 的错误处理基于 servlet 容器提供的 "错误页面 "支持。这意味着,虽然您可以测试 MVC 层是否按预期抛出和处理异常,但却无法直接测试是否渲染了特定的自定义错误页面。如果需要测试这些较低层次的问题,可以按照下一节所述启动一个完全运行的服务器。

:::

使用正在运行的服务器进行测试(Testing With a Running Server)

如果需要启动一个完整运行的服务器,我们建议你使用随机端口。如果使用"@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)",每次测试运行时都会随机选择一个可用端口。

可以使用 @LocalServerPort 注解注入实际使用的端口到测试中。为方便起见,需要对已启动的服务器进行 REST 调用的测试可以额外使用 @Autowire 一个 WebTestClient,它可以解析运行服务器的相对链接,并附带一个专用 API 来验证响应,如以下示例所示:

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

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

}

::: tip 提示

WebTestClient也可与mock environment一起使用,通过在测试类中加上注解@AutoConfigureWebTestClient,无需运行服务器。

:::

此设置需要在类路径上添加 spring-webflux 。如果不能或不愿添加 webflux,Spring Boot 还提供了TestRestTemplate工具:

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

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

}
自定义 WebTestClient(Customizing WebTestClient)

要自定义 WebTestClient Bean,请配置一个 WebTestClientBuilderCustomizer Bean。任何此类 Bean 都会与用于创建 WebTestClientWebTestClient.Builder 一起调用。

使用 JMX(Using JMX)

由于测试上下文框架会缓存上下文,因此默认情况下会禁用 JMX,以防止相同组件在同一域上注册。如果此类测试需要访问 MBeanServer,请考虑将其也标记为 dirty:

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

    @Autowired
    private MBeanServer mBeanServer;

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

}
使用 Metrics(Using Metrics)

当使用@SpringBootTest时,无论你的classpath是什么,除了内存中支持的度量注册表外,其他度量注册表都不会自动配置。

如果需要将指标导出到不同的后端作为集成测试的一部分,请使用 @AutoConfigureMetrics注解。

Mocking and Spying Beans

在运行测试时,有时需要在应用程序上下文中模拟某些组件。例如,您可能会在某个远程服务上设置一个门面,而该服务在开发过程中是不可用的。当您想模拟在真实环境中很难触发的故障时,模拟也很有用。

Spring Boot 包含一个 @MockBean 注解,可用于为您的 ApplicationContext 中的 Bean 定义 Mockito mock。您可以使用注解添加新的 Bean 或替换现有的 Bean 定义。注解可直接用于测试类、测试中的字段或 @Configuration 类和字段。在字段中使用时,创建的模拟实例也会被注入。每次测试方法结束后,模拟 Bean 都会自动重置。

::: tip 备注

如果您的测试使用了 Spring Boot 的测试注解之一(如 @SpringBootTest),该功能就会自动启用。要在不同的安排下使用此功能,必须显式添加监听器,如下例所示:

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:

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

}

::: tip 备注

@MockBean 不能用于模拟在应用程序上下文刷新期间执行的 Bean 行为。执行测试时,应用程序上下文刷新已经完成,来不及配置模拟行为。在这种情况下,我们建议使用 @Bean 方法来创建和配置模拟。

:::

此外,您还可以使用 @SpyBean 用 Mockito spy 封装任何现有的 Bean。详情请参见 Javadoc

::: tip 备注

CGLib 代理(例如为作用域 Bean 创建的代理)会将代理方法声明为 final。这使得 Mockito 无法正常运行,因为在默认配置中,它无法模拟或监视 final 方法。如果你想模拟或监视这样的 bean,可以通过在应用程序的测试依赖项中添加 org.mockito:mockito-inline 来配置 Mockito 以使用其内联模拟器。这将允许 Mockito 模拟和监视 final 方法。

:::

::: tip 备注

Spring 的测试框架会在测试之间缓存应用程序上下文,并在共享相同配置的测试中重用上下文,但使用 @MockBean@SpyBean 会影响缓存键,这很可能会增加上下文的数量。

:::

::: tip 提示

如果您使用 @SpyBean 来监视一个带有 @Cacheable 方法的 Bean,而这些方法会通过名称引用参数,那么您的应用程序必须使用 -parameters 进行编译。这将确保一旦 Bean 被监视,缓存基础架构就可以使用参数名称。

:::

::: tip 提示

当您使用 @SpyBean 监视由 Spring 代理的 Bean 时,您可能需要在某些情况下移除 Spring 的代理,例如在使用 givenwhen 设置期望值时。请使用 AopTestUtils.getTargetObject(yourProxiedSpy)

:::

自动配置的测试(Auto-configured Tests)

Spring Boot 的自动配置系统适用于应用程序,但有时对测试来说可能有点太多。通常只加载测试应用程序 "片段 "所需的配置部分会有帮助。例如,您可能想测试 Spring MVC 控制器是否正确映射了 URL,但您不想在这些测试中涉及数据库调用;您可能想测试 JPA 实体,但您对这些测试运行时的 Web 层不感兴趣。

Spring-boot-test-autoconfigure 模块包含许多注解,可用于自动配置此类 “切片”。每个注解都以类似的方式工作,提供一个用于加载 ApplicationContext@...Test注解和一个或多个可用于自定义自动配置设置的 @AutoConfigure...注解。

::: tip 备注

每个片段都会将组件扫描限制在适当的组件上,并加载一组非常有限的自动配置类。如果您需要排除其中一个,大多数 @...Test 注解都提供了一个 excludeAutoConfiguration 属性。或者,您也可以使用 @ImportAutoConfiguration#exclude

:::

::: tip 备注

不支持在一个测试中使用多个 @...Test 注释来包含多个 “片段”。如果需要多个 “片段”,请选择其中一个@...Test注释,然后手工添加其他 "片段 "的@AutoConfigure...注释。

:::

::: tip 提示

也可以将"@AutoConfigure… “注解与标准的”@SpringBootTest "注解一起使用。如果你对 "切片 "应用程序不感兴趣,但又希望使用某些自动配置的测试 Bean,那么就可以使用这种组合。

:::

自动配置的JSON测试(Auto-configured JSON Tests)

要测试对象 JSON 序列化和反序列化是否按预期运行,可以使用 @JsonTest 注解。@JsonTest 会自动配置可用的、受支持的 JSON 映射器,该映射器可以是以下库之一:

  • Jackson ObjectMapper,任何 @JsonComponent beans 和任何 Jackson Modules
  • Gson
  • Jsonb

::: tip 提示

@JsonTest 启用的自动配置列表 见附录

:::

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

Spring Boot 包含基于 AssertJ 的帮助组件,可与 JSONAssert 和 JsonPath 库协同工作,检查 JSON 是否按预期显示。JacksonTester, GsonTester, JsonbTester, 和 BasicJsonTester 类分别用于 Jackson, Gson, Jsonb, 和 Strings 。使用 @JsonTest 时,测试类上的任何辅助字段都可以是 @Autowired。下面的示例展示了一个用于 Jackson 的测试类:

@JsonTest
class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

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

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

}

::: tip 备注

JSON 辅助类也可直接用于标准单元测试。为此,如果不使用 @JsonTest,请在 @Before 方法中调用辅助类的 initFields 方法。

:::

如果您使用 Spring Boot 基于 AssertJ 的辅助组件来断言给定 JSON 路径上的数字值,根据类型的不同,您可能无法使用 isEqualTo。相反,您可以使用 AssertJ 的 satisfies 来断言该值符合给定条件。例如,下面的示例断言实际数字是一个浮点数值,在偏移量0.01范围内接近0.15

@Test
void someTest() throws Exception {
    SomeObject value = new SomeObject(0.152f);
    assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
        .satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
自动配置的 Spring MVC测试(Auto-configured Spring MVC Tests)

要测试 Spring MVC 控制器是否按预期运行,请使用 @WebMvcTest 注解。@WebMvcTest 会自动配置 Spring MVC 基础架构,并将扫描的 Bean 限制为 @Controller@ControllerAdvice@JsonComponentConverterGenericConverterFilterHandlerInterceptorWebMvcConfigurerWebMvcRegistrationsHandlerMethodArgumentResolver。当使用 @WebMvcTest 注解时,常规的 @Component@ConfigurationProperties Bean 不会被扫描。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。

::: tip 提示

@WebMvcTest 启用的自动配置设置列表 见附录

:::

::: tip 提示

如果需要注册额外的组件,如 Jackson Module,可以在测试中使用 @Import导入额外的配置类。

:::

通常,@WebMvcTest仅限于单个控制器,并与@MockBean结合使用,为所需协作器提供模拟实现。

@WebMvcTest 也会自动配置 MockMvc。Mock MVC 为快速测试 MVC 控制器提供了一种强大的方法,而无需启动完整的 HTTP 服务器。

::: tip 提示

你也可以在非@WebMvcTest(如@SpringBootTest)中自动配置MockMvc,方法是用@AutoConfigureMockMvc注释它。下面的示例使用了 MockMvc

:::

@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

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

}

::: tip 提示

如果需要配置自动配置的元素(例如,何时应用 servlet 过滤器),可以使用 @AutoConfigureMockMvc 注解中的属性。

:::

如果使用 HtmlUnit 和 Selenium,自动配置还会提供 HtmlUnit WebClient Bean 和/或 Selenium WebDriver Bean。下面的示例使用 HtmlUnit:

@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

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

}

::: tip 备注

默认情况下,Spring Boot 会将 WebDriver Bean 放在一个特殊的 "作用域 "中,以确保驱动程序在每次测试后退出,并注入一个新实例。如果不希望出现这种行为,可以在WebDriver``@Bean定义中添加@Scope("singleton")

:::

::: warning 警告

Spring Boot 创建的 webDriver 作用域将取代任何用户定义的同名作用域。如果您定义了自己的 webDriver 作用域,可能会发现它在使用 @WebMvcTest 时停止工作。

:::

如果类路径上有 Spring Security,@WebMvcTest 也会扫描 WebSecurityConfigurer Bean。你可以使用 Spring Security 的测试支持,而不是完全禁用此类测试的安全性。有关如何使用 Spring Security 的 MockMvc 支持的更多详情,请参阅 Testing With Spring Security 如何使用一节。

::: tip 提示

有时,仅编写 Spring MVC 测试是不够的;Spring Boot 可以帮助您运行使用实际服务器进行完整的端到端测试

:::

自动配置 Spring WebFlux 测试(Auto-configured Spring WebFlux Tests)

要测试 Spring WebFlux 控制器是否按预期运行,可以使用 @WebFluxTest 注解。@WebFluxTest 会自动配置 Spring WebFlux 基础架构,并将扫描的 Bean 限制为 @Controller@ControllerAdvice@JsonComponentConverterGenericConverterWebFilterWebFluxConfigurer。常规的 @Component@ConfigurationProperties Bean 不会被扫描。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。

::: tip 提示

@WebFluxTest 启用的自动配置列表 见附录

:::

::: tip 提示

如果需要注册额外的组件,如 Jackson Module,可以在测试中使用 @Import导入额外的配置类。

:::

通常,"@WebFluxTest “仅限于单个控制器,并与”@MockBean "注解结合使用,为所需的协作器提供模拟实现。

@WebFluxTest 还会自动配置 WebTestClient,这为快速测试 WebFlux 控制器提供了一种强大的方式,而无需启动完整的 HTTP 服务器。

::: tip 提示

你也可以在非@WebFluxTest(如@SpringBootTest)中自动配置WebTestClient,方法是用@AutoConfigureWebTestClient注释它。下面的示例展示了一个同时使用 @WebFluxTestWebTestClient 的类:

:::

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

}

::: tip 提示

目前只有 WebFlux 应用程序支持这种设置,因为在模拟网络应用程序中使用 WebTestClient仅适用于 WebFlux。

:::

::: tip 备注

@WebFluxTest 无法检测通过功能性 Web 框架注册的路由。若要测试上下文中的 RouterFunction Bean,可考虑使用 @Import@SpringBootTest自行导入 RouterFunction

:::

::: tip 备注

@WebFluxTest 无法检测注册为 SecurityWebFilterChain 类型的 @Bean 的自定义安全配置。要将其纳入测试,需要使用 @Import@SpringBootTest 导入注册 Bean 的配置。

:::

::: tip 提示

有时,仅编写 Spring WebFlux 测试是不够的;Spring Boot 可以帮助您运行使用实际服务器进行完整的端到端测试

:::

自动配置的Spring GraphQL测试(Auto-configured Spring GraphQL Tests)

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。测试中会大量使用该测试器,因此请务必熟悉使用。GraphQlTester有多种变体,Spring Boot 会根据测试类型自动配置它们:

  • ExecutionGraphQlServiceTester在服务器端执行测试,无需客户端或传输设备。
  • 无论是否有实时服务器,HttpGraphQlTester都会使用连接到服务器的客户端执行测试。

Spring Boot 通过 @GraphQlTest 注解帮助您测试 Spring GraphQL Controllers@GraphQlTest 自动配置 Spring GraphQL 基础架构,不涉及任何传输或服务器。这就将扫描的 Bean 限制为 @ControllerRuntimeWiringConfigurerJsonComponentConverterGenericConverterDataFetcherExceptionResolverInstrumentationGraphQlServiceConfigurer
GraphQlSourceBuilderCustomizer. 当使用 @GraphQlTest 注解时,常规的 @Component@ConfigurationProperties Bean 不会被扫描。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。

::: tip 提示

@GraphQlTest 启用的自动配置列表 见附录

:::

::: tip 提示

如果需要注册额外的组件,如 Jackson Module,可以在测试中使用 @Import导入额外的配置类。

:::

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

@GraphQlTest(GreetingController.class)
class GreetingControllerTests {

    @Autowired
    private GraphQlTester graphQlTester;

    @Test
    void shouldGreetWithSpecificName() {
        this.graphQlTester.document("{ greeting(name: \"Alice\") } ")
            .execute()
            .path("greeting")
            .entity(String.class)
            .isEqualTo("Hello, Alice!");
    }

    @Test
    void shouldGreetWithDefaultName() {
        this.graphQlTester.document("{ greeting } ")
            .execute()
            .path("greeting")
            .entity(String.class)
            .isEqualTo("Hello, Spring!");
    }

}

@SpringBootTest 测试是完全集成测试,涉及整个应用程序。使用随机端口或定义端口时,会配置一个实时服务器,并自动生成一个 HttpGraphQlTesterBean,以便使用它来测试服务器。配置 MOCK 环境时,您也可以通过在测试类中注释@AutoConfigureHttpGraphQlTester来请求一个 HttpGraphQlTesterBean:

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

}
自动配置数据Cassandra测试(Auto-configured Data Cassandra Tests)

您可以使用 @DataCassandraTest 测试 Cassandra 应用程序。默认情况下,它会配置CassandraTemplate、扫描@Table类并配置 Spring Data Cassandra 资源库。使用 @DataCassandraTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。(有关在 Spring Boot 中使用 Cassandra 的更多信息、
请参阅"Cassandra")。

::: tip 提示

@DataCassandraTest启用的自动配置设置列表见附录

:::

下面的示例展示了在 Spring Boot 中使用 Cassandra 测试的典型设置:

@DataCassandraTest
class MyDataCassandraTests {

    @Autowired
    private SomeRepository repository;

}
自动配置数据Couchbase测试(Auto-configured Data Couchbase Tests)

您可以使用 @DataCouchbaseTest 测试 Couchbase 应用程序。默认情况下,它会配置CouchbaseTemplateReactiveCouchbaseTemplate,扫描@Document类,并配置 Spring Data Couchbase 存储库。使用 @DataCouchbaseTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可以使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。(有关在 Spring Boot 中使用 Couchbase 的更多信息、请参阅本章前面的"Couchbase")。

::: tip 提示

@DataCouchbaseTest启用的自动配置设置列表见附录

:::

下面的示例展示了在 Spring Boot 中使用 Couchbase 测试的典型设置:

@DataCouchbaseTest
class MyDataCouchbaseTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
自动配置数据Elasticsearch测试(Auto-configured Data Elasticsearch Tests)

您可以使用 @DataElasticsearchTest 测试 Elasticsearch 应用程序。默认情况下,它会配置一个 ElasticsearchRestTemplate,扫描 @Document 类,并配置 Spring Data Elasticsearch 资源库。使用 @DataElasticsearchTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可以使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。(有关在 Spring Boot 中使用 Elasticsearch 的更多信息、请参阅本章前面的"Elasticsearch")。

::: tip 提示

@DataElasticsearchTest 启用的自动配置设置列表 见附录

:::

下面的示例展示了在 Spring Boot 中使用 Elasticsearch 测试的典型设置:

@DataElasticsearchTest
class MyDataElasticsearchTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
自动配置的Data JPA测试(Auto-configured Data JPA Tests)

您可以使用 @DataJpaTest 注解来测试 JPA 应用程序。默认情况下,它会扫描 @Entity 类并配置 Spring Data JPA 存储库。如果类路径上有嵌入式数据库,它也会配置一个。通过将 spring.jpa.show-sql 属性设置为 true,默认情况下会记录 SQL 查询。可以使用注解的 showSql() 属性禁用此功能。

当使用 @DataJpaTest 注解时,常规的 @Component@ConfigurationProperties Bean 不会被扫描。@EnableConfigurationProperties 可用于包含 @ConfigurationProperties Bean。

::: tip 提示

@DataJpaTest启用的自动配置设置列表见附录

:::

默认情况下,数据 JPA 测试是事务性的,并在每次测试结束时回滚。详情请参见 Spring Framework 参考文档中的 相关章节。如果这不是你想要的,你可以为某个测试或整个类禁用事务管理,方法如下:

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

    // ...

}

数据 JPA 测试还可以注入 TestEntityManager Bean,它提供了标准 JPA EntityManager的替代方案,是专门为测试设计的。

::: tip 提示

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

:::

如果需要,还可以使用 JdbcTemplate 。下面的示例显示了使用中的 @DataJpaTest 注解:

@DataJpaTest
class MyRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

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

}

内存嵌入式数据库通常非常适合用于测试,因为它们速度快,而且无需安装。但是,如果您更喜欢针对真实数据库运行测试,则可以使用 @AutoConfigureTestDatabase 注解,如下例所示:

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

    // ...

}
自动配置的JDBC测试(Auto-configured JDBC Tests)

@JdbcTest@DataJpaTest类似,但适用于只需要DataSource 而不使用 Spring Data JDBC 的测试。默认情况下,它会配置一个内存嵌入式数据库和一个 JdbcTemplate。使用 @JdbcTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。

::: tip 提示

@JdbcTest 启用的自动配置列表 见附录

:::

默认情况下,JDBC 测试是事务性的,并在每次测试结束时回滚。详情请参见 Spring Framework Reference Documentation 相关章节。如果这不是你想要的,你可以禁用某个测试或整个类的事务管理,具体做法如下:

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

}

如果您希望您的测试在真实数据库中运行,您可以使用 @AutoConfigureTestDatabase 注解,方法与 @DataJpaTest 相同。(请参阅“自动配置Data JPA 测试”)。

自动配置的Data JDBC 测试(Auto-configured Data JDBC Tests)

@DataJdbcTest@JdbcTest类似,但适用于使用 Spring Data JDBC 存储库的测试。默认情况下,它会配置一个内存嵌入式数据库、一个 JdbcTemplate 和 Spring Data JDBC 存储库。使用 @DataJdbcTest 注解时,只会扫描 AbstractJdbcConfiguration 子类,而不会扫描常规的 @Component@ConfigurationProperties Bean。@EnableConfigurationProperties 可用于包含 @ConfigurationProperties Bean。

::: tip 提示

@DataJdbcTest 启用的自动配置列表 见附录

:::

默认情况下,Data JDBC 测试是事务性的,并在每次测试结束时回滚。详情请参见 Spring Framework Reference Documentation 相关章节。如果这不是你想要的,你可以禁用某个测试或整个测试类的事务管理。
JDBC 示例所示

如果您希望测试在真实数据库中运行,可以使用 @AutoConfigureTestDatabase 注解,方法与 @DataJpaTest 相同。(请参阅“自动配置Data JPA 测试”)。

自动配置的Data R2DBC测试(Auto-configured Data R2DBC Tests)

@DataR2dbcTest@DataJdbcTest类似,但适用于使用 Spring Data R2DBC 存储库的测试。默认情况下,它会配置一个内存嵌入式数据库、一个 R2dbcEntityTemplate 和 Spring Data R2DBC 存储库。使用 @DataR2dbcTest 注解时,常规的 @Component@ConfigurationProperties Bean 不会被扫描。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。

::: tip 提示

@DataR2dbcTest 启用的自动配置列表 见附录

:::

默认情况下,Data R2DBC 测试不是事务性的。

如果您希望测试在真实数据库中运行,可以使用 @AutoConfigureTestDatabase 注解,方法与 @DataJpaTest 相同。(请参阅“自动配置Data JPA 测试”)。

自动配置的JOOQ测试(Auto-configured jOOQ Tests)

您可以以类似于 @JdbcTest 的方式使用 @JooqTest,但仅限于与 jOOQ 相关的测试。由于 jOOQ 在很大程度上依赖于与数据库模式相对应的基于 Java 的模式,因此会使用现有的 DataSource。如果你想用内存数据库取而代之,可以使用 @AutoConfigureTestDatabase 来覆盖这些设置。(有关在 Spring Boot 中使用 jOOQ 的更多信息,请参阅“使用 jOOQ”)。
使用 @JooqTest 注解时,常规的 @Component@ConfigurationProperties Bean 不会被扫描。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。

::: tip 提示

@JooqTest启用的自动配置列表见附录

:::

@JooqTest 配置了一个 DSLContext。下面的示例展示了 @JooqTest 注解的使用:

@JooqTest
class MyJooqTests {

    @Autowired
    private DSLContext dslContext;

    // ...

}

JOOQ 测试是事务性的,默认情况下会在每个测试结束时回滚。如果这不是你想要的,你可以禁用某个测试或整个测试类的事务管理,如JDBC 示例所示

自动配置的Data MongoDB测试(Auto-configured Data MongoDB Tests)

您可以使用 @DataMongoTest 测试 MongoDB 应用程序。默认情况下,它会配置内存中的嵌入式 MongoDB(如果可用)、配置 MongoTemplate、扫描 @Document 类并配置 Spring Data MongoDB 存储库。使用 @DataMongoTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可以使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。(有关在 Spring Boot 中使用 MongoDB 的更多信息,请参阅“MongoDB”)。

::: tip 提示

@DataMongoTest 启用的自动配置设置列表 见附录

:::

下面的示例展示 @DataMongoTest 注解的使用:

@DataMongoTest
class MyDataMongoDbTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    // ...

}

内存中的嵌入式 MongoDB 通常能很好地进行测试,因为它速度快,而且不需要开发人员安装。不过,如果你更喜欢在真正的 MongoDB 服务器上运行测试,则应排除嵌入式 MongoDB 自动配置,如下例所示:

@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
class MyDataMongoDbTests {

    // ...

}
自动配置的Data Neo4j测试Auto-configured Data Neo4j Tests

You can use @DataNeo4jTest to test Neo4j applications. By default, it scans for @Node classes, and configures Spring Data Neo4j repositories. Regular @Component and @ConfigurationProperties beans are not scanned when the @DataNeo4jTest annotation is used. @EnableConfigurationProperties can be used to include @ConfigurationProperties beans. (For more about using Neo4J with Spring Boot,
see “Neo4j”.)

你可以使用 @DataNeo4jTest 测试 Neo4j 应用程序。默认情况下,它会扫描 @Node 类,并配置 Spring Data Neo4j 存储库。使用@DataNeo4jTest注解时,不会扫描常规的@Component@ConfigurationPropertiesBean。可以使用@EnableConfigurationProperties来包含@ConfigurationPropertiesBean。(有关在 Spring Boot 中使用 Neo4J 的更多信息,参见“Neo4j”)。

::: tip 提示

@DataNeo4jTest 启用的自动配置设置列表 见附录

:::

下面的示例展示了在 Spring Boot 中使用 Neo4J 测试的典型设置:

@DataNeo4jTest
class MyDataNeo4jTests {

    @Autowired
    private SomeRepository repository;

    // ...

}

默认情况下,Data Neo4j 测试是事务性的,并在每次测试结束时回滚。更多详情,请参阅 Spring Framework 参考文档中的 相关章节。如果这不是你想要的,你可以禁用某个测试或整个类的事务管理,如下所示:

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

}

::: tip 备注

反应式访问不支持事务测试。如果使用这种风格,则必须如上所述配置 @DataNeo4jTest 测试。

:::

自动配置的Data Redis测试(Auto-configured Data Redis Tests)

你可以使用 @DataRedisTest 测试 Redis 应用程序。默认情况下,它会扫描 @RedisHash 类并配置 Spring Data Redis 存储库。使用 @DataRedisTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可以使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。有关在 Spring Boot 中使用 Redis 的更多信息,请参阅“Redis”)。

::: tip 提示

@DataRedisTest启用的自动配置设置列表见附录

:::

下面的示例展示了 @DataRedisTest 注解的使用:

@DataRedisTest
class MyDataRedisTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
自动配置的Data LDAP测试(Auto-configured Data LDAP Tests)

您可以使用 @DataLdapTest 测试 LDAP 应用程序。默认情况下,它会配置内存中的嵌入式 LDAP(如果可用)、配置 LdapTemplate、扫描 @Entry 类并配置 Spring Data LDAP 存储库。使用 @DataLdapTest 注解时,不会扫描常规的 @Component@ConfigurationProperties Bean。可使用 @EnableConfigurationProperties 来包含 @ConfigurationProperties Bean。(有关在 Spring Boot 中使用 LDAP 的更多信息、请参阅“LDAP”)。

::: tip 提示

@DataLdapTest 启用的自动配置设置列表 见附录

:::

下面的示例展示了 @DataLdapTest 注解的使用:

@DataLdapTest
class MyDataLdapTests {

    @Autowired
    private LdapTemplate ldapTemplate;

    // ...

}

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

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

    // ...

}
自动配置的REST客户端(Auto-configured REST Clients)

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

::: tip 提示

@RestClientTest启用的自动配置设置列表见附录

:::

应使用 @RestClientTestvaluecomponents 属性指定要测试的特定 Bean,如下例所示:

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

}
自动配置的Spring REST Docs测试(Auto-configured Spring REST Docs Tests)

您可以使用 @AutoConfigureRestDocs 注解,在使用 Mock MVC、REST Assured 或 WebTestClient 的测试中使用 Spring REST Docs。它消除了对 Spring REST Docs 中 JUnit 扩展的需求。

@AutoConfigureRestDocs 可用于覆盖默认输出目录(如果使用 Maven,则为 target/generated-snippets;如果使用 Gradle,则为 build/generated-snippets)。它还可用于配置出现在任何文档 URI 中的主机、方案和端口。

使用Mock Mvc进行测试自动配置的Spring REST Docs测试(Auto-configured Spring REST Docs Tests With Mock MVC)

@AutoConfigureRestDocs 可自定义 MockMvc Bean,以便在测试基于 servlet 的 Web 应用程序时使用 Spring REST Docs。您可以使用 @Autowired 注入它,并在测试中使用它,就像通常使用 Mock MVC 和 Spring REST Docs 时一样,如下例所示:

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

    @Autowired
    private MockMvc mvc;

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

}

如果您需要对 Spring REST Docs配置进行比 @AutoConfigureRestDocs 属性更多的控制,可以使用 RestDocsMockMvcConfigurationCustomizer Bean,如下例所示:

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

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

}

如果您想使用 Spring REST Docs对参数化输出目录的支持,可以创建一个 RestDocumentationResultHandler Bean。自动配置会使用此结果处理程序调用 alwaysDo,从而使每次 MockMvc 调用都自动生成默认片段。下面的示例显示了一个已定义的 RestDocumentationResultHandler

@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {

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

}
使用WebTestClient进行测试自动配置的Spring REST Docs测试(Auto-configured Spring REST Docs Tests With WebTestClient)

在测试反应式 Web 应用程序时,还可将 @AutoConfigureRestDocsWebTestClient 结合使用。您可以使用 @Autowired 来注入它,并在测试中使用它,就像通常使用 @WebFluxTest 和 Spring REST Docs 时那样,如下例所示:

@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {

    @Autowired
    private WebTestClient webTestClient;

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

}

如果您需要对 Spring REST Docs配置进行比 @AutoConfigureRestDocs 属性更多的控制,可以使用 RestDocsWebTestClientConfigurationCustomizer Bean,如下例所示:

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

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

}

如果想使用 Spring REST Docs 对参数化输出目录的支持,可以使用 WebTestClientBuilderCustomizer 为每个实体交换结果配置一个消费者。下面的示例展示了这种 WebTestClientBuilderCustomizer 的定义:

@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {

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

}
使用REST Assured进行测试自动配置的Spring REST Docs测试(Auto-configured Spring REST Docs Tests With REST Assured)

@AutoConfigureRestDocs 为您的测试提供了一个预配置为使用 Spring REST Docs的 RequestSpecification Bean。您可以使用 @Autowired 注入该 Bean,并在测试中使用它,就像通常使用 REST Assured 和 Spring REST Docs 时那样,如下例所示:

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

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

}

如果您需要对 Spring REST Docs配置进行比 @AutoConfigureRestDocs 属性更多的控制,则可以使用 RestDocsRestAssuredConfigurationCustomizer Bean,如下例所示:

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

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

}
自动配置的Spring Web Services测试(Auto-configured Spring Web Services Tests)
自动配置的Spring Web Services Client测试(Auto-configured Spring Web Services Client Tests)

您可以使用 @WebServiceClientTest 测试使用 Spring Web Services 项目调用 Web 服务的应用程序。默认情况下,它会配置一个模拟的 WebServiceServerBean 并自动定制您的 WebServiceTemplateBuilder。(有关在 Spring Boot 中使用 Web 服务的更多信息,请参阅“Web Services”)。

::: tip 提示

@WebServiceClientTest 启用的自动配置设置列表 见附录

:::

下面的示例展示了 @WebServiceClientTest 注解的使用:

@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {

    @Autowired
    private MockWebServiceServer server;

    @Autowired
    private SomeWebService someWebService;

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

}
自动配置的Spring Web Services Server 测试(Auto-configured Spring Web Services Server Tests)

您可以使用 @WebServiceServerTest 测试使用 Spring Web Services 项目实现 Web 服务的应用程序。默认情况下,它会配置一个用于调用 Web 服务端点的 MockWebServiceClient Bean。(有关在 Spring Boot 中使用 Web 服务的更多信息,请参阅“Web Services”)。

::: tip 提示

@WebServiceServerTest 启用的自动配置设置列表 见附录

:::

下面的示例展示了 @WebServiceServerTest 注解的使用:

@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {

    @Autowired
    private MockWebServiceClient client;

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

}
附加自动配置和切片(Additional Auto-configuration and Slicing)

每个切片都提供一个或多个 @AutoConfigure...注解,即定义应作为切一部分的自动配置。可通过创建自定义 @AutoConfigure... 注解或在测试中添加 @ImportAutoConfiguration 来逐个测试添加其他自动配置,如下例所示:

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

}

::: tip 备注

请确保不要使用常规的 @Import 注解来导入自动配置,因为 Spring Boot 会以特定的方式处理它们。

:::

另外,还可以为切片注解的任何使用添加额外的自动配置,方法是在存储在 META-INF/spring 中的文件中注册这些配置,如下例所示:

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

com.example.IntegrationAutoConfiguration

在此示例中,com.example.IntegrationAutoConfiguration会在每个注解为@JdbcTest的测试中启用。

::: tip 提示

您可以在该文件中使用带有 # 的注释。

:::

::: tip 提示

只要使用@ImportAutoConfiguration元注解,就能以这种方式自定义片段或@AutoConfigure...注解。

:::

用户配置和切片(User Configuration and Slicing)

If you structure your code in a sensible way, your @SpringBootApplication class is used by default as the configuration of your tests.

It then becomes important not to litter the application’s main class with configuration settings that are specific to a particular area of its functionality.

Assume that you are using Spring Batch and you rely on the auto-configuration for it. You could define your @SpringBootApplication as follows:

如果你的代码结构合理,默认情况下会使用@SpringBootApplication类作为测试的配置。

因此,重要的是不要在应用程序的主类中添加针对其特定功能区域的配置设置。

假设您正在使用 Spring Batch,并依赖于它的自动配置。你可以将 @SpringBootApplication 定义如下:

@SpringBootApplication
@EnableBatchProcessing
public class MyApplication {

    // ...

}

由于该类是测试的源配置,因此任何片段测试都会尝试启动 Spring Batch,这绝对不是你想要做的。推荐的方法是将特定区域的配置转移到与应用程序同级的单独的 @Configuration 类中,如下例所示:

@Configuration(proxyBeanMethods = false)
@EnableBatchProcessing
public class MyBatchConfiguration {

    // ...

}

::: tip 提示

根据应用程序的复杂程度,您可以为自定义设置一个 @Configuration 类,或者为每个域区设置一个类。后一种方法允许您在必要时使用 @Import 注解在其中一个测试中启用它。有关何时需要为切片测试启用特定 @Configuration 类的详细信息,请参阅本节说明

:::

Test slices exclude @Configuration classes from scanning. For example, for a @WebMvcTest, the following configuration will not include the given WebMvcConfigurer bean in the application context loaded by the test slice:

测试切片会从扫描中排除了 @Configuration 类。例如,对于 @WebMvcTest,以下配置不会在测试片段加载的应用程序上下文中包含给定的 WebMvcConfigurer Bean:

@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {

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

}

不过,下面的配置会导致测试片加载自定义的WebMvcConfigurer

@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    // ...

}

另一个造成混乱的原因是类路径扫描。假设您以合理的方式编排代码,但需要扫描一个额外的软件包。您的应用程序可能类似于以下代码:

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

    // ...

}

这样做会有效地覆盖默认组件扫描指令,并产生扫描这两个包的副作用,而与您选择的片段无关。例如,@DataJpaTest 似乎会突然扫描应用程序的组件和用户配置。同样,将自定义指令移至单独的类是解决这一问题的好方法。

::: tip 提示

如果你不能这样做,可以在测试的层次结构中创建一个 @SpringBootConfiguration 来代替它。或者,你也可以为测试指定一个源,从而禁用查找默认源的行为。

:::

使用Spock测试Spring Boot应用程序(Using Spock to Test Spring Boot Applications)

Spock 2.x 可用于测试 Spring Boot 应用程序。为此,请在应用程序的构建中添加对 Spock 的 spock-spring 模块的依赖。spock-spring将 Spring 的测试框架集成到 Spock 中。详见Spock 的 Spring 模块文档

7.9.4 测试工具(Test Utilities)

一些在测试应用程序时非常有用的测试实用程序类被打包成了 spring-boot 的一部分。

ConfigDataApplicationContextInitializer

ConfigDataApplicationContextInitializer 是一个ApplicationContextInitializer,你可以将它应用于你的测试,以加载 Spring Boot 的 application.properties文件。当你不需要 @SpringBootTest 提供的全套功能时,可以使用它,如下例所示:

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

    // ...

}

::: tip 备注

仅使用 ConfigDataApplicationContextInitializer 并不支持 @Value(“${...}”) 注入。它的唯一任务是确保将 application.properties 文件加载到 Spring 的 Environment 中。要获得 @Value 支持,你需要额外配置一个 PropertySourcesPlaceholderConfigurer 或者使用 @SpringBootTest,它会自动为你配置一个。

:::

TestPropertyValues

TestPropertyValues可让您快速向 ConfigurableEnvironmentConfigurableApplicationContext添加属性。您可以使用 key=value 字符串调用它,如下所示:

class MyEnvironmentTests {

    @Test
    void testPropertySources() {
        MockEnvironment environment = new MockEnvironment();
        TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
        assertThat(environment.getProperty("name")).isEqualTo("Boot");
    }

}
OutputCapture

OutputCapture是一个JUnit扩展,可用于捕获System.outSystem.err的输出。要使用该扩展,请添加 @ExtendWith(OutputCaptureExtension.class) 并将 CapturedOutput 作为参数注入测试类构造函数或测试方法,如下所示:

@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {

    @Test
    void testName(CapturedOutput output) {
        System.out.println("Hello World!");
        assertThat(output).contains("World");
    }

}
TestRestTemplate

TestRestTemplate是 Spring 的 RestTemplate的便利替代品,在集成测试中非常有用。你可以获得一个普通模板或一个发送 Basic HTTP 身份验证(用户名和密码)的模板。无论哪种情况,模板都是容错的。这意味着它不会在出现 4xx 和 5xx 错误时抛出异常,从而以测试友好的方式运行。相反,可以通过返回的 ResponseEntity 及其状态代码来检测此类错误。

::: tip 提示

Spring Framework 5.0 提供了新的 WebTestClient,可用于 WebFlux 集成测试WebFlux 和 MVC 端到端测试。与 TestRestTemplate 不同,它为断言提供了流畅的 API。

:::

建议使用 Apache HTTP 客户端(4.3.2 或更高版本),但并非必须。如果您的类路径上有该客户端,TestRestTemplate会对客户端进行适当配置。如果使用 Apache 的 HTTP 客户端,则会启用一些额外的测试友好功能:

  • 不跟踪重定向(因此可以断言响应位置)。
  • 忽略 Cookie(因此模板是无状态的)。

如以下示例所示,TestRestTemplate 可直接在集成测试中实例化:

class MyTests {

    private final TestRestTemplate template = new TestRestTemplate();

    @Test
    void testRequest() {
        ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
        assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
    }

}

另外,如果使用了带有WebEnvironment.RANDOM_PORTWebEnvironment.DEFINED_PORT @SpringBootTest注解,就可以注入一个完全配置好的TestRestTemplate并开始使用。如有必要,还可通过 RestTemplateBuilder Bean 进行其他定制。任何未指定主机和端口的 URL 都会自动连接到嵌入式服务器,如下例所示:

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

    @Autowired
    private TestRestTemplate template;

    @Test
    void testRequest() {
        HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
        assertThat(headers.getLocation()).hasHost("other.example.com");
    }

    @TestConfiguration(proxyBeanMethods = false)
    static class RestTemplateBuilderConfiguration {

        @Bean
        RestTemplateBuilder restTemplateBuilder() {
            return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
                .setReadTimeout(Duration.ofSeconds(1));
        }

    }

}

7.10 创建自己的自动配置(Creating Your Own Auto-configuration)

如果您在一家开发共享库的公司工作,或者如果您在开发开源或商业库,您可能想开发自己的自动配置。自动配置类可以捆绑在外部 jar 中,但仍会被 Spring Boot 获取。

自动配置可以与 “启动器 ”相关联,“启动器 ”提供自动配置代码以及与之配合使用的典型库。我们首先介绍构建自己的自动配置所需的知识,然后介绍创建自定义启动器所需的典型步骤

7.10.1 了解自动配置Beans(Understanding Auto-configured Beans)

You can browse the source code of spring-boot-autoconfigure to see the @AutoConfiguration classes that Spring provides (see
the file).

实现自动配置的类会使用注解 @AutoConfiguration。该注解本身使用 @Configuration 元注解,使自动配置成为标准的 @Configuration 类。附加的 @Conditional 注解用于限制自动配置何时应用。通常,自动配置类使用 @ConditionalOnClass@ConditionalOnMissingBean 注解。这可确保自动配置仅在找到相关类时才会应用
并且您没有声明自己的 @Configuration 时,自动配置才会适用。

你可以浏览 spring-boot-autoconfigure的源代码,查看 Spring 提供的@AutoConfiguration类(参见
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件)。

7.10.2 定位自动配置候选方案(Locating Auto-configuration Candidates)

Spring Boot 会检查您发布的 jar 中是否存在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。该文件应列出您的配置类,每行一个类名,如下例所示:

com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

::: tip 提示

您可以使用 # 字符在导入文件中添加注释。

:::

::: tip 备注

自动配置必须通过在导入文件中命名的方式加载。确保它们被定义在特定的包空间中,并且永远不会成为组件扫描的目标。此外,自动配置类不应允许组件扫描找到其他组件。应使用特定的 @Import 来代替。

:::

如果您的配置需要按特定顺序应用,您可以使用 @AutoConfiguration注解上的 beforebeforeNameafterafterName 属性,或使用专用的 @AutoConfigureBefore@AutoConfigureAfter注解上的属性。例如,如果您提供了特定于网络的配置类可能需要在 WebMvcAutoConfiguration 之后应用。

如果您想对某些不应直接相互了解的自动配置进行排序,也可以使用 @AutoConfigureOrder。该注解与常规的 @Order 注解语义相同,但为自动配置类提供了专用的顺序。

与标准的 @Configuration 类一样,应用自动配置类的顺序只影响定义其 Bean 的顺序。随后创建这些 Bean 的顺序不会受到影响,而是由每个 Bean 的依赖关系和任何 @DependsOn 关系决定。

7.10.3 条件注解(Condition Annotations)

您几乎总是希望在自动配置类中包含一个或多个 @Conditional 注解。@ConditionalOnMissingBean 注解就是一个常见的例子,它允许开发人员在对默认值不满意时覆盖自动配置。

Spring Boot 包含大量 @Conditional 注解,您可以通过注解 @Configuration 类或单个 @Bean 方法在自己的代码中重复使用这些注解。这些注解包括

Class 条件(Class Conditions)

The @ConditionalOnClass and @ConditionalOnMissingClass annotations let @Configuration classes be included based on the presence or absence of specific classes. Due to the fact that annotation metadata is parsed by using ASM, you can use the value attribute to refer to the real class, even though that class might not actually appear on the running application classpath. You can also use the name attribute if you prefer to specify the class name by using a String
value.

@ConditionalOnClass@ConditionalOnMissingClass注解使@Configuration类可以根据特定类的存在或不存在而进行诸如。由于注解元数据是通过 ASM解析的,因此您可以使用 value 属性来引用真正的类,即使该类实际上可能不会出现在运行应用程序的 classpath 中。如果您希望使用 String 值来指定类名,也可以使用 name 属性值来指定类名。

这种机制并不适用于 @Bean 方法,在这种方法中,返回类型通常是条件的目标:在方法上的条件适用之前,JVM 将加载类,并可能处理方法引用,如果类不存在,方法引用将失败。

要处理这种情况,可以使用单独的 @Configuration 类来隔离条件,如下例所示:

@AutoConfiguration
// Some conditions ...
public class MyAutoConfiguration {

    // Auto-configured beans ...

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(SomeService.class)
    public static class SomeServiceConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public SomeService someService() {
            return new SomeService();
        }

    }

}

::: tip 提示

如果使用 @ConditionalOnClass@ConditionalOnMissingClass 作为元注解的一部分来编写自己的组成注解,则必须使用 name,因为在这种情况下引用类是不会被处理的。

:::

Bean 条件(Bean Conditions)

通过 @ConditionalOnBean@ConditionalOnMissingBean 注解,可以根据特定 Bean 的存在或不存在来包含 Bean。您可以使用 value 属性按类型指定 Bean,或使用 name 属性按名称指定 Bean。通过 search 属性,您可以限制在搜索 Bean 时应考虑的 ApplicationContext 层次结构。

如下面的示例所示,当将目标类型放在 @Bean 方法上时,目标类型默认为该方法的返回类型:

@AutoConfiguration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }

}

在前面的示例中,如果 ApplicationContext 中尚未包含 SomeService 类型的 Bean,则将创建 someService Bean。

::: tip 提示

您需要非常注意添加 Bean 定义的顺序,因为这些条件是根据目前已处理的内容进行评估的。因此,我们建议在自动配置类中只使用 @ConditionalOnBean@ConditionalOnMissingBean 注解(因为这些注解保证在添加任何用户定义的 Bean 定义后加载)。

:::

::: tip 备注

@ConditionalOnBean@ConditionalOnMissingBean 并不阻止创建 @Configuration 类。在类级别使用这些条件与使用注解标记每个包含的 @Bean 方法之间的唯一区别是,如果条件不匹配,前者会阻止将 @Configuration 类注册为 Bean。

:::

::: tip 提示

在声明 @Bean 方法时,请在方法的返回类型中提供尽可能多的类型信息。例如,如果 Bean 的具体类实现了一个接口,那么 Bean 方法的返回类型就应该是具体类而不是接口。在 @Bean 方法中提供尽可能多的类型信息在使用 Bean 条件时尤为重要,因为对这些条件的评估只能依赖于方法签名中可用的类型信息。

:::

Property 条件(Property Conditions)

通过 @ConditionalOnProperty 注解,可根据 Spring 环境属性加入配置。使用 prefixname 属性指定应检查的属性。默认情况下,任何存在且不等于 false 的属性都会被匹配。您还可以使用 havingValuematchIfMissing 属性创建更高级的检查。

Resource 条件(Resource Conditions)

@ConditionalOnResource 注解允许配置仅在特定资源存在时才被包含。资源可通过使用通常的 Spring 约定来指定,如下例所示:file:/home/user/test.dat

Web Application 条件(Web Application Conditions)

通过 @ConditionalOnWebApplication@ConditionalOnNotWebApplication 注解,可以根据应用程序是否是网络应用程序来包含配置。基于 servlet 的网络应用是指任何使用 Spring WebApplicationContext、定义了 session 作用域或拥有 ConfigurableWebEnvironment 的应用。反应式网络应用是指使用 ReactiveWebApplicationContext或具有ConfigurableReactiveWebEnvironment的任何应用。

@ConditionalOnWarDeployment@ConditionalOnNotWarDeployment注解允许根据应用程序是否是部署到 servlet 容器的传统 WAR 应用程序来包含配置。对于使用嵌入式 Web 服务器运行的应用程序,此条件将不匹配。

SpEL 表达式条件(SpEL Expression Conditions)

@ConditionalOnExpression注解允许根据SpEL 表达式的结果加入配置。

::: tip 备注

在表达式中引用一个 Bean 会导致该 Bean 在上下文刷新处理的早期就被初始化。因此,Bean 无法进行后处理(如配置属性绑定),其状态也可能不完整。

:::

::: tip 备注

在表达式中引用一个 Bean 会导致该 Bean 在上下文刷新处理的早期就被初始化。因此,Bean 无法进行后处理(如配置属性绑定),其状态也可能不完整。

:::

7.10.4 测试自己的自动配置(Testing your Auto-configuration)

自动配置会受到许多因素的影响:用户配置(@Bean定义和 Environment自定义)、条件评估(特定库的存在)等。具体来说,每个测试都应创建一个定义明确的应用上下文ApplicationContext),它代表了这些自定义的组合。ApplicationContextRunner 可以很好地实现这一点。

ApplicationContextRunner通常定义为测试类的一个字段,用于收集基本的通用配置。下面的示例确保始终调用 MyServiceAutoConfiguration

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));

::: tip 提示

如果需要定义多个自动配置,则无需对其声明进行排序,因为它们的调用顺序与运行应用程序时完全相同。

:::

每个测试都可以使用运行程序来表示特定的用例。例如,下面的示例调用了用户配置(UserConfiguration),并检查自动配置是否正确关闭。调用 run 提供了一个回调上下文,可与 AssertJ 一起使用。

@Test
void defaultServiceBacksOff() {
    this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
    });
}

@Configuration(proxyBeanMethods = false)
static class UserConfiguration {

    @Bean
    MyService myCustomService() {
        return new MyService("mine");
    }

}

还可以轻松自定义Environment,如下例所示:

@Test
void serviceNameCanBeConfigured() {
    this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
    });
}

运行程序还可用于显示 ConditionEvaluationReport(条件评估报告)。报告可在 INFODEBUG 级别打印。下面的示例展示了如何使用 ConditionEvaluationReportLoggingListener 在自动配置测试中打印报告。

class MyConditionEvaluationReportingTests {

    @Test
    void autoConfigTest() {
        new ApplicationContextRunner()
            .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
            .run((context) -> {
                    // Test something...
            });
    }

}
Simulating a Web Context

如果需要测试仅在 servlet 或反应式网络应用上下文中运行的自动配置,请分别使用 WebApplicationContextRunnerReactiveWebApplicationContextRunner

Overriding the Classpath

还可以测试运行时不存在特定类和/或包时的情况。Spring Boot 随附了一个FilteredClassLoader,运行程序可以轻松地使用它。在下面的示例中,我们断言如果不存在 MyService,自动配置将被正确禁用:

@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
    this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
        .run((context) -> assertThat(context).doesNotHaveBean("myService"));
}

7.10.5 创建自己的启动器( Creating Your Own Starter)

一个典型的 Spring Boot 启动程序包含自动配置和定制特定技术基础架构的代码,我们称之为 “acme”。为了使其易于扩展,可以向环境公开专用命名空间中的大量配置键。最后,还提供了一个 “启动器 ”依赖项,以帮助用户尽可能轻松地开始使用。

具体来说,自定义启动器可以包含以下内容:

  • autoconfigure模块包含 ”acme "的自动配置代码。
  • starter模块提供了对autoconfigure模块、”acme "模块和其他有用的依赖关系的依赖。简而言之,添加启动器就能提供开始使用该库所需的一切。

这种将两个模块分开的做法完全没有必要。如果 “acme ”有多种选项或可选功能,那么最好将自动配置分开,因为这样可以清楚地表达某些功能是可选的。此外,您还可以制作一个启动器,对这些可选的依赖性提出自己的看法。与此同时,其他人可以只依赖于autoconfigure模块,然后制作他们自己的starter,并提出不同的意见。

如果 “自动配置 ”模块相对简单,而且没有可选功能,那么将这两个模块合并到启动器中无疑是一个不错的选择。

命名(Naming)

您应确保为启动器提供适当的命名空间。即使使用不同的 Maven groupId,也不要以 spring-boot 开头。我们将来可能会为您自动配置的内容提供官方支持。

根据经验,应使用启动器的名称来命名组合模块。例如,假设你正在为 “acme ”创建一个启动器,并将自动配置模块命名为 acme-spring-boot,将启动器命名为 acme-spring-boot-starter。如果只有一个模块将两者结合在一起,则将其命名为 acme-spring-boot-starter

配置键(Configuration keys)

如果您的启动程序提供配置键,请为它们使用唯一的命名空间。尤其是,不要将您的键包含在 Spring Boot 使用的命名空间(如 servermanagementspring 等)中。如果您使用相同的命名空间,我们将来可能会修改这些命名空间,从而破坏您的模块。作为经验法则,请在所有键的前缀加上您拥有的命名空间(例如 acme)。

为每个属性添加字段 javadoc,确保配置键都有文档记录,如下例所示:

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

}

::: tip 备注

您只能使用纯文本的 @ConfigurationProperties 字段 Javadoc,因为它们在添加到 JSON 之前不会被处理。

:::

以下是我们内部遵循的一些规则,以确保描述的一致性:

  • 不要以 “The ”或 “A ”作为描述的开头。
  • 对于boolean 类型,以 "Whether"或 "Enable"开始描述。
  • 对于基于集合的类型,以 “逗号分隔列表 ”开始描述
  • 使用 java.time.Duration而不是long,如果默认单位与毫秒不同,请对其进行说明,例如 “如果未指定持续时间后缀,将使用秒”。
  • 除非必须在运行时确定,否则不要在描述中提供默认值。

确保触发元数据生成,以便 IDE 也能为你的密钥提供帮助。你可能需要查看生成的元数据(META-INF/spring-configuration-metadata.json),以确保你的key被正确地记录下来。在兼容的集成开发环境中使用自己的启动器也是验证元数据质量的一个好主意。

autoconfigure模块(The “autoconfigure” Module)

autoconfigure模块包含了开始使用该库所需的一切内容。它还可能包含配置键定义(如@ConfigurationProperties)和任何可用于进一步自定义组件初始化方式的回调接口。

::: tip 提示

你应该将对该库的依赖标记为可选,这样你就可以更轻松地在项目中包含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 目标将依赖关系添加到 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"
}
启动器模块(Starter Module)

启动器实际上是一个空 jar。它的唯一目的是提供使用库所需的依赖项。你可以把它看作是对开始工作所需内容的一种看法。

不要对添加了启动程序的项目做任何假设。如果您要自动配置的库通常需要其他启动程序,也请提及他们。如果可选依赖项的数量较多,则很难提供一套合适的默认依赖项,因为您应避免包含对库的典型用法而言不必要的依赖项。换句话说,你不应该包含可选的依赖项。

::: tip 备注

无论采用哪种方式,您的启动器都必须直接或间接引用 Spring Boot 核心启动器 (spring-boot-starter)(如果您的启动器依赖于其他启动器,则无需添加)。如果仅使用自定义启动器创建项目,则核心启动程序将支持Spring引导的核心功能。

:::

7.11 Kotlin支持(Kotlin Support)

Kotlin 是一种针对 JVM(和其他平台)的静态类型语言,它允许编写简洁优雅的代码,同时提供与现有 Java 库的互操作性

Spring Boot 通过利用其他 Spring 项目(如 Spring Framework、Spring Data 和 Reactor)中的支持来提供 Kotlin 支持。有关详细信息,请参阅 Spring Framework Kotlin 支持文档

开始使用 Spring Boot 和 Kotlin 的最简单方法是遵循 本综合教程。您可以使用 start.spring.io 创建新的 Kotlin 项目。如果您需要支持,请随时加入 Kotlin Slack 的 #spring 频道,或在 Stack Overflow 上使用 springkotlin 标签提问。

7.11.1 要求(Requirements)

Spring Boot 至少需要 Kotlin 1.3.x,并通过依赖关系管理来管理合适的 Kotlin 版本。要使用 Kotlin,classpath 上必须有org.jetbrains.kotlin:kotlin-stdliborg.jetbrains.kotlin:kotlin-reflect。也可以使用 kotlin-stdlib 变体 kotlin-stdlib-jdk7kotlin-stdlib-jdk8

由于Kotlin 类默认为最终类,你很可能想要配置 kotlin-spring 插件,以便自动打开 Spring 标注的类,从而可以代理它们。

在 Kotlin 中序列化/反序列化 JSON 数据需要 Jackson 的 Kotlin 模块。在类路径上找到该模块时,系统会自动注册该模块。如果存在 Jackson 和 Kotlin,但没有 Jackson Kotlin 模块,则会记录一条警告信息。

::: tip 备注

如果在 start.spring.io 上启动 Kotlin 项目,则默认会提供这些依赖项和插件。

:::

7.11.2 无安全性(Null-safety)

Kotlin的关键特性之一是无安全性。它在编译时处理 null值,而不是将问题推迟到运行时再处理,也不会遇到 NullPointerException(空指针异常)。这有助于消除常见的 bug 源头,而无需花费像 Optional 这样的封装器的成本。Kotlin 还允许使用具有可空值的函数式构造,详情请参阅 Kotlin 无安全性综合指南 中所述。

虽然 Java 不允许在其类型系统中表示无安全性,但 Spring Framework、Spring Data 和 Reactor 现在通过工具友好注解为其 API 提供了表示无安全性。默认情况下,Kotlin 中使用的 Java API 中的类型会被识别为平台类型,其空检查会被放宽。Kotlin 对 JSR 305 注释的支持 与 nullability 注解相结合,为 Kotlin 中的相关 Spring API 提供了无安全性。

JSR 305 检查可通过添加带有以下选项的 -Xjsr305 编译器标记来配置:-Xjsr305={strict|warn|ignore}。默认行为与 -Xjsr305=warn相同。要在从 Spring API 推断的 Kotlin 类型中考虑到空安全性,就必须使用 strict 值,但在使用时应了解 Spring API 空性声明即使在小版本之间也会发生变化,而且将来可能会添加更多检查)。

::: warning 警告

尚未支持通用类型参数、变量和数组元素的无效性。有关最新信息,请参见 SPR-15942。另外请注意,Spring Boot 自身的 API 尚未注释

:::

7.11.3 Kotlin API

runApplication

Spring Boot 提供了一种使用 runApplication<MyApplication>(*args) 运行应用程序的惯用方式,如下例所示:

@SpringBootApplication
class MyApplication

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

它可以直接替代 SpringApplication.run(MyApplication::class.java, *args)。它还允许自定义应用程序,如下例所示:

runApplication<MyApplication>(*args) {
    setBannerMode(OFF)
}
扩展(Extensions)

Kotlin 扩展提供了用附加功能扩展现有类的能力。Spring Boot Kotlin API 利用这些扩展为现有 API 添加了新的 Kotlin 特定便利功能。

提供了TestRestTemplate 扩展,类似于 Spring Framework 为 Spring Framework 中的RestOperations提供的扩展。除其他外,这些扩展还能利用 Kotlin 具体类型参数。

7.11.4 依赖管理(Dependency management)

为了避免在类路径上混合不同版本的 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 属性自定义版本。

::: tip 提示

start.spring.io上至少有一个反应式依赖的 Kotlin 项目进行引导 ,默认提供 org.jetbrains.kotlinx:kotlinx-coroutines-reactor 依赖。

:::

7.11.5 @ConfigurationProperties

@ConfigurationProperties@ConstructorBinding结合使用时,可支持具有不可变 val 属性的类,如下例所示:

@ConstructorBinding
@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
        val name: String,
        val description: String,
        val myService: MyService) {

    data class MyService(
            val apiToken: String,
            val uri: URI
    )
}

::: tip 提示

要使用注释处理器生成您自己的元数据kapt应配置spring-boot-configuration-processor依赖关系。请注意,由于 kapt 所提供模型的局限性,某些功能(如检测默认值或废弃项)无法正常工作。

:::

7.11.6 测试(Testing)

虽然可以使用 JUnit 4 测试 Kotlin 代码,但默认提供并推荐使用 JUnit 5。JUnit 5能让测试类实例化一次,并在类的所有测试中重复使用。这使得可以在非静态方法上使用 @BeforeAll@AfterAll 注解,这非常适合 Kotlin。

要模拟 Kotlin 类,建议使用 MockK。如果您需要与 Mockito 特定的 @MockBean@SpyBean 注解相对应的 MockK,可以使用 SpringMockK,它提供了类似的 @MockkBean@SpykBean注解。

7.11.7 资料(Resources)

延伸阅读(Further reading)
示例(Examples)

7.12 下一步阅读(What to Read Next)

如果您想了解本节讨论的任何类的更多信息,请参阅 Spring Boot API 文档 或者可以直接浏览 源代码。如果您有具体问题,请参阅 how-to 部分。

如果您已经熟悉 Spring Boot 的核心功能,可以继续阅读 production-ready features.

  • 29
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值