# 核心功能

这一节深入讨论了 Spring 引导的细节。在这里,你可以了解你可能想要使用和自定义的关键功能。如果你还没有这样做,你可能希望阅读“getting-started.html”和“using.html”部分,以便你有一个良好的基础知识。

# 1. SpringApplication

SpringApplication类提供了一种方便的方式来引导从main()方法启动的 Spring 应用程序。在许多情况下,可以委托给静态SpringApplication.run方法,如以下示例所示:

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

@SpringBootApplication
public class MyApplication {

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

}

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

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

2021-02-03 10:33:25.224  INFO 17321 --- [           main] o.s.b.d.s.s.SpringApplicationExample    : Starting SpringApplicationExample using Java 1.8.0_232 on mycomputer with PID 17321 (/apps/myjar.jar started by pwebb)
2021-02-03 10:33:25.226  INFO 17900 --- [           main] o.s.b.d.s.s.SpringApplicationExample    : No active profile set, falling back to default profiles: default
2021-02-03 10:33:26.046  INFO 17321 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-02-03 10:33:26.054  INFO 17900 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-02-03 10:33:26.055  INFO 17900 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-02-03 10:33:26.097  INFO 17900 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-02-03 10:33:26.097  INFO 17900 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 821 ms
2021-02-03 10:33:26.144  INFO 17900 --- [           main] s.tomcat.SampleTomcatApplication         : ServletContext initialized
2021-02-03 10:33:26.376  INFO 17900 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-02-03 10:33:26.384  INFO 17900 --- [           main] o.s.b.d.s.s.SpringApplicationExample    : Started SampleTomcatApplication in 1.514 seconds (JVM running for 1.823)

默认情况下,将显示INFO日志消息,包括一些相关的启动细节,例如启动应用程序的用户。如果需要INFO以外的日志级别,则可以设置它,如Log Levels中所述。应用程序版本是使用主应用程序类的包中的实现版本来确定的。可以通过将spring.main.log-startup-info设置为false来关闭启动信息日志。这也将关闭应用程序活动配置文件的日志记录。

要在启动期间添加额外的日志记录,可以在SpringApplication的子类中覆盖logStartupInfo(boolean)

# 1.1.启动失败

如果你的应用程序无法启动,注册FailureAnalyzers将有机会提供一个专用的错误消息和具体的操作来解决问题。例如,如果你在端口8080上启动了一个 Web 应用程序,并且该端口已经在使用中,那么你应该会看到类似于以下消息的内容:

***************************
APPLICATION FAILED TO START
***************************

Description:

Embedded servlet container failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that is listening on port 8080 or configure this application to listen on another port.
Spring Boot 提供了许多FailureAnalyzer实现,并且可以添加你自己的

如果没有故障分析器能够处理异常,则仍然可以显示完整的条件报告,以更好地了解出了什么问题。为此,你需要[启用debug属性]或[启用DEBUG日志](#features.logging.log-levels)来实现org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

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

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

# 1.2.惰性初始化

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

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

可以使用SpringApplicationBuilder上的lazyInitialization方法或setLazyInitialization上的setLazyInitialization方法以编程方式启用惰性初始化。或者,可以使用spring.main.lazy-initialization属性启用它,如以下示例所示:

属性

spring.main.lazy-initialization=true

Yaml

spring:
  main:
    lazy-initialization: true
如果你希望在对应用程序的其余部分使用惰性初始化的同时禁用某些 bean 的惰性初始化,那么可以使用@Lazy(false)注释显式地将它们的惰性属性设置为 false。

# 1.3.自定义横幅

可以通过将banner.txt文件添加到 Classpath 或将spring.banner.location属性设置为此类文件的位置来更改在启动时打印的横幅。如果文件的编码不是 UTF-8,则可以设置spring.banner.charset。除了文本文件,还可以在 Classpath 中添加banner.gifbanner.jpgbanner.png图像文件,或者设置spring.banner.image.location属性。图像被转换为 ASCII 艺术表现,并打印在任何文本横幅之上。

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

Variable 说明
${application.version} 应用程序的版本号,如MANIFEST.MF中声明的。
例如,Implementation-Version: 1.0打印为1.0
${application.formatted-version} 应用程序的版本号,如在MANIFEST.MF中声明的那样,并已格式化以供显示(周围用括号包围,前缀为v)。
例如(v1.0)
${spring-boot.version} 你正在使用的 Spring 引导版本。
例如2.6.4
${spring-boot.formatted-version} 你正在使用的 Spring 引导版本,格式为显示(周围用括号和前缀v)。
例如(v2.6.4)
${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME}) 其中NAME是一个 ANSI 逃逸代码的名称。
详见[AnsiPropertySource](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot/SRC/main/java/org/springframework/boot/ansi/ansipropertysource.java)。
${application.title} MANIFEST.MF中声明的应用程序的标题。
例如Implementation-Title: MyApp打印为MyApp
如果你想以编程方式生成横幅,可以使用SpringApplication.setBanner(…​)方法。
使用org.springframework.boot.Banner接口并实现你自己的printBanner()方法。

你还可以使用spring.main.banner-mode属性来确定横幅是否必须在System.outconsole)上打印、发送到配置的记录器(log),或者根本不产生(off)。

打印的横幅以单件 Bean 的形式注册,其名称如下:springBootBanner

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

这就是为什么我们建议你总是使用java org.springframework.boot.loader.JarLauncher启动 Unpacked JAR。
这将在构建 Classpath 并启动应用程序之前初始化application.*横幅变量。

# 1.4.自定义 SpringApplication

如果SpringApplication默认值不符合你的喜好,那么你可以创建一个本地实例并对其进行自定义。例如,要关闭横幅,你可以写:

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

@SpringBootApplication
public class MyApplication {

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

}

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

也可以通过使用application.properties文件配置SpringApplication。详见 外部化配置

有关配置选项的完整列表,请参见[SpringApplicationJavadoc](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/springapplication.html)。

# 1.5.Fluent Builder API

如果你需要构建ApplicationContext层次结构(具有父/子关系的多个上下文),或者如果你更喜欢使用“Fluent”Builder API,则可以使用SpringApplicationBuilder

SpringApplicationBuilder允许你将多个方法调用链接在一起,并且包括parentchild方法,这些方法允许你创建一个层次结构,如以下示例所示:

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

在创建ApplicationContext层次结构时会有一些限制。
例如,Web 组件必须被包含在子上下文中,并且相同的Environment同时用于父上下文和子上下文。
参见[SpringApplicationBuilderJavadoc](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/org/applicationframework/applicbuilder/applicbuilder/applicbuilder/applatform/applicform/applicbuilder)以获取全部详细信息。

# 1.6.应用程序可用性

当部署在平台上时,应用程序可以使用Kubernetes 探测器 (opens new window)之类的基础设施向平台提供有关其可用性的信息。 Spring 启动包括对常用的“活性”和“就绪”可用性状态的开箱即用支持。如果你使用 Spring Boot 的“actuator”支持,那么这些状态将作为健康端点组公开。

此外,你还可以通过将ApplicationAvailability接口注入你自己的 bean 来获得可用性状态。

# 1.6.1.活性状态

应用程序的“活性”状态表示其内部状态是否允许其正确工作,或者在当前出现故障时是否允许其自行恢复。中断的“活性”状态意味着应用程序处于无法恢复的状态,基础结构应该重新启动应用程序。

通常,“活性”状态不应该基于外部检查,例如健康检查
如果是这样,失败的外部系统(数据库、Web API、外部缓存)将触发大规模重启和跨平台的级联故障。

Spring 引导应用程序的内部状态主要由 Spring ApplicationContext表示。 Spring 如果应用程序上下文已成功启动,则启动假定应用程序处于有效状态。一旦上下文被刷新,应用程序就被认为是实时的,请参见Spring Boot application lifecycle and related Application Events

# 1.6.2.准备状态

应用程序的“就绪”状态表明该应用程序是否已准备好处理流量。一个失败的“就绪”状态告诉平台,它现在不应该将流量路由到应用程序。这通常发生在启动过程中,而CommandLineRunnerApplicationRunner组件正在被处理,或者在任何时候,如果应用程序决定它太忙而不能进行额外的流量。

一旦调用了应用程序和命令行运行器,就认为应用程序已准备就绪,请参见Spring Boot application lifecycle and related Application Events

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

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

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

例如,我们可以将应用程序的“就绪”状态导出到一个文件中,以便 Kubernetes 的“Exec Probe”可以查看该文件:

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

@Component
public class MyReadinessStateExporter {

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

}

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

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

@Component
public class MyLocalCacheVerifier {

    private final ApplicationEventPublisher eventPublisher;

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

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

}

Spring Boot 提供Kubernetes HTTP 探测与执行器健康端点的“活性”和“准备”。你可以获得更多关于deploying Spring Boot applications on Kubernetes in the dedicated section的指导。

# 1.7.应用程序事件和监听器

除了通常的 Spring 框架事件,如[ContextRefreshedEvent](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/event/contextrefreshedevent.html),aSpringApplication发送一些额外的应用程序事件。

有些事件实际上是在ApplicationContext被创建之前被触发的,所以你不能在这些事件上注册一个侦听器作为@Bean
你可以用SpringApplication.addListeners(…​)方法或SpringApplicationBuilder.listeners(…​)方法来注册它们,

如果你想要自动注册这些侦听器,无论创建应用程序的方式如何,你都可以通过使用org.springframework.context.ApplicationListener键向项目添加META-INF/spring.factories文件并引用侦听器,如以下示例所示:

<br/>org.springframework.context.ApplicationListener=com.example.project.MyListener<br/>

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

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

  2. 当要在上下文中使用的Environment是已知的但在创建上下文之前,则发送ApplicationEnvironmentPreparedEvent

  3. ApplicationContext准备好并且在加载任何 Bean 定义之前调用了应用上下文初始化器时,发送ApplicationContextInitializedEvent

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

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

  6. 在使用LivenessState.CORRECT之后立即发送AvailabilityChangeEvent,以表明该应用程序被认为是动态的。

  7. 在调用任何应用程序和命令行运行器后发送ApplicationReadyEvent

  8. 在使用ReadinessState.ACCEPTING_TRAFFIC之后立即发送AvailabilityChangeEvent,以表明应用程序已准备好为请求提供服务。

  9. 如果启动时有异常,则发送ApplicationFailedEvent

上面的列表只包含与SpringApplication绑定的SpringApplicationEvent。除了这些,以下事件还发布在ApplicationPreparedEvent之后和ApplicationStartedEvent之前:

  • WebServer准备好之后发送WebServerInitializedEventServletWebServerInitializedEventReactiveWebServerInitializedEvent分别是 Servlet 和活性变量。

  • 当刷新ApplicationContext时,将发送ContextRefreshedEvent

你通常不需要使用应用程序事件,但是知道它们的存在是很方便的。
在内部, Spring boot 使用事件来处理各种任务。
默认情况下,事件侦听器在同一个线程中执行时,不应该运行可能很长的任务。
考虑使用应用程序和命令行运行器

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

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

# 1.8.网络环境

aSpringApplication试图代表你创建ApplicationContext的正确类型。用于确定WebApplicationType的算法如下:

  • 如果存在 Spring MVC,则使用AnnotationConfigServletWebServerApplicationContext

  • 如果 Spring MVC 不存在而 Spring WebFlux 存在,则使用AnnotationConfigReactiveWebServerApplicationContext

  • 否则,使用AnnotationConfigApplicationContext

这意味着,如果你在同一应用程序中使用 Spring MVC 和 Spring WebFlux 中的新WebClient,则默认情况下将使用 Spring MVC。你可以通过调用setWebApplicationType(WebApplicationType)轻松地重写它。

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

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

# 1.9.访问应用程序参数

如果需要访问传递给SpringApplication.run(…​)的应用程序参数,则可以插入org.springframework.boot.ApplicationArguments Bean。ApplicationArguments接口提供对 RAWString[]参数以及解析的optionnon-option参数的访问,如以下示例所示:

import java.util.List;

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

@Component
public class MyBean {

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

}

Spring Boot 还使用 Spring Environment注册了CommandLinePropertySource
这也允许你通过使用@Value注释来注入单个应用程序参数。

# 1.10.使用 ApplicationRunner 或 CommandLineRunner

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

此契约非常适合在应用程序启动后但在它开始接受流量之前运行的任务。

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

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

@Component
public class MyCommandLineRunner implements CommandLineRunner {

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

}

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

# 1.11.应用程序退出

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

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

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

@SpringBootApplication
public class MyApplication {

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

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

}

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

# 1.12.管理功能

通过指定spring.application.admin.enabled属性,可以为应用程序启用与管理相关的特性。这暴露了平台上的[SpringApplicationAdminMXBean](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot/SRC/main/java/org/springframework/boot/admin/applicationadmxbean.java)MBeanServer。你可以使用此功能远程管理你的 Spring 启动应用程序。这个特性对于任何服务包装器实现都是有用的。

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

# 1.13.应用程序启动跟踪

在应用程序启动期间,SpringApplicationApplicationContext执行许多与应用程序生命周期、bean 生命周期甚至处理应用程序事件有关的任务。通过[ApplicationStartup](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/metrics/applicationstartup.html), Spring Framework[允许你使用StartupStep对象跟踪应用程序启动序列](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/ception/core-funicity.html#context-function-收集这些数据可以用于分析目的,或者仅仅是为了更好地了解应用程序启动过程。

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

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

@SpringBootApplication
public class MyApplication {

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

}

第一个可用的实现方式,FlightRecorderApplicationStartup是由 Spring 框架提供的。它将 Spring 特定的启动事件添加到 Java 飞行记录器会话中,用于分析应用程序,并将其 Spring 上下文生命周期与 JVM 事件(例如分配、GCS、类加载……)关联起来。一旦配置完成,你就可以通过启用飞行记录器来运行应用程序来记录数据:

$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar

Spring boot 附带BufferingApplicationStartup变体;该实现用于缓冲启动步骤并将它们排入外部度量系统。应用程序可以在任何组件中请求类型BufferingApplicationStartup的 Bean。

Spring 启动还可以被配置为公开一个[startup端点](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/actuator-api/htmlsingle/#startup),它将此信息作为 JSON 文档提供。

# 2. 外部化配置

Spring 引导允许你外部化你的配置,以便你可以在不同的环境中使用相同的应用程序代码。你可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。

可以使用@Value注释直接将属性值注入到 bean 中,该注释可通过 Spring 的Environment抽象访问,也可以通过绑定到结构化对象通过@Configuration属性访问。

Spring 引导使用非常特殊的PropertySource顺序,该顺序被设计为允许合理地覆盖值。按以下顺序考虑属性(来自较低项的值覆盖了较早的项):

  1. 默认属性(通过设置SpringApplication.setDefault属性指定)。

  2. [@PropertySource](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/propertysource.html)你的@Configuration类上的注释。请注意,在刷新应用程序上下文之前,不会将此类属性源添加到Environment中。现在配置某些属性(如logging.*spring.main.*)已经太晚了,这些属性是在开始刷新之前读取的。

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

  4. aRandomValuePropertySource仅在random.*中具有属性。

  5. OS 环境变量。

  6. Java 系统属性(System.get属性())。

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

  8. ServletContextinit 参数。

  9. ServletConfiginit 参数。

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

  11. 命令行参数。

  12. properties测试中的属性。 可在[@SpringBootTest](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/test/context/springbootttest.html)和测试用于测试应用程序的特定部分的注释上查阅。

  13. [@TestPropertySource](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/testpropertysource.html)测试上的注释。

  14. 当 devtools 处于活动状态时,DevTools 全局设置属性$HOME/.config/spring-boot目录中。

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

  1. 应用程序属性封装在你的 jar 中(application.properties和 YAML 变体)。

  2. 特定于配置文件的应用程序属性封装在你的 jar 中(application-{profile}.properties和 YAML 变体)。

  3. 应用程序属性在你打包的 jar 之外(application.properties和 YAML 变体)。

  4. 特定于配置文件的应用程序属性在你打包的 jar 之外(application-{profile}.properties和 YAML 变体)。

对于整个应用程序,建议使用一种格式。如果你的配置文件同时具有.properties.yml两种格式,那么.properties将优先使用。

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

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

@Component
public class MyBean {

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

    // ...

}

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

envconfigprops端点可以用于确定属性为什么具有特定值。
你可以使用这两个端点来诊断意外的属性值。
有关详细信息,请参见“生产就绪功能一节。

# 2.1.访问命令行属性

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

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

# 2.2.JSON 应用程序属性

环境变量和系统属性通常有一些限制,这意味着一些属性名称不能使用。为了帮助实现这一点, Spring Boot 允许你将一组属性编码到一个 JSON 结构中。

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

例如,SPRING_APPLICATION_JSON属性可以在 un*x shell 的命令行中作为环境变量提供:

$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar

在前面的示例中,在 Spring Environment中得到my.name=test

同样的 JSON 也可以作为系统属性提供:

$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar

或者,你可以通过使用一个命令行参数来提供 JSON:

$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'

如果要部署到经典的应用程序服务器,还可以使用名为java:comp/env/spring.application.json的 JNDI 变量。

虽然来自 JSON 的null值将被添加到结果属性源中,但PropertySourcesPropertyResolvernull属性视为缺失值。
这意味着 JSON 不能使用null值覆盖来自较低顺序属性源的属性。

# 2.3.外部应用程序属性

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

  1. 来自 Classpath

    1. Classpath 根

    2. Classpath /config

  2. 从当前目录

    1. 当前目录

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

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

该列表是按优先级排序的(来自较低项的值覆盖了较早的项)。来自加载的文件的文档以PropertySources的形式添加到 Spring Environment中。

如果不喜欢将application作为配置文件名,则可以通过指定spring.config.name环境属性切换到另一个文件名。例如,要查找myproject.propertiesmyproject.yaml文件,你可以按以下方式运行应用程序:

$ java -jar myproject.jar --spring.config.name=myproject

你还可以使用spring.config.locationEnvironment 属性引用显式位置。此属性接受要检查的一个或多个位置的逗号分隔列表。

下面的示例展示了如何指定两个不同的文件:

$ java -jar myproject.jar --spring.config.location=\
    optional:classpath:/default.properties,\
    optional:classpath:/override.properties
如果位置是可选的,则使用前缀optional:,如果它们不存在,则不介意。
spring.config.namespring.config.locationspring.config.additional-location很早就被用于确定必须加载哪些文件。
它们必须被定义为一个环境属性(通常是一个 OS 环境变量、一个系统属性或一个命令行参数)。

如果spring.config.location包含目录(与文件相反),则它们应该以/结尾。在运行时,在加载之前,将使用从spring.config.name生成的名称对它们进行追加。在spring.config.location中指定的文件是直接导入的。

还扩展了目录和文件位置值以检查特定于配置文件的文件
例如,如果你有spring.config.locationclasspath:myconfig.properties,你还会发现适当的classpath:myconfig-<profile>.properties文件已加载。

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

如果你有一个复杂的位置设置,并且你使用了特定于配置文件的配置文件,那么你可能需要提供进一步的提示,以便 Spring 引导知道应该如何对它们进行分组。位置组是所有位置都在同一级别上被考虑的位置的集合。例如,你可能希望对所有 Classpath 位置进行分组,然后对所有外部位置进行分组。位置组中的项应该用;分隔。有关更多详细信息,请参见“配置文件特定的文件”小节中的示例。

使用spring.config.location配置的位置替换默认位置。例如,如果spring.config.location被配置为值optional:classpath:/custom-config/,optional:file:./custom-config/,则考虑的完整位置集为:

  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的 basename)中为你的应用程序提供默认值。然后可以在运行时使用位于自定义位置之一中的不同文件重写这些默认值。

如果使用环境变量而不是系统属性,大多数操作系统都不允许使用周期分隔的键名,但可以使用下划线代替(例如,SPRING_CONFIG_NAME而不是spring.config.name)。
有关详细信息,请参见来自环境变量的绑定
如果你的应用程序运行在 Servlet 容器或应用程序服务器中,那么可以使用 JNDI 属性(在java:comp/env中)或 Servlet 上下文初始化参数来代替环境变量或系统属性。

# 2.3.1.可选位置

默认情况下,当指定的配置数据位置不存在时, Spring 启动将抛出ConfigDataLocationNotFoundException,并且你的应用程序将不会启动。

如果你想指定一个位置,但并不介意它并不总是存在,那么可以使用optional:前缀。你可以在spring.config.locationspring.config.additional-location属性以及[spring.config.import](#features.external-config.files.importing)声明中使用此前缀。

例如,spring.config.importoptional:file:./myconfig.properties允许你的应用程序启动,即使缺少myconfig.properties文件。

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

# 2.3.2.通配符位置

如果配置文件位置包含最后一个路径段的*字符,则将其视为通配符位置。在加载配置时,通配符将被展开,以便也检查直接的子目录。当存在多个配置属性源时,通配符位置在 Kubernetes 等环境中特别有用。

例如,如果你有一些 Redis 配置和一些 MySQL 配置,那么你可能希望将这两个配置分开,同时要求这两个配置都存在于application.properties文件中。这可能会导致两个单独的application.properties文件安装在不同的位置,例如/config/redis/application.properties/config/mysql/application.properties。在这种情况下,具有通配符位置config/*/,将导致这两个文件都被处理。

默认情况下, Spring 引导在默认搜索位置中包括config/*/。这意味着将搜索 jar 之外/config目录的所有子目录。

你可以使用spring.config.locationspring.config.additional-location属性自己使用通配符位置。

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

# 2.3.3.配置文件特定的文件

除了application属性文件外, Spring 引导还将尝试使用命名约定application-{profile}加载配置文件特定的文件。例如,如果应用程序激活名为prod的配置文件并使用 YAML 文件,那么application.ymlapplication-prod.yml都将被考虑。

配置文件特定的属性是从与标准application.properties相同的位置加载的,配置文件特定的文件总是覆盖非特定的文件。如果指定了多个配置文件,则应用“最后胜出”策略。例如,如果配置文件prod,livespring.profiles.active属性指定,则application-prod.properties中的值可以被application-live.properties中的值覆盖。

Last-wins 策略适用于位置组级别。
aspring.config.locationofclasspath:/cfg/,classpath:/ext/将不会具有与classpath:/cfg/;classpath:/ext/相同的覆盖规则。

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

<br/>/cfg<br/> application-live.properties<br/>/ext<br/> application-live.properties<br/> application-prod.properties<br/>

当我们有spring.config.locationofclasspath:/cfg/,classpath:/ext/时,我们在所有/ext文件之前处理所有
文件:
1./cfg/application-live.properties

2。/ext/application-prod.properties

3。/ext/application-live.properties

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

1。/ext/application-prod.properties

2。/cfg/application-live.properties

3。/ext/application-live.properties

Environment具有一组默认配置文件(默认情况下,[default]),如果未设置活动配置文件,则使用这些配置文件。换句话说,如果没有配置文件被显式激活,那么application-default中的属性将被考虑。

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

# 2.3.4.导入附加数据

应用程序属性可以使用spring.config.import属性从其他位置导入更多配置数据。导入在被发现时进行处理,并被视为在声明导入的文档下面插入的附加文档。

例如,你的 Classpath application.properties文件中可能包含以下内容:

属性

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

Yaml

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

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

一次进口,无论申报了多少次,都只能进口一次。在 属性/YAML 文件中的单个文档中定义的导入顺序并不重要。例如,下面的两个示例产生相同的结果:

属性

spring.config.import=my.properties
my.property=value

Yaml

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

属性

my.property=value
spring.config.import=my.properties

Yaml

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

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

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

在适当的情况下,特定于配置文件的变体也被考虑用于导入。
上面的示例将导入my.properties以及任何my-<profile>.properties变体。
Spring 引导包括可插入的 API,允许支持各种不同的位置地址。
默认情况下可以导入 Java 属性、YAML 和“配置树”。

第三方 JAR 可以提供对额外技术的支持(不要求文件是本地的)。
例如,你可以想象配置数据来自外部商店,例如 Consul、Apache ZooKeeper 或 Netflix Archaius。

如果你想支持自己的位置,请参阅ConfigDataLocationResolverConfigDataLoader包中的类。

# 2.3.5.导入无扩展文件

一些云平台不能向卷安装的文件添加文件扩展名。要导入这些无扩展文件,你需要给 Spring 引导一个提示,以便它知道如何加载它们。你可以通过在方括号中添加扩展提示来实现此目的。

例如,假设你有一个/etc/config/myconfig文件,你希望将其导入为 YAML。你可以使用以下方法从你的application.properties导入它:

属性

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

Yaml

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

# 2.3.6.使用配置树

在云平台(如 Kubernetes)上运行应用程序时,通常需要读取平台提供的配置值。为此目的使用环境变量并不少见,但这可能有缺点,特别是如果值应该保密的话。

作为环境变量的一种替代方法,许多云平台现在允许你将配置映射到已安装的数据卷中。例如,Kubernetes 可以同时挂载[ConfigMaps](https:/kubernetes.io/DOCS/tasks/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap)和[Secrets(https://kubernetes.io/DOCS/concepts/configuration/sects/secret/#using-secrets-as-files-from-a-pod)。

可以使用两种常见的卷安装模式:

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

  2. 多个文件被写入目录树,文件名成为“键”,内容成为“值”。

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

举个例子,让我们假设 Kubernetes 已经安装了以下卷:

etc/
  config/
    myapp/
      username
      password

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

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

属性

spring.config.import=optional:configtree:/etc/config/

Yaml

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

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

配置树下的文件夹形成属性名。
在上面的示例中,要访问属性为usernamepassword,你可以将spring.config.import设置为optional:configtree:/etc/config/myapp
例如,在上面的示例中,一个名为myapp.username的文件在/etc/config中将在Environment中产生一个myapp.username属性。
根据预期的内容,配置树值可以绑定到字符串Stringbyte[]两种类型。

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

例如,给出以下卷:

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

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

属性

spring.config.import=optional:configtree:/etc/config/*/

Yaml

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

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

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

配置树也可以用于 Docker 秘密。当 Docker Swarm 服务被授予对秘密的访问权限时,该秘密将被装载到容器中。例如,如果一个名为db.password的秘密被安装在位置/run/secrets/上,则可以使用以下方法使db.password环境对 Spring 环境可用:

属性

spring.config.import=optional:configtree:/run/secrets/

Yaml

spring:
  config:
    import: "optional:configtree:/run/secrets/"

# 2.3.7.财产占位符

application.propertiesapplication.yml中的值在使用时会通过现有的Environment进行过滤,因此你可以引用以前定义的值(例如,从系统属性)。标准的${name}属性占位符语法可以在值的任何地方使用。

例如,下面的文件将app.description设置为“MyApp 是一个 Spring 启动应用程序”:

属性

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

Yaml

app:
  name: "MyApp"
  description: "${app.name} is a Spring Boot application"
你也可以使用此技术来创建现有 Spring 引导属性的“短”变体。
有关详细信息,请参见 *howto.html*how-to。

# 2.3.8.使用多文档文件

Spring 启动允许将单个物理文件拆分成多个独立添加的逻辑文档。文件的处理顺序是从上到下的。后面的文档可以覆盖前面的文档中定义的属性。

对于application.yml文件,使用标准的 YAML 多文档语法。三个连续的连字符表示一个文档的结束,和下一个文档的开始。

例如,下面的文件有两个逻辑文档:

spring:
  application:
    name: "MyApp"
---
spring:
  application:
    name: "MyCloudApp"
  config:
    activate:
      on-cloud-platform: "kubernetes"

对于application.properties文件,使用特殊的#---注释来标记文档分割:

spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
属性文件分隔符不能有任何前置空格,并且必须正好有三个连字符。
分隔符前后的行不得是注释。
多文档属性文件通常与激活属性结合使用,例如spring.config.activate.on-profile
有关详细信息,请参见下一节
使用@PropertySource@TestPropertySource注释无法加载多文档属性文件。

# 2.3.9.活化特性

有时,只有在满足某些条件时才激活给定的一组属性是有用的。例如,你可能拥有仅在特定配置文件处于活动状态时才相关的属性。

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

以下激活属性可用:

Property
on-profile 必须匹配才能使文档处于活动状态的配置文件表达式。
on-cloud-platform 要使文档处于活动状态,必须检测到的CloudPlatform

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

Properties

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

Yaml

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

# 2.4.加密属性

Spring 引导不提供任何内置的对加密属性值的支持,但是,它确实提供了修改包含在 Spring Environment中的值所必需的钩点。EnvironmentPostProcessor接口允许你在应用程序启动之前操作Environment。详见howto.html

如果你需要一种安全的方式来存储凭据和密码,Spring Cloud Vault (opens new window)项目提供了在HashiCorp 保险库 (opens new window)中存储外部化配置的支持。

# 2.5.与 YAML 合作

YAML (opens new window)是 JSON 的超集,因此,是用于指定分层配置数据的一种方便的格式。只要在 Classpath 上有SnakeYAML (opens new window)库,SpringApplication类就会自动支持 YAML 作为属性的替代。

如果使用“starter”,那么 SnakeYAML 将由spring-boot-starter自动提供。

# 2.5.1.将 YAML 映射到属性

YAML 文档需要从其层次结构格式转换为可与 Spring Environment一起使用的平面结构。例如,考虑以下 YAML 文档:

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

为了从Environment中访问这些属性,它们将按以下方式进行平坦化:

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

同样,YAML 的列表也需要扁平化。它们被表示为带有[index]dereferencers 的属性键。例如,考虑以下 YAML:

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

前面的示例将转换为以下属性:

my.servers[0]=dev.example.com
my.servers[1]=another.example.com
使用[index]表示法的属性可以使用 Spring boot 的类型安全配置属性对象绑定到 JavaSet对象。Binderclass.
有关更多详细信息,请参见下面的“类型安全配置属性”一节。
无法通过使用@PropertySource@TestPropertySource注释来加载 YAML 文件。
因此,在需要以这种方式加载值的情况下,需要使用属性文件。

# 2.5.2.直接加载 YAML

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

如果希望以 Spring PropertySource的形式加载 YAML,也可以使用YamlPropertySourceLoader类。

# 2.6.配置随机值

RandomValuePropertySource对于注入随机值(例如,注入到秘密或测试用例中)很有用。它可以产生整数、长、UUID 或字符串,如以下示例所示:

Properties

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

Yaml

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

random.int*语法是OPEN value (,max) CLOSE,其中OPEN,CLOSE是任意字符,value,max是整数。如果提供了max,则value是最小值,max是最大值(不包含)。

# 2.7.配置系统环境属性

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

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

# 2.8.类型安全配置属性

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

另请参见[@Value和类型安全配置属性之间的区别]。

# 2.8.1.JavaBean 属性绑定

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

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

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

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

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

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

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

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

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

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

    public static class Security {

        private String username;

        private String password;

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

        // getters / setters...

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

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

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

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

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

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

    }

}

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

  • my.service.enabled,默认值为false

  • my.service.remote-address,其类型可以从String强制执行。

  • my.service.security.username,带有嵌套的“security”对象,其名称由属性的名称确定。特别是,该类型在那里根本不使用,并且可能是SecurityProperties

  • my.service.security.password.

  • my.service.security.roles,其集合String默认为USER

映射到 Spring 引导中可用的@ConfigurationProperties类的属性(通过属性文件、YAML 文件、环境变量和其他机制进行配置)是公共 API,但是类本身的访问器(getters/setters)并不是直接使用的。
这种安排依赖于缺省的空构造函数,getter 和 setter 通常是强制性的,因为绑定是通过标准的 Java bean 属性描述符进行的,就像在 Spring MVC 中一样。
在以下情况下,可以省略 setter:

* 映射,只要它们被初始化,需要一个 getter,但不一定是 setter,因为它们可以通过绑定程序进行变异,

* 集合和数组可以通过索引(通常使用 YAML)或使用一个逗号分隔的值(属性)进行访问,
在后一种情况下,setter 是强制性的。
我们建议总是为这样的类型添加一个 setter。
如果你初始化一个集合,请确保它不是不可变的(如前面的示例),

* 如果初始化了嵌套的 POJO 属性(如前面示例中的Security字段),setter 不是必需的。
如果你想让 binder 使用其默认构造函数动态地创建实例,你需要一个 setter。

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

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

# 2.8.2.构造函数绑定

上一节中的示例可以以一种不可更改的方式重写,如下例所示:

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

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

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

    // fields...

    private final boolean enabled;

    private final InetAddress remoteAddress;

    private final Security security;

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

    // getters...

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

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

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

    public static class Security {

        // fields...

        private final String username;

        private final String password;

        private final List<String> roles;

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

        // getters...

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

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

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

    }

}

在此设置中,@ConstructorBinding注释用于指示应该使用构造函数绑定。这意味着绑定器将期望找到一个具有你希望绑定的参数的构造函数。如果你使用的是 Java16 或更高版本,那么构造函数绑定可以用于记录。在这种情况下,除非你的记录有多个构造函数,否则不需要使用@ConstructorBinding

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

可以使用@DefaultValue指定默认值,并且将应用相同的转换服务来强制将String值强制到丢失属性的目标类型。默认情况下,如果没有属性绑定到Security,则MyProperties实例将包含nullsecurity。如果你希望返回Security的非空实例,即使没有属性绑定到它,也可以使用空的@DefaultValue注释来这样做:

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

要使用构造函数绑定,必须使用@EnableConfigurationProperties或配置属性扫描来启用类。
你不能使用构造函数绑定由常规 Spring 机制创建的 bean(例如@Componentbean,使用@Bean方法创建的 bean 或使用@Import加载的 bean)
如果你的类有多个构造函数,那么你也可以在应该绑定的构造函数上直接使用@ConstructorBinding
不推荐使用java.util.Optional@ConfigurationProperties,因为它主要是作为返回类型使用的。
因此,它不适合配置属性注入。
用于与其他类型的属性保持一致,如果你确实声明了一个Optional属性并且它没有值,则将绑定null而不是空的Optional

# 2.8.3.启用 @configrationProperties-注释类型

Spring Boot 提供了用于绑定@ConfigurationProperties类型并将它们注册为 bean 的基础设施。你可以逐个类地启用配置属性,也可以启用与组件扫描工作方式类似的配置属性扫描。

有时,用@ConfigurationProperties注释的类可能不适合扫描,例如,如果你正在开发自己的自动配置,或者希望有条件地启用它们。在这些情况下,使用@EnableConfigurationProperties注释指定要处理的类型列表。这可以在任何@Configuration类上完成,如以下示例所示:

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

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

}

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

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

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

}

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

上面示例中的 Bean 名称是com.example.app-com.example.app.SomeProperties

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

# 2.8.4.使用 @configrationProperties 注释类型

这种配置风格在SpringApplication外部 YAML 配置中尤其适用,如以下示例所示:

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

要使用@ConfigurationPropertiesbean,你可以以与任何其他 Bean 相同的方式注入它们,如以下示例所示:

import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final SomeProperties properties;

    public MyService(SomeProperties properties) {
        this.properties = properties;
    }

    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        server.start();
        // ...
    }

    // ...

}

使用@ConfigurationProperties还可以生成元数据文件,IDE 可以使用这些文件为你自己的密钥提供自动补全功能。
有关详细信息,请参见appendix

# 2.8.5.第三方配置

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

要从Environment属性配置 Bean,请将@ConfigurationProperties添加到其 Bean 注册中,如以下示例所示:

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

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

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

}

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

# 2.8.6.松弛结合

Spring Boot 使用一些宽松的规则将Environment属性绑定到@ConfigurationPropertiesbean,因此不需要在Environment属性名称和 Bean 属性名称之间进行精确匹配。这很有用的常见示例包括 dash 分隔的环境属性(例如,context-path绑定到contextPath)和大写的环境属性(例如,PORT绑定到port)。

例如,考虑以下@ConfigurationProperties类:

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

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

    private String firstName;

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

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

}

使用前面的代码,可以使用以下属性名称:

Property
my.main-project.person.first-name 烤肉串的情况下,这是建议在.properties.yml文件中使用。
my.main-project.person.firstName 标准的驼峰大小写语法。
my.main-project.person.first_name 下划线符号,这是在.properties.yml文件中使用的一种替代格式。
MY_MAINPROJECT_PERSON_FIRSTNAME 大写格式,这是建议时,使用系统环境变量.
注释prefix的值必须以烤肉串为例(小写并用-分隔,如my.main-project.person)。
Property Source 简单 List
Properties Files camel case、kebab case 或下划线符号 Standard list syntax using [ ] or comma-separated values
YAML Files camel case、kebab case 或下划线符号 Standard YAML list syntax or comma-separated values
Environment Variables 大写格式,下划线作为分隔符(参见来自环境变量的绑定)。 Numeric values surrounded by underscores (see 来自环境变量的绑定)
System properties camel case、kebab case 或下划线符号 Standard list syntax using [ ] or comma-separated values
我们建议在可能的情况下,将属性存储在小写字母的烤肉串格式中,例如my.person.first-name=Rod
# 绑定地图

当绑定到Map属性时,你可能需要使用特殊的括号表示法,以便保留原来的key值。如果键不被[]包围,则删除所有不是 alpha-numeric,-.的字符。

例如,考虑将以下属性绑定到Map<String,String>:

Properties

my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3

Yaml

my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"
对于 YAML 文件,括号中需要用引号包围,以便正确解析这些键。

上面的属性将绑定到Map,其中/key1/key2key3作为映射中的键。斜杠已从key3中删除,因为它没有被方括号包围。

如果你的key包含.并且绑定到非标量值,那么你有时也可能需要使用括号表示法。例如,将a.b=c绑定到Map<String, Object>将返回一个带有条目{"a"={"b"="c"}}的映射,而[a.b]=c将返回一个带有条目{"a.b"="c"}的映射。

# Binding from Environment Variables

大多数操作系统都对可用于环境变量的名称施加了严格的规则。例如,Linux Shell 变量只能包含字母(azAZ)、数字(09)或下划线字符(_)。按照惯例,UNIX shell 变量的名称也将是大写的。

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

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

  • 将点(.)替换为下划线(_)。

  • 删除任何破折号(-)。

  • 转换为大写。

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

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

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

# 2.8.7.合并复杂类型

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

例如,假设一个MyPojo对象,其namedescription属性默认为null。下面的示例公开了来自MyPropertiesMyPojo对象的列表:

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

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

@ConfigurationProperties("my")
public class MyProperties {

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

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

}

考虑以下配置:

Properties

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

Yaml

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

如果dev配置文件不是活动的,则MyProperties.list包含一个MyPojo条目,如前面定义的那样。但是,如果启用了dev配置文件,则listStill只包含一个条目(名称为my another name,描述为null)。此配置不是将第二个MyPojo实例添加到列表中,并且它不合并项。

当在多个配置文件中指定List时,将使用优先级最高的那个配置文件。考虑以下示例:

Properties

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

Yaml

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

在前面的示例中,如果dev配置文件是活动的,则MyProperties.list包含OneMyPojo条目(名称为my another name,描述为null)。对于 YAML,逗号分隔的列表和 YAML 列表都可以用于完全覆盖列表的内容。

对于Map属性,你可以绑定来自多个源的属性值。但是,对于多个源中的相同属性,使用具有最高优先级的属性。下面的示例公开了来自MyPropertiesMap<String, MyPojo>:

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

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

@ConfigurationProperties("my")
public class MyProperties {

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

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

}

考虑以下配置:

Properties

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

Yaml

my:
  map:
    key1:
      name: "my name 1"
      description: "my description 1"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  map:
    key1:
      name: "dev name 1"
    key2:
      name: "dev name 2"
      description: "dev description 2"

如果dev配置文件不是活动的,MyProperties.map包含一个键值key1的条目(名称为my name 1,描述为my description 1)。但是,如果启用了dev配置文件,则map包含两个带有键key1的条目(名称为dev name 1,描述为my description 1)和key2(名称为dev name 2,描述为dev description 2)。

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

# 2.8.8.属性转换

Spring 当绑定到@ConfigurationPropertiesbean 时,引导尝试强制将外部应用程序属性转换为正确的类型。如果需要自定义类型转换,可以提供ConversionService Bean(带有 Bean 名为conversionService)或自定义属性编辑器(通过CustomEditorConfigurer Bean)或自定义Converters(带有 Bean 注释为@ConfigurationPropertiesBinding的定义)。

由于在应用程序生命周期的很早阶段就请求了这个 Bean,因此请确保限制你的ConversionService正在使用的依赖关系。
通常,你所需要的任何依赖项可能在创建时没有完全初始化。
如果配置键强制不需要它,并且仅依赖于用@ConfigurationPropertiesBinding限定的自定义转换器,那么你可能想要重命名自定义ConversionService
# 转换持续时间

Spring Boot 具有用于表示持续时间的专用支持。如果你公开了java.time.Duration属性,那么应用程序属性中的以下格式是可用的:

  • 一个常规的long表示(使用毫秒作为默认单位,除非指定了@DurationUnit

  • 标准的 ISO-8601 格式[由java.time.Duration使用](https://DOCS.oracle.com/javase/8/DOCS/api/java/time/duration.html#parse-java.lang.charsequence-)

  • 一种更可读的格式,其中值和单位是耦合的(10s表示 10 秒)

考虑以下示例:

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

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

@ConfigurationProperties("my")
public class MyProperties {

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

    private Duration readTimeout = Duration.ofMillis(1000);

    // getters / setters...

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

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

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

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

}

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

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

  • ns为纳秒

  • us微秒

  • ms毫秒

  • s

  • m分钟

  • h小时

  • d

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

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

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

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

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

    // fields...

    private final Duration sessionTimeout;

    private final Duration readTimeout;

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

    // getters...

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

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

}

如果要升级Long属性,请确保在不是毫秒的情况下定义该单元(使用@DurationUnit)。
这样做可以提供透明的升级路径,同时支持更丰富的格式。
# 转换周期

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

  • 一个常规的int表示(使用天数作为默认单位,除非指定了@PeriodUnit

  • 标准的 ISO-8601 格式[由java.time.Period使用](https://DOCS.oracle.com/javase/8/DOCS/api/java/time/period.html#parse-java.lang.charsequence-)

  • 一种更简单的格式,其中值和单位对是耦合的(1y3d表示 1 年零 3 天)

以下单元以简单格式提供支持:

  • 多年来y

  • m持续数月

  • w

  • d

java.time.Period类型实际上不会存储周数,它是一种表示“7 天”的快捷方式。
# 转换数据大小

Spring 框架具有DataSize值类型,该类型表示以字节为单位的大小。如果你公开了DataSize属性,那么应用程序属性中的以下格式是可用的:

  • 一个常规的long表示(使用字节作为默认单位,除非指定了@DataSizeUnit

  • 一种更可读的格式,其中的值和单位是耦合的(10MB表示 10 兆)

考虑以下示例:

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

@ConfigurationProperties("my")
public class MyProperties {

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

    private DataSize sizeThreshold = DataSize.ofBytes(512);

    // getters/setters...

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

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

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

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

}

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

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

  • B表示字节

  • KB表示千字节

  • MB代表兆字节

  • GB=“936”/>

  • TB用于 TB

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

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

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

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

    // fields...

    private final DataSize bufferSize;

    private final DataSize sizeThreshold;

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

    // getters...

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

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

}

如果你正在升级Long属性,请确保定义不是字节的单元(使用@DataSizeUnit)。
这样做可以提供一个透明的升级路径,同时支持更丰富的格式。

# 2.8.9.@configrationProperties 验证

Spring 每当使用 Spring 的@Validated注释对类进行注释时,引导都会尝试验证@ConfigurationProperties类。你可以直接在你的配置类上使用 JSR-303javax.validation约束注释。要做到这一点,请确保在你的 Classpath 上有一个兼容的 JSR-303 实现,然后将约束注释添加到你的字段中,如以下示例所示:

import java.net.InetAddress;

import javax.validation.constraints.NotNull;

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

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

    @NotNull
    private InetAddress remoteAddress;

    // getters/setters...

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

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

}

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

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

import java.net.InetAddress;

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

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

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

    @NotNull
    private InetAddress remoteAddress;

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

    // getters/setters...

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

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

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

    public static class Security {

        @NotEmpty
        private String username;

        // getters/setters...

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

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

    }

}

还可以通过创建一个名为configurationPropertiesValidator的 Bean 定义来添加自定义 Spring Validator。应该声明@Bean方法static。配置属性验证器是在应用程序生命周期的早期创建的,将@Bean方法声明为 static 可以使 Bean 在无需实例化@Configuration类的情况下被创建。这样做可以避免早期实例化可能带来的任何问题。

spring-boot-actuator模块包括一个公开所有@ConfigurationPropertiesbean 的端点。
将你的 Web 浏览器指向/actuator/configprops或使用等效的 JMX 端点。
有关详细信息,请参见“应用程序属性文件一节。

# 2.8.10.@configrationProperties vs.@value

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

Feature @ConfigurationProperties @Value
Relaxed binding Yes Limited(见note below
Meta-data support Yes
SpEL evaluation No 是的
如果你确实想使用@Value,我们建议你使用它们的规范形式(kebab-case 只使用小写字母)来引用属性名称,
这将允许 Spring 引导使用与放松绑定@ConfigurationProperties时相同的逻辑,例如,
@Value("{demo.item-price}")将从application.properties文件中获取demo.item-pricedemo.itemPrice窗体,以及从系统环境中获取DEMO_ITEMPRICE
如果使用@Value("{demo.itemPrice}")代替,则不会考虑demo.item-priceDEMO_ITEMPRICE

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

在解析这些文件和填充环境时,不会处理SpEL中的应用程序属性文件表达式。但是,可以在@Value中写入SpEL表达式。如果来自应用程序属性文件的属性的值是SpEL表达式,则在通过@Value使用时将对其进行求值。

# 3. 配置文件

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

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

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

    // ...

}

如果@Configuration属性bean 是通过@EnableConfiguration属性注册的,而不是通过自动扫描,则@Profile注释需要在具有@Configuration注释的类上指定。
@Configuration属性被扫描的情况下,@Profile可以在@Configuration属性类本身上指定。

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

属性

spring.profiles.active=dev,hsqldb

Yaml

spring:
  profiles:
    active: "dev,hsqldb"

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

如果没有配置文件处于活动状态,则启用默认配置文件。默认配置文件的名称是default,可以使用spring.profiles.default``Environment属性对其进行调优,如以下示例所示:

属性

spring.profiles.default=none

Yaml

spring:
  profiles:
    default: "none"

# 3.1.添加活动配置文件

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

有时,将添加的属性用于活动配置文件而不是替换它们是很有用的。SpringApplication入口点有一个用于设置附加配置文件的 Java API(即,在那些由spring.profiles.active属性激活的配置文件之上)。参见SpringApplication (opens new window)中的setAdditionalProfiles()方法。在下一节中描述的配置文件组也可以用于在给定的配置文件处于活动状态时添加活动配置文件。

# 3.2.配置文件组

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

为了帮助实现这一点, Spring Boot 允许你定义配置文件组。配置文件组允许你为相关的配置文件组定义逻辑名称。

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

属性

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq

Yaml

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

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

# 3.3.以编程方式设置配置文件

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

# 3.4.特定于配置文件的配置文件

application.properties(或application.yml)和通过@Configuration属性引用的文件的配置文件特定变体都被视为文件并加载。详见“配置文件特定的文件”。

# 4. 伐木

Spring 启动对所有内部日志使用共享日志记录 (opens new window),但对底层日志实现保持开放。为Java util 日志记录 (opens new window)Log4J2 (opens new window)@SpringBootApplication提供了默认配置。在每种情况下,记录器都被预先配置为使用控制台输出,可选的文件输出也可用。

默认情况下,如果你使用“starters”,则会使用回录来进行日志记录。还包括适当的回登路由,以确保使用 Java util 日志、Commons 日志、log4j 或 SLF4j 的依赖库都能正确工作。

有很多日志框架可用于 Java。
如果上面的列表看起来令人困惑,请不要担心。
通常,你不需要更改日志依赖项,并且 Spring 引导默认值工作得很好。
当你将应用程序部署到 Servlet 容器或应用程序服务器时,使用 Java util Logging API 执行的日志记录不会路由到应用程序的日志中。
这将防止由容器执行的日志或已部署到它的其他应用程序出现在应用程序的日志中。

# 4.1.日志格式

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

2019-03-05 10:57:51.112  INFO 45469 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.52
2019-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1358 ms
2019-03-05 10:57:51.698  INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2019-03-05 10:57:51.702  INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]

以下项目是输出:

  • 日期和时间:毫秒精度和容易排序.

  • 日志级别:ERRORWARNINFO下一节,或TRACE

  • 进程 ID。

  • 一个---分隔符,用于区分实际日志消息的开始。

  • 线程名称:括在方括号中(对于控制台输出,可能会被截断)。

  • 日志程序名称:这通常是源类名称(通常是缩写)。

  • 日志消息。

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

# 4.2.控制台输出

默认的日志配置会在消息写入时将消息回传到控制台。默认情况下,将记录ERROR-level、WARN-level 和INFO-level 消息。你还可以通过使用--debug标志启动应用程序来启用“调试”模式。

$ java -jar myapp.jar --debug
你还可以在application.properties中指定debug=true

当调试模式被启用时,核心记录器(嵌入式容器、 Hibernate 和 Spring 引导)的选择被配置为输出更多信息。启用调试模式将不是配置你的应用程序以DEBUG级别记录所有消息。

或者,你可以通过使用--trace标志(或trace=true中的application.properties)启动应用程序来启用“跟踪”模式。这样做可以实现对核心记录器(嵌入式容器、 Hibernate 模式生成和整个 Spring 投资组合)的选择的跟踪日志记录。

# 4.2.1.颜色编码输出

如果你的终端支持 ANSI,则使用颜色输出来提高可读性。可以将spring.output.ansi.enabled设置为支持的值 (opens new window)以覆盖自动检测。

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

%clr(%5p)

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

电平 Color
FATAL Red
ERROR Red
WARN Yellow
INFO Green
DEBUG Green
TRACE Green

或者,你可以通过将其作为转换的选项来指定应该使用的颜色或样式。例如,要使文本为黄色,请使用以下设置:

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

支持以下颜色和样式:

  • blue

  • cyan

  • faint

  • green

  • magenta

  • red

  • yellow

# 4.3.文件输出

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

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

logging.file.name logging.file.path Example 说明
(none) (none) 仅用于控制台日志记录。
Specific file (none) my.log 写入指定的日志文件。
名称可以是确切的位置或相对于当前目录的位置。
(none) Specific directory /var/log spring.log写入指定的目录。
名称可以是确切的位置或相对于当前目录的位置。

当日志文件达到 10MB 时会旋转,并且与控制台输出一样,ERROR-level,WARN-level 和INFO-level 消息在默认情况下会被记录。

日志记录属性独立于实际的日志基础设施。因此,特定的配置键(例如
用于回登)不受 Spring 引导的管理。

# 4.4.文件旋转

如果你使用的是回录,那么可以使用application.propertiesapplication.yaml文件来微调日志旋转设置。对于所有其他日志系统,你将需要直接自己配置旋转设置(例如,如果你使用 log4j2,那么你可以添加application.yamllog4j2-spring.xml文件)。

支持以下旋转策略属性:

Name 说明
logging.logback.rollingpolicy.file-name-pattern 用于创建日志归档的文件名模式。
logging.logback.rollingpolicy.clean-history-on-start 如果日志归档清理应该在应用程序启动时进行。
logging.logback.rollingpolicy.max-file-size 存档前日志文件的最大大小。
logging.logback.rollingpolicy.total-size-cap 在被删除之前,日志档案可以占用的最大大小。
logging.logback.rollingpolicy.max-history 保存的归档日志文件的最大数量(默认为 7)。

# 4.5.日志级别

通过使用logging.level.<logger-name>=<level>,所有受支持的日志系统都可以在 Spring Environment(例如,在application.properties)中设置日志程序级别,其中application.properties是跟踪、调试、信息、警告、错误、致命或关闭。root记录器可以通过使用logging.level.root进行配置。

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

属性

logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error

Yaml

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

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

上述方法仅对包级日志记录有效。
由于放松绑定总是将环境变量转换为小写,因此不可能以这种方式为单个类配置日志记录。
如果需要为一个类配置日志记录,可以使用[theSPRING_APPLICATION_JSON]变量。

# 4.6.日志组

能够将相关的记录器组合在一起,以便可以同时对它们进行配置,这通常是很有用的。例如,你通常可能会更改全部 Tomcat 相关日志记录器的日志记录级别,但是你不能很容易地记住顶级包。

为了解决这个问题, Spring Boot 允许你在 Spring Environment中定义日志记录组。例如,下面是如何将“ Tomcat”组添加到application.properties中来定义该组的方法:

属性

logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat

Yaml

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

一旦定义好,你就可以用一行来改变组中所有记录器的级别:

Properties

logging.level.tomcat=trace

Yaml

logging:
  level:
    tomcat: "trace"

Spring 启动包括以下预定义的记录组,这些记录组可以开箱即用:

Name 伐木工
web org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sql org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

# 4.7.使用日志关闭钩子

为了在应用程序终止时释放日志资源,提供了一个关闭钩子,该钩子将在 JVM 退出时触发日志系统清理。除非你的应用程序被部署为 WAR 文件,否则此关闭钩子将自动注册。如果你的应用程序具有复杂的上下文层次结构,那么关闭钩子可能无法满足你的需求。如果没有,请禁用关闭钩子,并调查底层日志系统直接提供的选项。例如,Logback 提供上下文选择器 (opens new window),允许在自己的上下文中创建每个记录器。你可以使用logging.register-shutdown-hook属性禁用关闭钩子。将其设置为false将禁用注册。你可以在falseapplication.yaml文件中设置该属性:

Properties

logging.register-shutdown-hook=false

Yaml

logging:
  register-shutdown-hook: false

# 4.8.自定义日志配置

各种日志记录系统可以通过在 Classpath 上包括适当的库来激活,并且可以通过在 Classpath 的根目录中或在由下面的 Spring 指定的位置中提供适当的配置文件来进一步定制属性:。

可以使用org.springframework.boot.logging.LoggingSystem系统属性强制 Spring 引导使用特定的日志系统。该值应该是LoggingSystem实现的完全限定类名称。还可以使用@EnableScheduling的值来完全禁用 Spring Boot 的日志配置。

由于日志是初始化的在此之前ApplicationContext已被创建,因此不可能在 Spring @PropertySources文件中从@Configuration文件中控制日志。在此之前改变日志系统或完全禁用它的唯一方法是通过系统属性。

根据你的日志记录系统,将加载以下文件:

Logging System 定制
Logback logback-spring.xmllogback-spring.groovylogback.xml,或logback.groovy
Log4j2 log4j2-spring.xmllog4j2.xml
JDK (Java Util Logging) logging.properties
如果可能,我们建议你在日志配置中使用-spring变量(例如,logback-spring.xml而不是logback.xml)。
如果使用标准配置位置, Spring 不能完全控制日志初始化。
在从“可执行文件 jar”运行时,Java util 日志记录中存在已知的类加载问题,这些问题会导致问题。
如果可能的话,我们建议你在从“可执行文件 jar”运行时避免此类问题。

为了帮助进行定制,将一些其他属性从 Spring Environment转移到系统属性,如下表所述:

Spring Environment System Property 评论
logging.exception-conversion-word LOG_EXCEPTION_CONVERSION_WORD 记录异常时使用的转换词.
logging.file.name LOG_FILE 如果定义了,它将在默认的日志配置中使用。
logging.file.path LOG_PATH 如果定义了,它将在默认的日志配置中使用。
logging.pattern.console CONSOLE_LOG_PATTERN 在控制台上使用的日志模式。
logging.pattern.dateformat LOG_DATEFORMAT_PATTERN 日志日期格式的附录模式。
logging.charset.console CONSOLE_LOG_CHARSET 用于控制台日志记录的字符集。
logging.pattern.file FILE_LOG_PATTERN 在文件中使用的日志模式(如果启用LOG_FILE)。
logging.charset.file FILE_LOG_CHARSET 用于文件日志记录的字符集(如果启用LOG_FILE)。
logging.pattern.level LOG_LEVEL_PATTERN 呈现日志级别时要使用的格式(默认%5p)。
PID PID 当前的进程 ID(如果可能的话,在尚未定义为 OS 环境变量时发现)。

如果使用注销,还会传输以下属性:

Spring Environment System Property 评论
logging.logback.rollingpolicy.file-name-pattern LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN 滚转日志文件名的模式(默认${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz)。
logging.logback.rollingpolicy.clean-history-on-start LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START 是否在启动时清除归档日志文件。
logging.logback.rollingpolicy.max-file-size LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE 最大日志文件大小。
logging.logback.rollingpolicy.total-size-cap LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP 要保留的日志备份的总大小。
logging.logback.rollingpolicy.max-history LOGBACK_ROLLINGPOLICY_MAX_HISTORY 要保存的归档日志文件的最大数量。

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

如果你想在日志属性中使用占位符,那么你应该使用Spring Boot’s syntax,而不是底层框架的语法。
值得注意的是,如果你使用 logback,你应该使用:作为属性名与其默认值之间的分隔符,而不是使用:-
你可以通过只重写LOG_LEVEL_PATTERN(或logging.pattern.level带回日志),将 MDC 和其他临时内容添加到日志行中,
例如,如果你使用logging.pattern.level=user:%X{user} %5p,那么默认的日志格式包含一个“user”的 MDC 条目,如果它存在的话,如下面的示例所示。

<br/>2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0] demo.Controller<br/>Handling authenticated request<br/>

# 4.9.回录扩展

Spring 启动包括许多对注销的扩展,这些扩展可以帮助进行高级配置。你可以在logback-spring.xml配置文件中使用这些扩展名。

因为标准的logback.xml配置文件加载得太早,所以不能在其中使用扩展名。
你需要使用logback-spring.xml或定义logging.config属性。
这些扩展名不能与 Logback 的配置扫描 (opens new window)一起使用。
如果你试图这样做,对配置文件进行更改将导致类似于记录以下错误之一的错误:
ERROR in [email protected]:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]]
ERROR in ch.qos.logback.core.joran.spi.Interpret[email protected]:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]

# 4.9.1.特定于配置文件的配置

<springProfile>标记允许你基于活动 Spring 配置文件可选地包括或排除配置的部分。在<configuration>元素中的任何地方都支持配置文件部分。使用name属性指定哪个配置文件接受配置。<springProfile>标记可以包含配置文件名(例如staging)或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑,例如production & (eu-central | eu-west)。查看参考指南 (opens new window)以获取更多详细信息。下面的清单显示了三个示例配置文件:

<springProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>

# 4.9.2.环境属性

<springProperty>标记允许你公开 Spring Environment中的属性,以便在回传过程中使用。如果你希望在你的回登配置中访问application.properties文件中的值,那么这样做会很有用。该标记的工作方式与 Logback 的标准<property>标记类似。但是,不是直接指定value,而是指定属性的source(来自Environment)。如果需要将属性存储在local范围以外的其他地方,则可以使用scope属性。如果你需要一个回退值(如果该属性未在Environment中设置),则可以使用defaultValue属性。下面的示例展示了如何公开属性以便在回登过程中使用:

<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
        defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
    <remoteHost>${fluentHost}</remoteHost>
    ...
</appender>
source必须在烤肉串的情况下指定(例如my.property-name)。
但是,可以通过使用放松的规则将属性添加到Environment中。

# 5. 国际化

Spring 启动支持本地化消息,以便你的应用程序能够迎合具有不同语言偏好的用户。默认情况下, Spring 引导在 Classpath 的根位置查找messages资源包的存在。

当配置的资源包的默认属性文件可用时(默认情况下messages.properties),自动配置将应用,
如果你的资源包只包含语言特定的属性文件,则需要添加默认的属性文件。
如果没有找到匹配任何配置的基名的属性文件,不会自动配置messages

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

Properties

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

Yaml

spring:
  messages:
    basename: "messages,config.i18n.messages"
    fallback-to-system-locale: false
spring.messages.basename支持以逗号分隔的位置列表,可以是包限定符,也可以是从 Classpath 根目录解析的资源。

参见[MessageSourceProperties](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/springframework/boot/autofigure/context/mentt/messagesourceproperties.java)以获得更多支持的选项。

# 6. JSON

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

  • 格森

  • Jackson

  • JSON-B

Jackson 是首选的默认库。

# 6.1.Jackson

提供了用于 Jackson 的自动配置,并且 Jackson 是messages的一部分。当 Jackson 在 Classpath 上时,ObjectMapper Bean 被自动配置。为[自定义ObjectMapper](howto.html#howto. Spring-mvc.customize-Jackson-objectmapper)的配置提供了几个配置属性。

# 6.2.格森

提供了 GSON 的自动配置。当 GSON 在 Classpath 上时,Gson Bean 被自动配置。为定制配置提供了几个spring.gson.*配置属性。要获得更多的控制,可以使用一个或多个GsonBuilderCustomizerbean。

# 6.3.JSON-B

提供了 JSON-B 的自动配置。当 JSON-B API 和实现都在 Classpath 上时,Jsonb Bean 将被自动配置。首选的 JSON-B 实现是 Apache Johnzon,它提供了依赖管理。

# 7. 任务执行和调度

在上下文中没有Executor Bean 的情况下, Spring 引导自动配置具有合理默认值的ThreadPoolTaskExecutor,该默认值可以自动关联到异步任务执行(@EnableAsync)和 Spring MVC 异步请求处理。

如果你在上下文中定义了自定义的@EnableAsync,则常规的任务执行(即@EnableAsync)将透明地使用它,但是 Spring MVC 支持将不会被配置,因为它需要AsyncTaskExecutor实现(名为applicationTaskExecutor)。
取决于你的目标安排,你可以将你的Executor更改为ThreadPoolTaskExecutor,或者同时定义ThreadPoolTaskExecutorAsyncConfigurer包装你的自定义Executor
自动配置的TaskExecutorBuilder允许你轻松地创建实例,这些实例复制默认情况下自动配置的功能。

线程池使用 8 个核心线程,这些线程可以根据负载的大小进行增长和收缩。可以使用Executor名称空间对这些默认设置进行微调,如下例所示:

Properties

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

Yaml

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

这将线程池更改为使用有界队列,以便当队列已满(100 个任务)时,线程池最多增加到 16 个线程。当线程空闲 10 秒(而不是默认的 60 秒)时,会回收线程,因此池的收缩会更剧烈。

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

Properties

spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2

Yaml

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

如果需要创建自定义执行器或调度程序,则在上下文中可以使用TaskExecutorBuilder Bean 和TaskSchedulerBuilder Bean。

# 8. 测试

Spring 引导提供了许多实用工具和注释,以在测试应用程序时提供帮助。测试支持由两个模块提供:spring-boot-test包含核心项,spring-boot-test-autoconfigure支持测试的自动配置。

大多数开发人员使用spring-boot-starter-test“starter”,它既导入 Spring 引导测试模块,也导入 JUnit Jupiter、AssertJ、Hamcrest 和许多其他有用的库。

如果你有使用 JUnit4 的测试,可以使用 JUnit5 的 Vintage 引擎来运行它们。@EnableScheduling要使用 Vintage 引擎,请在junit-vintage-engine上添加一个依赖项,如以下示例所示:

spring-boot-starter-test被排除在外,而org.hamcrest:hamcrestspring-boot-starter-test的一部分。

# 8.1.测试范围依赖项

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

在编写测试时,我们通常会发现这些公共库非常有用。如果这些库不适合你的需要,那么你可以添加自己的额外测试依赖项。

# 8.2.测试 Spring 应用程序

依赖注入的一个主要优点是,它应该使代码更容易进行单元测试。你可以使用new运算符实例化对象,甚至不需要涉及 Spring。你也可以使用Java util 日志记录 (opens new window)来代替真正的依赖关系。

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

Spring 框架包括用于这种集成测试的专用测试模块。你可以直接将依赖项声明为org.springframework:spring-test,或者使用spring-boot-starter-test“starter”来传递地将其拉入。

如果你以前没有使用spring-test模块,那么你应该从阅读 Spring 框架参考文档的相关部分 (opens new window)开始。

# 8.3.测试 Spring 引导应用程序

Spring 引导应用程序是 Spring ApplicationContext,因此,除了通常使用普通上下文 Spring 所做的事情外,无需做任何非常特殊的事情来测试它。

只有在使用SpringApplication创建它的情况下, Spring 引导的外部属性、日志记录和其他功能才默认安装在上下文中。

Spring Boot 提供了一个@SpringBootTest注释,其可以作为标准spring-test``@ContextConfiguration注释的替代项,当需要 Spring 启动功能时。该注释的工作原理是[通过SpringApplication创建测试中使用的ApplicationContext](#features.testing. Spring-boot-applications.detecting-configuration)。除了@SpringBootTest之外,还为测试更具体的切片的应用程序提供了许多其他注释。

如果你正在使用 JUnit4,请不要忘记在你的测试中也添加@RunWith(SpringRunner.class),否则注释将被忽略。
如果你正在使用 JUnit5,没有必要将等价的@ExtendWith(SpringExtension.class)添加为@SpringBootTest,并且其他@…​Test注释已经用它注释了。

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

  • MOCK(默认):加载 WebApplicationContext并提供模拟 Web 环境。使用此注释时,嵌入式服务器不会启动。如果你的 Classpath 上没有可用的 Web 环境,则该模式可透明地返回到创建常规的非 WebApplicationContext。它可以与[@AutoConfigureMockMvc@AutoConfigureWebTestClient](#features.testing. Spring-boot-applications.with-mock-environment)结合使用,用于对 Web 应用程序进行基于模拟的测试。

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

  • DEFINED_PORT:加载WebServerApplicationContext,并提供一个真正的 Web 环境。嵌入式服务器在定义的端口(从application.properties)或默认端口8080上启动和侦听。

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

如果你的测试是@Transactional,那么默认情况下,它会在每个测试方法的末尾回滚事务。
但是,由于使用这种安排,RANDOM_PORTRANDOM_PORT隐式地提供了一个真正的 Servlet 环境,HTTP 客户机和服务器在单独的线程中运行,因此,在单独的事务中。
在这种情况下,在服务器上发起的任何事务都不回滚。
如果应用程序为管理服务器使用不同的端口,则@SpringBootTestwithwebEnvironment = WebEnvironment.RANDOM_PORT也将在单独的随机端口上启动管理服务器。

# 8.3.1.检测 Web 应用程序类型

Spring 如果 MVC 是可用的,则配置一个常规的基于 MVC 的应用程序上下文。如果你只有 Spring 个 WebFlux,我们将检测到它并配置一个基于 WebFlux 的应用程序上下文。

如果两者都存在, Spring MVC 优先。如果要在此场景中测试一个反应式 Web 应用程序,则必须设置spring.main.web-application-type属性:

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

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

    // ...

}

# 8.3.2.检测测试配置

如果你熟悉 Spring 测试框架,那么你可能习惯于使用@ContextConfiguration(classes=…​)来指定要加载哪个 Spring @Configuration。或者,你可能经常在测试中使用嵌套的@Configuration类。

在测试 Spring 引导应用程序时,这通常不是必需的。 Spring 只要不显式地定义一个配置,Boot 的@*Test注释就会自动搜索你的主要配置。

搜索算法从包含测试的包开始工作,直到找到一个用@SpringBootApplication@SpringBootConfiguration注释的类。只要以一种合理的方式
,通常就可以找到你的主配置。

如果使用测试注释以测试应用程序的一个更具体的部分,你应该避免在主方法的应用程序类上添加特定于特定区域的配置设置。

@SpringBootApplication的底层组件扫描配置定义了排除过滤器如果你在你的@SpringBootApplication-注释类上使用显式的@ComponentScan指令,请注意这些过滤器将被禁用。
如果你正在使用切片,则应再次定义它们。

如果你想定制主配置,可以使用嵌套的@TestConfiguration类。不同于嵌套的@Configuration类,嵌套的@TestConfiguration类将被用来代替应用程序的主配置,在应用程序的主配置之外还使用了嵌套的@TestConfiguration类。

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

# 8.3.3.不包括测试配置

如果你的应用程序使用组件扫描(例如,如果你使用@SpringBootApplication@ComponentScan),你可能会发现只为特定测试创建的顶级配置类在任何地方都会意外地被选中。

正如我们看过更早的@TestConfiguration可以用于对内部类的测试进行自定义的主要配置。当放置在顶级类上时,@TestConfiguration表示不应通过扫描来读取src/test/java中的类。然后,你可以在需要的地方显式地导入该类,如下面的示例所示:

import org.junit.jupiter.api.Test;

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

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

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

}

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

# 8.3.4.使用应用程序参数

如果应用程序期望@SpringBootTest,则可以使用args属性注入它们。

import org.junit.jupiter.api.Test;

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

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

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

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

}

# 8.3.5.在模拟环境中进行测试

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

使用 Spring MVC,我们可以使用[MockMvc](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/testing.html# Spring-mvc-test-framework)或WebTestClient查询我们的 Web 端点,如以下示例所示:

import org.junit.jupiter.api.Test;

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

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

@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {

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

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

}

如果你希望只关注 Web 层,而不是启动一个完整的ApplicationContext,请考虑[使用@WebMvcTest代替](#features.testing. Spring-boot-applications. Spring-mvc-tests)。

使用 Spring WebFlux 端点,可以使用[WebTestClient](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/testing.html#WebTestClient-tests),如以下示例所示:

import org.junit.jupiter.api.Test;

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

@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {

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

}

在模拟环境中进行测试通常比使用完整的 Servlet 容器运行更快。但是,由于模拟发生在 Spring MVC 层,因此依赖较低级别 Servlet 容器行为的代码不能直接使用 MockMVC 进行测试。例如,, Spring boot 的错误处理是基于 Servlet 容器提供的“错误页”支持。
这意味着,虽然你可以测试你的 MVC 层抛出和处理预期的异常,你无法直接测试呈现了特定的自定义错误页面
如果你需要测试这些较低级别的问题,你可以启动一个完全运行的服务器,如下一节所述。

# 8.3.6.使用正在运行的服务器进行测试

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

@LocalServerPort注释可以用于注入实际使用的端口到你的测试中。为了方便起见,需要对启动的服务器进行 REST 调用的测试还可以另外@Autowirea[WebTestClient](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/testing.html#webtestclient-tests),它解析与运行中的服务器的相关链接,并附带用于验证响应的专用 API,如以下示例所示:

import org.junit.jupiter.api.Test;

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

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

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

}

WebTestClient可以同时用于实时服务器和模拟环境

此设置需要在 Classpath 上执行spring-webflux。如果不能或不会添加 WebFlux, Spring Boot 还提供了TestRestTemplate功能:

import org.junit.jupiter.api.Test;

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

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

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

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

}

# 8.3.7.自定义 WebTestClient

要定制WebTestClient Bean,请配置WebTestClientBuilderCustomizer Bean。使用WebTestClient.Builder调用任何这样的 bean,该 bean 用于创建WebTestClient

# 8.3.8.使用 JMX

当测试上下文框架缓存上下文时,默认情况下禁用 JMX,以防止相同的组件在同一域上注册。如果这样的测试需要访问MBeanServer,也可以考虑将其标记为 dirty:

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

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

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

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

    @Autowired
    private MBeanServer mBeanServer;

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

}

# 8.3.9.使用度量

无论你的 Classpath 是什么,除了内存备份之外,仪表注册中心在使用@SpringBootTest时不会自动配置。

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

# 8.3.10.嘲笑和窥探豆子

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

Spring 引导包括一个@MockBean注释,该注释可用于在你的ApplicationContext中为 Bean 定义一个 mockito 模拟。你可以使用该注释来添加新的 bean 或替换单个现有的 Bean 定义。注释可以直接用于测试类、测试中的字段或@Configuration类和字段。当在字段上使用时,创建的模拟实例也会被注入。在每种测试方法之后,mock bean 都会自动重置。

如果你的测试使用 Spring boot 的测试注释之一(例如@SpringBootTest),则自动启用此功能。
要以不同的方式使用此功能,必须显式地添加监听器,如以下示例所示:

<br/>import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;<br/>import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener;<br/>import org.springframework.test.context.ContextConfiguration;<br/>import org.springframework.test.context.TestExecutionListeners;<br/><br/>@ContextConfiguration(classes = MyConfig.class)<br/>@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })<br/>class MyTests {<br/><br/> // ...<br/><br/>}<br/><br/>

下面的示例用模拟实现替换现有的RemoteService Bean:

import org.junit.jupiter.api.Test;

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

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

@SpringBootTest
class MyTests {

    @Autowired
    private Reverser reverser;

    @MockBean
    private RemoteService remoteService;

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

}

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

此外,你可以使用@SpyBean用 mockitospy包装任何现有的 Bean。有关详细信息,请参见Javadoc (opens new window)

CGLIB 代理,例如为作用域 bean 创建的代理,将 proxied 方法声明为final
这将阻止 Mockito 正常运行,因为它无法在其默认配置中模拟或监视final方法。,
如果你想模拟或监视这样的 Bean,通过将org.mockito:mockito-inline添加到应用程序的测试依赖项,将 Mockito 配置为使用其内联 mock maker。
这允许 Mockito 模拟和监视final方法。
Spring 的测试框架在测试之间缓存应用程序上下文,并为共享相同配置的测试重用上下文,而@MockBean@SpyBean的使用会影响缓存键,这很可能会增加上下文的数量。
如果你使用@SpyBean监视带有@Cacheable方法的 Bean,该方法通过名称引用参数,则你的应用程序必须使用-parameters进行编译。
这将确保在监视了 Bean 之后,参数名称对缓存基础设施是可用的。
当你使用@SpyBean监视由 Spring 代理的 Bean 时,在某些情况下可能需要删除 Spring 的代理,例如在使用givenwhen设置期望时。
使用AopTestUtils.getTargetObject(yourProxiedSpy)来这样做。

# 8.3.11.自动配置的测试

Spring Boot 的自动配置系统在应用程序中运行良好,但有时在测试中可能会有点太多。通常情况下,只加载测试应用程序“部分”所需的配置部分是有帮助的。例如,你可能希望测试 Spring MVC 控制器是否正确映射 URL,并且你不希望在这些测试中涉及数据库调用,或者你可能希望测试 JPA 实体,并且你对这些测试运行时的 Web 层不感兴趣。

spring-boot-test-autoconfigure模块包括许多注释,可用于自动配置此类“切片”。它们都以类似的方式工作,提供一个@…​Test注释,用于加载ApplicationContext和一个或多个@AutoConfigure…​注释,这些注释可用于自定义自动配置设置。

每个切片将组件扫描限制为适当的组件,并加载一组非常受限制的自动配置类。
如果需要排除其中之一,大多数@…​Test注释都提供了excludeAutoConfiguration属性。
或者,你可以使用@ImportAutoConfiguration#exclude
不支持在一个测试中使用多个@…​Test注释来包含多个“切片”。
如果需要多个“切片”,请选择其中一个@…​Test注释,并手动包含其他“切片”的@AutoConfigure…​注释。
也可以使用带有标准@SpringBootTest注释的@AutoConfigure…​注释。
如果你对应用程序“切片”不感兴趣,但希望使用一些自动配置的测试 bean,则可以使用此组合。

# 8.3.12.自动配置的 JSON 测试

要测试对象 JSON 序列化和反序列化是否如预期的那样工作,你可以使用@JsonTest注释。@JsonTest自动配置可用的受支持的 JSON 映射器,它可以是以下库之一:

  • JacksonObjectMapper,任何@JsonComponentbean 和任何 JacksonModules

  • Gson

  • Jsonb

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

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

Spring 引导包括基于 AssertJ 的帮助器,这些帮助器与 JSONASSERT 和 JSONPATH 库一起工作,以检查 JSON 是否如预期的那样出现。JacksonTesterGsonTesterJsonbTesterBasicJsonTester类可以分别用于 Jackson、GSON、JSONB 和字符串。当使用@JsonTest时,测试类上的任何 helper 字段都可以是@Autowired。下面的示例展示了一个用于 Jackson 的测试类:

import org.junit.jupiter.api.Test;

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

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

@JsonTest
class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

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

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

}

JSON helper 类也可以在标准单元测试中直接使用。
要这样做,如果不使用@JsonTest方法,请在你的initFields方法中调用该 helper 的initFields方法。

如果使用 Spring boot 的基于 AssertJ 的助手在给定的 JSON 路径上断言一个数字值,则可能无法根据类型使用isEqualTo。相反,你可以使用 AssertJ 的satisfies来断言该值匹配给定的条件。例如,下面的示例断言,在0.01的偏移量内,实际数字是一个接近0.15的浮点值。

@Test
void someTest() throws Exception {
    SomeObject value = new SomeObject(0.152f);
    assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
            .satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}

# 8.3.13.自动配置的 Spring MVC 测试

要测试 Spring MVC 控制器是否如预期的那样工作,请使用@WebMvcTest注释。@WebMvcTest自动配置 Spring MVC 基础架构,并将扫描的 bean 限制为@Controller@ControllerAdviceConverterGenericConverterFilterHandlerInterceptor,<1440"/>,,和”1442">。常规的@Component@ConfigurationPropertiesbean 在使用@WebMvcTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。

@WebMvcTest启用的自动配置设置列表可以是在附录中找到
如果需要注册额外的组件,例如 JacksonModule,则可以在测试中使用@Import导入额外的配置类。

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

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

你还可以在非-@WebMvcTest(例如@SpringBootTest)中自动配置MockMvc(例如@SpringBootTest),方法是用@AutoConfigureMockMvc对其进行注释。
下面的示例使用MockMvc:
import org.junit.jupiter.api.Test;

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

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

@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

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

}

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

如果使用 htmlUnit 和 Selenium,Auto-Configuration 还提供了一个 htmlUnit<gtr="1463"/> Bean 和/或一个 Selenium<gtr="1464"/> Bean。以下示例使用了 htmlUnit:

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

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

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

@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

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

}

默认情况下, Spring boot 将WebDriverbean 放在一个特殊的“范围”中,以确保每次测试后驱动程序退出,并且注入了一个新的实例。
如果你不想要这种行为,可以将@Scope("singleton")添加到你的WebDriver定义中。
由 Spring 引导创建的webDriver作用域将替换任何用户定义的同名作用域。
如果你定义自己的webDriver作用域,则当你使用@WebMvcTest时,你可能会发现它停止工作。

如果在 Classpath 上具有 Spring 安全性,则@WebMvcTest也将扫描WebSecurityConfigurerbean。你可以使用 Spring Security 的测试支持,而不是完全禁用此类测试的安全性。关于如何使用 Spring Security 的MockMvc支持的更多详细信息,可以在这个 *howto.html*how-to 部分中找到。

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

# 8.3.14.自动配置的 Spring WebFlux 测试

要测试Spring WebFlux (opens new window)控制器是否按预期工作,可以使用@WebFluxTest注释。@WebFluxTest自动配置 Spring WebFlux 基础设施,并将扫描的 bean 限制为@Controller@ControllerAdviceConverterGenericConverterWebFilter,以及WebFluxConfigurer。常规的@Component@ConfigurationPropertiesbean 在使用@WebFluxTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。

@WebFluxTest启用的自动配置的列表可以是在附录中找到
如果需要注册额外的组件,例如 JacksonModule,则可以在测试中使用@Import导入额外的配置类。

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

@WebFluxTest还自动配置[WebTestClient](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/Reference/html/testing.html#WebTestClient),它提供了一种强大的方式,可以快速测试 WebFlux 控制器,而无需启动完整的 HTTP 服务器。

你还可以在非-@WebFluxTest(例如@SpringBootTest)中通过使用@AutoConfigureWebTestClient对其进行注释来自动配置WebTestClient
下面的示例显示了一个同时使用@WebFluxTestWebTestClient的类:
import org.junit.jupiter.api.Test;

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

import static org.mockito.BDDMockito.given;

@WebFluxTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private WebTestClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Honda Civic");
    }

}

此设置仅由 WebFlux 应用程序支持,因为在模拟 Web 应用程序中使用WebTestClient目前仅与 WebFlux 一起工作。
@WebFluxTest无法检测通过 Functional Web Framework 注册的路由。
对于在上下文中测试RouterFunctionbean,请考虑通过使用@Import或通过使用@SpringBootTest来导入你的RouterFunction自己。
@WebFluxTest无法检测到注册为@Bean类型的SecurityWebFilterChain的自定义安全配置。
要在测试中包含该配置,你将需要通过使用@Import或通过使用@SpringBootTest导入注册 Bean 的配置。
有时编写 Spring WebFlux 测试是不够的; Spring Boot 可以帮助你运行使用实际服务器进行完整的端到端测试

# 8.3.15.自动配置的数据 Cassandra 测试

你可以使用@DataCassandraTest来测试 Cassandra 应用程序。默认情况下,它配置CassandraTemplate,扫描@Table类,并配置 Spring 数据 Cassandra 存储库。常规的@Component@ConfigurationPropertiesbean 在使用@DataCassandraTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。(关于使用 Spring boot 的 Cassandra 的更多信息,请参见本章前面的“data.html”。

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

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

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

@DataCassandraTest
class MyDataCassandraTests {

    @Autowired
    private SomeRepository repository;

}

# 8.3.16.自动配置的数据 JPA 测试

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

常规的@Component@ConfigurationPropertiesbean 在使用@DataJpaTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。

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

默认情况下,数据 JPA 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,你可以禁用测试或整个类的事务管理,如下所示:

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

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

    // ...

}

数据 JPA 测试还可能注入一个[TestEntityManager](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-test-autofigure/SRC/main/java/org/springframework/boot/test/autoform/ JPA/testentitymanager.java) Bean,它为专门为测试设计的标准 JPA <gtr="1548"/>提供了一种替代方案。

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

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

import org.junit.jupiter.api.Test;

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

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

@DataJpaTest
class MyRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

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

}

内存中的嵌入式数据库通常可以很好地用于测试,因为它们速度快且不需要任何安装。但是,如果你更喜欢对真实的数据库运行测试,则可以使用@AutoConfigureTestDatabase注释,如下例所示:

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

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

    // ...

}

# 8.3.17.自动配置的 JDBC 测试

@JdbcTest类似于@DataJpaTest,但用于仅需要DataSource且不使用 Spring 数据 JDBC 的测试。默认情况下,它配置内存中的嵌入式数据库和JdbcTemplate。当使用@JdbcTest注释时,常规的@Component@ConfigurationPropertiesbean 不会被扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。

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

默认情况下,JDBC 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,那么你可以禁用测试或整个类的事务管理,如下所示:

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

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

}

如果你希望你的测试在真实的数据库中运行,那么你可以使用@AutoConfigureTestDatabase注释,就像使用DataJpaTest注释一样。(见“Auto-configured Data JPA Tests”。

# 8.3.18.自动配置的数据 JDBC 测试

@DataJdbcTest类似于@JdbcTest,但用于使用 Spring 数据 JDBC 存储库的测试。默认情况下,它配置内存中的嵌入式数据库、JdbcTemplate和 Spring 数据 JDBC 存储库。常规的@Component@ConfigurationPropertiesbean 在使用@DataJdbcTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。

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

默认情况下,数据 JDBC 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,那么你可以禁用测试或整个测试类的事务管理,如在 JDBC 示例中显示

如果你希望你的测试在真实的数据库中运行,那么你可以使用@AutoConfigureTestDatabase注释,就像使用DataJpaTest注释一样。(见“Auto-configured Data JPA Tests”。

# 8.3.19.自动配置的 Jooq 测试

你可以以与@JdbcTest类似的方式使用@JooqTest,但用于与 Jooq 相关的测试。由于 Jooq 在很大程度上依赖于与数据库模式相对应的基于 Java 的模式,因此使用了现有的DataSource。如果要用内存数据库替换它,可以使用@AutoConfigureTestDatabase来覆盖这些设置。(有关使用 Spring boot 使用 Jooq 的更多信息,请参见本章前面的“data.html”。)常规@Component@ConfigurationPropertiesbean 在使用@JooqTest注释时不会被扫描。@EnableConfigurationPropertiesbean 可用于包括@ConfigurationPropertiesbean。

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

@JooqTest配置DSLContext。下面的示例显示了正在使用的@JooqTest注释:

import org.jooq.DSLContext;

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

@JooqTest
class MyJooqTests {

    @Autowired
    private DSLContext dslContext;

    // ...

}

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

# 8.3.20.自动配置的数据 MongoDB 测试

你可以使用@DataMongoTest来测试 MongoDB 应用程序。默认情况下,它配置内存中的嵌入式 MongoDB(如果可用),配置MongoTemplate,扫描@Document类,并配置 Spring 数据 MongoDB 存储库。常规的@Component@ConfigurationPropertiesbean 在使用@DataMongoTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。(有关使用 Spring Boot 的 MongoDB 的更多信息,请参见本章前面的“data.html”。

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

下面的类显示了使用中的@DataMongoTest注释:

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

@DataMongoTest
class MyDataMongoDbTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    // ...

}

内嵌入内存的 MongoDB 通常在测试中运行良好,因为它速度快,并且不需要任何开发人员安装。但是,如果你更喜欢在真正的 MongoDB 服务器上运行测试,那么你应该排除嵌入的 MongoDB 自动配置,如下例所示:

import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;

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

    // ...

}

# 8.3.21.自动配置的数据 NEO4J 测试

你可以使用@DataNeo4jTest来测试 NEO4J 应用程序。默认情况下,它扫描@Node类,并配置 Spring 数据 NEO4j 存储库。常规的@Component@ConfigurationPropertiesbean 在使用@DataNeo4jTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。(有关在 Spring 引导下使用 NEO4j 的更多信息,请参见本章前面的“data.html”。

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

下面的示例显示了在 Spring 引导中使用 NEO4J 测试的典型设置:

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

@DataNeo4jTest
class MyDataNeo4jTests {

    @Autowired
    private SomeRepository repository;

    // ...

}

默认情况下,数据 NEO4J 测试是事务性的,并在每个测试结束时回滚。有关更多详细信息,请参见 Spring 框架参考文档中的相关部分 (opens new window)。如果这不是你想要的,那么你可以禁用测试或整个类的事务管理,如下所示:

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

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

}

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

# 8.3.22.自动配置的数据 Redis 测试

你可以使用@DataRedisTest来测试 Redis 应用程序。默认情况下,它扫描@RedisHash类并配置 Spring 数据 Redis 存储库。常规的@Component@ConfigurationPropertiesbean 在使用@DataRedisTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。(有关在 Spring 引导下使用 Redis 的更多信息,请参见本章前面的“data.html”。

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

下面的示例显示了使用中的@DataRedisTest注释:

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

@DataRedisTest
class MyDataRedisTests {

    @Autowired
    private SomeRepository repository;

    // ...

}

# 8.3.23.自动配置的数据 LDAP 测试

你可以使用@DataLdapTest来测试 LDAP 应用程序。默认情况下,它配置内存中的嵌入式 LDAP(如果可用),配置LdapTemplate,扫描@Entry类,并配置 Spring 数据 LDAP 存储库。常规的@Component@ConfigurationPropertiesbean 在使用@DataLdapTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。(有关在 Spring boot 中使用 LDAP 的更多信息,请参见本章前面的“data.html”。

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

下面的示例显示了使用中的@DataLdapTest注释:

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

@DataLdapTest
class MyDataLdapTests {

    @Autowired
    private LdapTemplate ldapTemplate;

    // ...

}

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

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

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

    // ...

}

# 8.3.24.自动配置的 REST 客户机

你可以使用@RestClientTest注释来测试 REST 客户机。默认情况下,它会自动配置 Jackson、GSON 和 JSONB 支持,配置RestTemplateBuilder,并添加对MockRestServiceServer的支持。常规的@Component@ConfigurationPropertiesbean 在使用@RestClientTest注释时不进行扫描。@EnableConfigurationProperties可以用于包括@ConfigurationPropertiesbean。

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

要测试的特定 bean 应该使用valuecomponentscomponents属性来指定,如以下示例所示:

import org.junit.jupiter.api.Test;

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

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

@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {

    @Autowired
    private RemoteVehicleDetailsService service;

    @Autowired
    private MockRestServiceServer server;

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

}

# 8.3.25.自动配置的 Spring REST DOCS 测试

在使用模拟 MVC、Rest Assured 或 WebTestClient 的测试中,可以使用@AutoConfigureRestDocs注释来使用Spring REST Docs (opens new window)。它消除了对 Spring REST DOCS 中的 JUnit 扩展的需要。

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

# Spring 使用模拟 MVC 的 REST DOCS 测试的自动配置

@AutoConfigureRestDocs在测试基于 Servlet 的 Web 应用程序时定制MockMvc Bean 以使用 Spring REST DOCS。你可以通过使用@Autowired注入它,并在测试中使用它,就像在使用模拟 MVC 和 Spring REST DOCS 时通常使用的那样,如以下示例所示:

import org.junit.jupiter.api.Test;

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

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

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

    @Autowired
    private MockMvc mvc;

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

}

如果需要对 Spring REST DOCS 配置的控制比@AutoConfigureRestDocs的属性提供的更多,则可以使用RestDocsMockMvcConfigurationCustomizer Bean,如以下示例所示:

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

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

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

}

如果希望利用 Spring REST DOCS 对参数化输出目录的支持,则可以创建RestDocumentationResultHandler Bean。自动配置使用此结果处理程序调用alwaysDo,从而使每个MockMvc调用自动生成缺省片段。下面的示例显示了正在定义的RestDocumentationResultHandler:

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

@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {

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

}

# 使用 WebTestClient 自动配置的 Spring REST DOCS 测试

@AutoConfigureRestDocs在测试反应性 Web 应用程序时也可以与WebTestClient一起使用。你可以通过使用@Autowired注入它,并在测试中使用它,就像在使用@WebFluxTest和 Spring REST DOCS 时通常使用它一样,如以下示例所示:

import org.junit.jupiter.api.Test;

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

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

@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {

    @Autowired
    private WebTestClient webTestClient;

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

}

如果你需要对 Spring REST DOCS 配置的控制超过由@AutoConfigureRestDocs的属性提供的控制,则可以使用RestDocsWebTestClientConfigurationCustomizer Bean,如以下示例所示:

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

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

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

}

如果希望利用 Spring REST DOCS 对参数化输出目录的支持,则可以使用WebTestClientBuilderCustomizer来为每个实体交换结果配置消费者。下面的示例显示了正在定义的这样的WebTestClientBuilderCustomizer:

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

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

@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {

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

}

# Spring 具有 REST 保证的 REST DOCS 测试的自动配置

@AutoConfigureRestDocs生成一个RequestSpecification Bean,预先配置为使用 Spring REST DOCS,可用于你的测试。你可以通过使用@Autowired注入它,并在测试中使用它,就像在使用 REST ASSURED 和 Spring REST DOCS 时通常使用它一样,如以下示例所示:

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

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

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

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

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

}

如果需要对 Spring REST DOCS 配置的控制超过由@AutoConfigureRestDocs的属性提供的控制,则可以使用RestDocsRestAssuredConfigurationCustomizer Bean,如以下示例所示:

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

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

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

}

# 8.3.26.自动配置的 Spring Web 服务测试

# 自动配置的 Spring Web 服务客户端测试

你可以使用@WebServiceClientTest来测试使用 Spring Web 服务项目调用 Web 服务的应用程序。默认情况下,它配置一个模拟WebServiceServer Bean 并自动定制你的WebServiceTemplateBuilder。(有关使用 Spring boot 的 Web 服务的更多信息,请参见本章前面的“io.html”。

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

下面的示例显示了使用中的@WebServiceClientTest注释:

import org.junit.jupiter.api.Test;

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

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

@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {

    @Autowired
    private MockWebServiceServer server;

    @Autowired
    private SomeWebService someWebService;

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

}

# 自动配置的 Spring Web 服务服务器测试

你可以使用@WebServiceServerTest来测试使用 Spring Web 服务项目实现 Web 服务的应用程序。默认情况下,它配置了一个MockWebServiceClient Bean,可用于调用你的 Web 服务端点。(有关使用 Spring 引导的 Web 服务的更多信息,请参见本章前面的“io.html”。

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

下面的示例显示了使用中的@WebServiceServerTest注释:

import org.junit.jupiter.api.Test;

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

@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {

    @Autowired
    private MockWebServiceClient client;

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

}

# 8.3.27.附加的自动配置和切片

每个切片提供一个或多个@AutoConfigure…​注释,即定义了作为切片的一部分应该包含的自动配置。通过创建自定义@AutoConfigure…​注释或在测试中添加@ImportAutoConfiguration,可以在逐个测试的基础上添加额外的自动配置,如下例所示:

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

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

}

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

或者,可以通过在META-INF/spring.factories中注册它们,为任何使用切片注释的情况添加额外的自动配置,如以下示例所示:

org.springframework.boot.test.autoconfigure.jdbc.JdbcTest=com.example.IntegrationAutoConfiguration
切片或@AutoConfigure…​注释可以通过这种方式进行定制,只要它是用@ImportAutoConfiguration进行元注释的。

# 8.3.28.用户配置和切片

如果以一种合理的方式构造你的代码,你的@SpringBootApplication类是默认使用作为测试的配置。

因此,重要的是,不要在应用程序的主类中浪费特定于其特定功能领域的配置设置。

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

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableBatchProcessing
public class MyApplication {

    // ...

}

因为这个类是测试的源配置,所以任何切片测试实际上都会尝试启动 Spring 批处理,而这绝对不是你想要做的。推荐的方法是将该区域特定的配置移动到与应用程序处于同一级别的单独的@Configuration类,如下例所示:

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.context.annotation.Configuration;

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

    // ...

}

根据应用程序的复杂性,你可以有一个用于自定义的@Configuration类,或者每个域区域有一个类。
后一种方法允许你在你的一个测试中启用它,如果需要的话,使用@Import注释。

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

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

@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {

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

}

但是,下面的配置将导致测试切片加载自定义WebMvcConfigurer

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

@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    // ...

}

另一个令人困惑的原因是扫描。假设,当你以一种合理的方式构造代码时,你需要扫描一个额外的包。你的应用程序可能类似于以下代码:

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

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

    // ...

}

这样做有效地覆盖了默认的组件扫描指令,其副作用是无论你选择的是哪一片,都要扫描这两个包。例如,@DataJpaTest似乎会突然扫描应用程序的组件和用户配置。同样,将自定义指令移动到一个单独的类是解决此问题的一个好方法。

如果这不是你的一个选项,那么你可以在测试的层次结构中的某个地方创建一个@SpringBootConfiguration,以便使用它。
或者,你可以为你的测试指定一个源,这将禁用查找缺省源的行为。

# 8.3.29.使用 Spock 测试 Spring 启动应用程序

Spock2.x 可用于测试 Spring 引导应用程序。为此,在应用程序的构建中添加对 Spock 的spock-spring模块的依赖关系。spock-spring将 Spring 的测试框架集成到 Spock 中。有关更多详细信息,请参见the documentation for Spock’s Spring module (opens new window)

# 8.4.测试实用程序

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

# 8.4.1.ConfigDataApplicationContextInitializer

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

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

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

    // ...

}

单独使用ConfigDataApplicationContextInitializer并不提供对@Value("${…​}")注入的支持。
其唯一的工作是确保application.properties文件被加载到 Spring 的Environment中,
对于@Value的支持,你需要另外配置一个PropertySourcesPlaceholderConfigurer,或者使用@SpringBootTest,它为你自动配置了一个。

# 8.4.2.测试 PropertyValues

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

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;

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

class MyEnvironmentTests {

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

}

# 8.4.3.输出捕获

OutputCapture是一个 JUnitExtension,你可以使用它来捕获System.outSystem.err输出。要使用添加@ExtendWith(OutputCaptureExtension.class)并将CapturedOutput作为参数注入到你的测试类构造函数或测试方法中,方法如下:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

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

@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {

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

}

# 8.4.4.测试模板

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

Spring Framework5.0 提供了一个新的WebTestClient,它对WebFlux 集成测试WebFlux 和 MVC 端到端测试都有效。
它为断言提供了一个流畅的 API,而不像TestRestTemplate

建议使用 Apache HTTP 客户机(版本 4.3.2 或更好),但不是强制的。如果你的 Classpath 上有这样的配置,则TestRestTemplate通过适当地配置客户机来进行响应。如果你确实使用了 Apache 的 HTTP 客户机,则启用了一些额外的测试友好特性:

  • 重定向不会被跟踪(因此你可以断言响应位置)。

  • cookies 被忽略(因此模板是无状态的)。

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

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;

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

class MyTests {

    private TestRestTemplate template = new TestRestTemplate();

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

}

或者,如果使用@SpringBootTest注释和WebEnvironment.RANDOM_PORTWebEnvironment.DEFINED_PORT,则可以插入一个完全配置的TestRestTemplate并开始使用它。如果需要,可以通过RestTemplateBuilder Bean 应用额外的自定义。任何未指定主机和端口的 URL 都会自动连接到嵌入式服务器,如以下示例所示:

import java.time.Duration;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;

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

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

    @Autowired
    private TestRestTemplate template;

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

    @TestConfiguration(proxyBeanMethods = false)
    static class RestTemplateBuilderConfiguration {

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

    }

}

# 9. 创建自己的自动配置

如果你在一家开发共享库的公司工作,或者在一家开放源代码或商业库工作,那么你可能希望开发自己的自动配置。自动配置类可以捆绑在外部 JAR 中,并且仍然可以在 Spring 启动时被拾取。

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

可以使用演示项目 (opens new window)来展示如何一步一步地创建一个启动程序。

# 9.1.理解自动配置的 bean

在引擎盖下,自动配置是通过标准的@Configuration类实现的。附加的@Conditional注释用于在应用自动配置时进行约束。通常,自动配置类使用@ConditionalOnClass@ConditionalOnMissingBean注释。这确保了自动配置仅在找到相关类以及尚未声明自己的@Configuration时才适用。

你可以浏览[spring-boot-autoconfigure](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/org/springframework/boot/autofigure/autofigure)的源代码,查看 Spring 提供的@Configuration类(参见[META-INF/spring.factories](https://github.com/ Spring-projects/[[[[tree]-tree/6.6]-

# 9.2.定位自动配置候选项

Spring 引导检查在你发布的 jar 中是否存在META-INF/spring.factories文件。该文件应该在EnableAutoConfiguration键下列出你的配置类,如下例所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
自动配置必须以只有的方式加载。
确保它们是在特定的包空间中定义的,并且它们永远不是组件扫描的目标。
此外,自动配置类不应使组件扫描能够找到其他组件。应该使用
特定的@Imports。

你可以使用[@AutoConfigureAfter](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autconfigure/SRC/main/java/org/SpringFramework/boot/autoConfigure/autoConfigure/autoafter.java.java)或[@AutoConfigureBefore](https:///github.com/ Spring-projects/[[ Spring-boot/tree/tree/v2.6.4/例如,如果你提供了特定于 Web 的配置,那么你的类可能需要在WebMvcAutoConfiguration之后应用。

如果你希望订购某些不应相互有任何直接了解的自动配置,也可以使用@AutoConfigureOrder。该注释与常规的@Order注释具有相同的语义,但为自动配置类提供了专用的顺序。

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

# 9.3.条件注释

你几乎总是希望在自动配置类中包含一个或多个@Conditional注释。@ConditionalOnMissingBean注释是一个常见的示例,如果开发人员对你的默认值不满意,它将允许他们覆盖自动配置。

Spring 引导包括许多@Conditional注释,你可以通过注释@Configuration类或单独的@Bean方法在自己的代码中重用这些注释。这些注释包括:

# 9.3.1.类条件

@ConditionalOnClass@ConditionalOnMissingClass注释让@Configuration类基于特定类的存在或不存在而被包括。由于批注元数据是通过使用ASM (opens new window)进行解析的,因此你可以使用value属性来引用真正的类,即使该类实际上可能不会出现在正在运行的应用程序上 Classpath。如果你希望通过使用String值来指定类名,也可以使用name属性。

此机制不以同样的方式应用于@Bean方法,其中返回类型通常是条件的目标:在方法的条件应用之前,JVM 将装载类和可能处理的方法引用,如果类不存在,这些引用将失败。

要处理此场景,可以使用一个单独的@Configuration类来隔离该条件,如以下示例所示:

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
// Some conditions ...
public class MyAutoConfiguration {

    // Auto-configured beans ...

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

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

    }

}

如果使用@ConditionalOnClass@ConditionalOnMissingClass作为元注释的一部分来编写自己的合成注释,则必须使用name作为在这种情况下未处理的类的引用。

# 9.3.2. Bean 条件

@ConditionalOnBean@ConditionalOnMissingBean注释允许基于特定 bean 的存在或不存在来包含 Bean。可以使用value属性按类型指定 bean,也可以使用name属性按名称指定 bean。search属性允许你限制在搜索 bean 时应该考虑的ApplicationContext层次结构。

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

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {

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

}

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

你需要非常小心添加 Bean 定义的顺序,因为这些条件是基于到目前为止已处理的内容进行评估的。,由于这个原因,<gt r=“1826”/,我们建议在自动配置类上只使用@ConditionalOnBean@ConditionalOnMissingBean注释(因为这些注释保证在添加了任何用户定义的 Bean 定义之后加载)。
@ConditionalOnBean@ConditionalOnMissingBean并不阻止创建@Configuration类。
在类级别上使用这些条件与用注释标记每个包含的@Bean方法之间的唯一区别是如果条件不匹配,则前者阻止将@Configuration类注册为 Bean。
在声明@Bean方法时,在方法的返回类型中提供尽可能多的类型信息,例如,
,如果 Bean 的 Concrete 类实现了一个接口,那么 Bean 方法的返回类型应该是 Concrete 类而不是接口。在方法中提供尽可能多的类型信息在使用 Bean 条件时尤其重要,因为它们的评估只能依赖于在方法签名中可用的类型信息。

# 9.3.3.财产条件

@ConditionalOnProperty注释允许基于 Spring Environment 属性包含配置。使用prefixname属性来指定应该检查的属性。默认情况下,任何存在且不等于false的属性都是匹配的。还可以使用havingValuematchIfMissing属性创建更高级的检查。

# 9.3.4.资源条件

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

# 9.3.5.Web 应用程序条件

@ConditionalOnWebApplication@ConditionalOnNotWebApplication注释允许根据应用程序是否为“Web 应用程序”而包括配置。基于 Servlet 的 Web 应用程序是任何使用 Spring WebApplicationContext、定义session作用域或具有ConfigurableWebEnvironment的应用程序。反应性 Web 应用程序是任何使用ReactiveWebApplicationContext或具有ConfigurableReactiveWebEnvironment的应用程序。

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

# 9.3.6.SPEL 表达条件

@ConditionalOnExpression注释允许基于Spel 表达式 (opens new window)的结果包含配置。

在表达式中引用 Bean 将导致 Bean 在上下文刷新处理中很早就被初始化。
因此, Bean 将不符合后处理的条件(例如配置属性绑定),并且其状态可能不完整。

# 9.4.测试你的自动配置

自动配置可能受到许多因素的影响:用户配置(@Bean定义和Environment定制)、条件评估(存在特定库),以及其他因素。具体地说,每个测试都应该创建一个定义良好的ApplicationContext,它代表了这些定制的组合。ApplicationContextRunner提供了一种很好的实现方法。

ApplicationContextRunner通常被定义为用于收集基础、公共配置的测试类的字段。下面的示例确保始终调用MyServiceAutoConfiguration:

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

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

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

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

@Configuration(proxyBeanMethods = false)
static class UserConfiguration {

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

}

也可以方便地定制Environment,如以下示例所示:

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

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

import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

class MyConditionEvaluationReportingTests {

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

}

# 9.4.1.模拟 Web 上下文

如果需要测试仅在 Servlet 或反应性 Web 应用程序上下文中操作的自动配置,则分别使用WebApplicationContextRunnerReactiveWebApplicationContextRunner

# 9.4.2.超越 Classpath

也可以测试在运行时不存在特定类和/或包时会发生什么。 Spring 具有FilteredClassLoader的引导船,该引导船可以很容易地被跑步者使用。在下面的示例中,我们断言,如果MyService不存在,则自动配置将被正确禁用:

@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
    this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
            .run((context) -> assertThat(context).doesNotHaveBean("myService"));
}

# 9.5.创建自己的启动器

典型的引导启动器包含自动配置和自定义给定技术基础设施的代码,我们将其称为“Acme”。为了使其易于扩展,可以将专用名称空间中的许多配置键公开到环境中。最后,提供了一个单一的“启动”依赖项,以帮助用户尽可能轻松地启动。

具体地说,自定义启动器可以包含以下内容:

  • autoconfigure模块,其中包含“acme”的自动配置代码。

  • starter模块提供了对autoconfigure模块的依赖关系,以及“acme”和任何通常有用的附加依赖关系。简而言之,添加 starter 应该提供开始使用该库所需的一切。

这种在两个模块中的分离是绝对没有必要的。如果“Acme”有几种口味、选项或可选功能,那么最好分离自动配置,因为你可以清楚地表达这样一个事实,即某些功能是可选的。此外,你还可以创建一个启动器,提供有关这些可选依赖项的意见。同时,其他人只能依赖autoconfigure模块,并根据不同的意见制作自己的启动器。

如果自动配置相对简单,并且没有可选功能,那么合并启动器中的两个模块肯定是一个选项。

# 9.5.1.命名

你应该确保为你的启动器提供一个正确的名称空间。即使使用不同的 Maven groupId,也不要以spring-boot开始模块名称。我们可能会在将来为你的自动配置提供官方支持。

根据经验,你应该以 starter 命名一个组合模块。例如,假设你正在为“acme”创建一个启动器,并且将自动配置模块命名为acme-spring-boot,并将启动器命名为acme-spring-boot-starter。如果只有一个模块合并了这两个模块,请将其命名为acme-spring-boot-starter

# 9.5.2.配置键

如果你的启动器提供了配置键,请为它们使用唯一的命名空间。特别是,不要在 Spring 引导使用的名称空间中包含你的键(例如servermanagementspring,等等)。如果使用相同的名称空间,将来我们可能会以破坏模块的方式修改这些名称空间。作为一条经验法则,在所有的键前加上一个你拥有的名称空间(例如acme)。

通过为每个属性添加字段 Javadoc,确保配置键是有文档记录的,如下例所示:

import java.time.Duration;

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

@ConfigurationProperties("acme")
public class AcmeProperties {

    /**
     * Whether to check the location of acme resources.
     */
    private boolean checkLocation = true;

    /**
     * Timeout for establishing a connection to the acme server.
     */
    private Duration loginTimeout = Duration.ofSeconds(3);

    // getters/setters ...

    public boolean isCheckLocation() {
        return this.checkLocation;
    }

    public void setCheckLocation(boolean checkLocation) {
        this.checkLocation = checkLocation;
    }

    public Duration getLoginTimeout() {
        return this.loginTimeout;
    }

    public void setLoginTimeout(Duration loginTimeout) {
        this.loginTimeout = loginTimeout;
    }

}

你应该只在@ConfigurationProperties字段 Javadoc 中使用纯文本,因为它们在被添加到 JSON 之前不会被处理。

以下是我们内部遵循的一些规则,以确保描述一致:

  • 不要以“the”或“a”开头描述。

  • 对于boolean类型,以“是否”或“启用”开始描述。

  • 对于基于集合的类型,以“逗号分隔的列表”开始描述。

  • 使用java.time.Duration而不是long,并描述缺省单位,如果它与毫秒不同,例如“如果没有指定持续时间后缀,将使用秒”。

  • 不要在描述中提供默认值,除非它必须在运行时确定。

请确保触发元数据生成,以便你的密钥也可以使用 IDE 辅助。你可能想要查看生成的元数据(META-INF/spring-configuration-metadata.json),以确保你的密钥被正确地记录。在兼容的 IDE 中使用自己的启动器也是验证元数据质量的一个好主意。

# 9.5.3.“自动配置”模块

autoconfigure模块包含了启动该库所需的所有内容。它还可能包含配置键定义(例如@ConfigurationProperties)和任何回调接口,可用于进一步定制组件的初始化方式。

你应该将库的依赖关系标记为可选的,这样你就可以更容易地在项目中包含autoconfigure模块。
如果你这样做,那么库就不会被提供,并且在默认情况下, Spring back off。

Spring 引导使用注释处理器来收集元数据文件(META-INF/spring-autoconfigure-metadata.properties)中关于自动配置的条件。如果该文件存在,它将被用来急切地过滤不匹配的自动配置,这将提高启动时间。建议在包含自动配置的模块中添加以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>

如果直接在应用程序中定义了自动配置,请确保配置spring-boot-maven-plugin,以防止repackage目标将依赖项添加到 fat jar 中:

<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-autoconfigure-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

对于 Gradle 4.5 或更早的版本,依赖关系应该在compileOnly配置中声明,如以下示例所示:

dependencies {
    compileOnly "org.springframework.boot:spring-boot-autoconfigure-processor"
}

对于 Gradle 4.6 及更高版本,依赖关系应该在annotationProcessor配置中声明,如以下示例所示:

dependencies {
    annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}

# 9.5.4.启动器模块

起动器实际上是一个空的 jar。它的唯一目的是提供使用该库所需的依赖项。你可以把它看作是对开始工作需要什么的一种固执己见的看法。

不要对添加了启动器的项目进行假设。如果你正在自动配置的库通常需要其他启动器,那么也应该提及它们。如果可选依赖关系的数量很高,那么提供一组适当的默认值依赖关系可能会很困难,因为你应该避免包含对于库的典型使用来说不必要的依赖关系。换句话说,你不应该包括可选的依赖关系。

无论哪种方式,你的启动程序都必须直接或间接地引用核心 Spring 启动程序(spring-boot-starter)(如果你的启动程序依赖于另一个启动程序,则无需添加它),如果仅使用你的自定义启动程序创建项目,则
, Spring Boot 的核心功能将因核心启动器的出现而受到尊重。

# 10. Kotlin 支持

Kotlin (opens new window)是一种针对 JVM(和其他平台)的静态类型语言,它允许编写简洁而优雅的代码,同时提供互操作性 (opens new window)用 Java 编写的现有库。

Spring Boot 通过利用其他 Spring 项目中的支持来提供 Kotlin 支持,例如 Spring 框架、 Spring 数据和反应堆。有关更多信息,请参见Spring Framework Kotlin support documentation (opens new window)

启动 Spring boot 和 Kotlin 的最简单方法是遵循这个全面的教程 (opens new window)。你可以使用start.spring.io (opens new window)创建新的 Kotlin 项目。如果你需要支持,可以随时加入Kotlin Slack (opens new window)的 # Spring 频道,或者使用springkotlin标签在堆栈溢出 (opens new window)上提问。

# 10.1.所需经费

Spring 启动需要至少 Kotlin 1.3.x 并且通过依赖管理管理管理管理合适的 Kotlin 版本。要使用 Kotlin,org.jetbrains.kotlin:kotlin-stdliborg.jetbrains.kotlin:kotlin-reflect必须存在于 Classpath 上。也可以使用kotlin-stdlib变种kotlin-stdlib-jdk7kotlin-stdlib-jdk8

由于Kotlin classes are final by default (opens new window),你可能想要配置kotlin-spring (opens new window)插件,以便自动打开 Spring-注释的类,以便可以代理它们。

在 Kotlin 中,序列化/反序列化 JSON 数据需要Jackson’s Kotlin module (opens new window)。当在 Classpath 上找到它时,它会自动注册。如果 Jackson 和 Kotlin 存在但 Jackson Kotlin 模块不存在,则记录警告消息。

默认情况下,如果在start.spring.io (opens new window)上引导一个 Kotlin 项目,就会提供这些依赖项和插件。

# 10.2.零安全

Kotlin 的关键特征之一是零安全 (opens new window)。它在编译时处理null值,而不是将问题推迟到运行时并遇到NullPointerException。这有助于消除常见的错误源,而无需支付Optional之类包装器的费用。 Kotlin 还允许使用如在此comprehensive guide to null-safety in Kotlin (opens new window)中所描述的具有可空的值的函数构造。

虽然 Java 不允许在其类型系统中表示空安全,但 Spring Framework、 Spring Data 和 Reactor 现在通过对工具友好的注释为其 API 提供了空安全。默认情况下,来自 Kotlin 中使用的 Java API 的类型被识别为平台类型 (opens new window),对其放松了空检查。Kotlin’s support for JSR 305 annotations (opens new window)与空性注释相结合,为 Kotlin 中相关的 Spring API 提供了空安全性。

可以通过添加带有以下选项的-Xjsr305编译器标志来配置 JSR305 检查:-Xjsr305={strict|warn|ignore}。默认行为与-Xjsr305=warn相同。在从 Spring API 推断出的 Kotlin 类型中,strict值必须考虑到空安全性,但在使用时应了解 Spring API 的无效性声明即使在较小的版本之间也可以发展,并且将来可能会添加更多的检查)。

还不支持泛型类型参数、varargs 和数组元素的可空性。
有关最新信息,请参见SPR-15942 (opens new window)
还请注意 Spring Boot 自己的 API 是尚未注释 (opens new window)

# 10.3. Kotlin 空气污染指数

# 10.3.1.运行应用程序

Spring Boot 提供了一种惯用的方式来运行带有runApplication<MyApplication>(*args)的应用程序,如以下示例所示:

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

@SpringBootApplication
class MyApplication

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

这是SpringApplication.run(MyApplication::class.java, *args)的直接替换。它还允许定制应用程序,如以下示例所示:

runApplication<MyApplication>(*args) {
    setBannerMode(OFF)
}

# 10.3.2.扩展

Kotlin extensions (opens new window)提供了扩展具有附加功能的现有类的能力。 Spring 引导 Kotlin API 利用这些扩展为现有的 API 添加新的 Kotlin 特定的便利。

TestRestTemplate扩展,类似于 Spring 框架中为RestOperations提供的扩展,在 Spring 框架中。在其他事情中,这些扩展使得有可能利用 Kotlin 具体化的类型参数。

# 10.4.依赖管理

为了避免混合 Kotlin 依赖于 Classpath 的不同版本, Spring 引导导入 Kotlin BOM。

有了 Maven, Kotlin 版本可以通过设置kotlin.version属性来定制,并且为kotlin-maven-plugin提供插件管理。在 Gradle 中, Spring 引导插件自动将kotlin.version与 Kotlin 插件的版本对齐。

Spring Boot 还通过导入 Kotlin 协程 BOM 来管理协程依赖的版本。可以通过设置kotlin-coroutines.version属性来定制版本。

如果一个 Kotlin 项目至少对start.spring.io (opens new window)具有一个反应性依赖项,则默认情况下提供org.jetbrains.kotlinx:kotlinx-coroutines-reactor依赖项。

# 10.5.@configrationProperties

@ConfigurationProperties与[@ConstructorBinding](#features.external-config.typesafe-configuration-properties.constructor-binding)结合使用时,支持具有不可变val属性的类,如以下示例所示:

@ConstructorBinding
@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
        val name: String,
        val description: String,
        val myService: MyService) {

    data class MyService(
            val apiToken: String,
            val uri: URI
    )
}

要使用注释处理器生成你自己的元数据,[kapt应该配置](https://kotlinlang.org/DOCS/reference/kapt.html)与spring-boot-configuration-processor依赖关系。
注意,由于 Kapt 提供的模型中的限制,一些功能(例如检测默认值或不推荐项)不起作用。

# 10.6.测试

虽然可以使用 JUnit4 来测试 Kotlin 代码,但 JUnit5 是默认提供的,并且是推荐的。JUnit5 允许一个测试类被实例化一次,并在类的所有测试中重用。这使得在非静态方法上使用@BeforeAll@AfterAll注释成为可能,这非常适合 Kotlin。

要模拟 Kotlin 类,建议使用MockK (opens new window)。如果你需要Mockk特定于 mockito 的[@MockBean@SpyBean注释](#features.testing. Spring-boot-applications.mocking-beans)的等价物,则可以使用斯普林莫克 (opens new window),它提供类似的@MockkBean@SpykBean注释。

# 10.7.资源

# 10.7.1.进一步阅读

# 10.7.2.例子

# 11. 接下来要读什么?

如果你想了解更多关于本节中讨论的任何类的信息,请参见Spring Boot API documentation (opens new window),或者你也可以浏览源代码直接 (opens new window)。如果你有具体的问题,请参阅how-to部分。

如果你对 Spring Boot 的核心功能感到满意,那么你可以继续阅读可投入生产的功能