47. 创建你自己的自动配置

如果你在一个开发共享库的公司工作,或者你在一个开源的或商业的图书馆工作,你可能想开发自己的自动配置。自动配置类可以捆绑在外部的 jar 中,并且仍然由 Spring Boot 来拾取。

自动配置可以与提供自动配置代码的 “starter” 以及你将使用的典型库相关联。我们首先覆盖你需要知道的,以建立你自己的自动配置,然后我们转到创建自定义启动器所需的典型步骤。

[Tip] Tip

一个演示项目可以用来演示如何一步一步地创建启动器。

47.1 理解自动配置的 bean

在底层,自动配置是用标准 @Configuration 类实现的。附加的 @Conditional 注解用于约束自动配置何时应用。通常,自动配置类使用 @ConditionalOnClass 和 @ConditionalOnMissingBean 注解。这确保自动配置仅在找到相关类和未声明自己的 @Configuration 时才适用。

你可以浏览 spring-boot-autoconfigure 的源码,以查看 Spring 提供的 @Configuration 类(详见 META-INF/spring.factories 文件)。

47.2 指定自动配置候选

Spring Boot 检查是否存在发布的 jar 中的 META-INF/spring.factories。文件应该在 EnableAutoConfiguration 键下列出配置类,如下面的示例所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

如果你的配置需要以特定的顺序应用,你可以使用 @AutoConfigureAfter 或 @AutoConfigureBefore 注解。例如,如果你提供特定于 web 的配置,你的类可能需要在 WebMvcAutoConfiguration 之后应用。

如果你想指定某些不应该有任何直接知识的自动配置,你也可以使用 @AutoConfigureOrder。该注解具有与规则 @Order 相同的语义,但提供了自动配置类的专用命令。

[Note] Note

自动配置必须以这种方式加载。确保它们是在特定的封装空间中定义的,特别是,它们永远不是组件扫描的目标。

47.3 条件注解

你几乎总是希望在自动配置类上包含一个或多个 @Conditional 注解。@ConditionalOnMissingBean 注解是一个常见的示例,用于允许开发人员重写自动配置,如果它们不满意默认值。

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

  • 小节 47.3.1, “类条件”
  • 小节 47.3.2, “bean 条件”
  • 小节 47.3.3, “属性条件”
  • 小节 47.3.4, “资源条件”
  • 小节 47.3.5, “web 应用程序条件”
  • 小节 47.3.6, “SpEL 表达式条件”

47.3.1 类条件

@ConditionalOnClass 和 @ConditionalOnMissingClass 允许配置基于特定类的存在或不存在而包含类。由于注解元数据是通过使用 ASM 解析的,所以可以使用 value 属性来引用真实类,即使该类可能不实际出现在正在运行的应用程序类路径上。如果你喜欢使用 String 值指定类名,也可以使用 name 属性。

[Tip] Tip

如果使用 @ConditionalOnClass 或 @ConditionalOnMissingClass 作为元注解的一部分来组成自己的组合注解,则必须使用 name 作为此类中未涉及的类。

47.3.2 bean 条件

@ConditionalOnBean 和 @ConditionalOnMissingBean 注解允许基于特定 bean 的存在或不存在而包含 bean。你可以使用 value 属性通过类型来指定 bean,或通过 name 按名称来指定 bean。search 属性允许你限制在搜索 bean 时应考虑 ApplicationContext 层次结构。

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

@Configuration
public class MyAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public MyService myService() { ... }

}

在前面的示例中,如果 ApplicationContext 中没有包含 MyService 类型的 bean,myService bean 将被创建。

[Tip] Tip

你需要非常小心地添加 bean 定义的顺序,因为这些条件是基于迄今为止处理的内容来评估的。因此,我们建议在自动配置类中只使用 @ConditionalOnBean 和 @ConditionalOnMissingBean 注解(因为在添加了任何用户定义的 bean 定义之后,它们都会被载入)。

[Note] Note

@ConditionalOnBean 和 @ConditionalOnMissingBean 不会阻止 @Configuration 类被创建。在类级别使用这些条件等同于用注解每个包含的 @Bean 方法。

47.3.3 属性条件

@ConditionalOnProperty 注解允许配置被包含在基于 Spring 环境属性中。使用 prefix 和 name 属性指定应该检查的属性。默认情况下,匹配任何存在且不等于 false 的属性。你还可以通过使用 havingValue 和 matchIfMissing 属性来创建更高级的检查。

47.3.4 资源条件

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

47.3.5 web 应用程序条件

@ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication 注解允许配置被包含取决于应用程序是否是 “web application”。web 应用程序是使用 Spring WebApplicationContext,定义 session 范围或有一个 StandardServletEnvironment 的任何应用程序。

47.3.6 SpEL 表达式条件

基于SpEL表达式的结果,可以包含@条件语句表达式注释。 @ConditionalOnExpression 注解允许配置被包含,是基于 SpEL 表达式的结果。

47.4 测试你的自动配置

自动配置可以能受到多种因素影响:用户配置(@Bean 定义和 Environment 定制)、条件评估(存在特定库)等。具体地说,每个测试应该创建一个定义良好的 ApplicationContext,这些上下文表示这些定制的组合。ApplicationContextRunner 提供了一个很好的方法来实现这一目标。

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

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
		.withConfiguration(AutoConfigurations.of(UserServiceAutoConfiguration.class));
[Tip] Tip

如果必须定义多个自动配置,则不必按照与运行应用程序相同的顺序调用它们的声明。

每个测试都可以使用转轮来表示特定的用例。例如,下面的示例调用用户配置(UserConfiguration)并检查自动配置是否正确备份。调用 run 提供了一个回调上下文,可以与 Assert4J 一起使用。

@Test
public void defaultServiceBacksOff() {
	this.contextRunner.withUserConfiguration(UserConfiguration.class)
			.run((context) -> {
				assertThat(context).hasSingleBean(UserService.class);
				assertThat(context.getBean(UserService.class)).isSameAs(
						context.getBean(UserConfiguration.class).myUserService());
			});
}

@Configuration
static class UserConfiguration {

	@Bean
	public UserService myUserService() {
		return new UserService("mine");
	}

}

还可以容易地定制 Environment,如下面的示例所示:

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

47.4.1 模拟 web 上下文

如果你需要测试仅在 Servlet 或 Reactive web 应用程序上下文中运行的自动配置,则分别使用 WebApplicationContextRunner 或 ReactiveWebApplicationContextRunner。

47.4.2 重写类路径

还可以测试在运行时不存在特定类和/或包时发生的情况。Spring Boot 有一个可以被运行者轻松使用的 FilteredClassLoader。在下面的示例中,我们断言,如果 UserService 不存在,则自动配置被禁用:

@Test
public void serviceIsIgnoredIfLibraryIsNotPresent() {
	this.contextRunner.withClassLoader(new FilteredClassLoader(UserService.class))
			.run((context) -> assertThat(context).doesNotHaveBean("userService"));
}

47.5 创建你自己的启动器

一个用于库的完整 Spring Boot 启动器可以包含以下组件:

  • 包含自动配置代码的 autoconfigure 模块。
  • starter 模块,它提供对autoconfigure 模块以及库和任何通常有用的附加依赖项。简而言之,添加启动器应该提供开始使用该库所需的一切。
[Tip] Tip

如果不需要将这两个问题分开,可以将自动配置代码和依赖性管理组合在一个模块中。

47.5.1 命名

你应该确保为你的启动器提供一个合适的命名空间。即使使用不同的 Maven groupId,也不要以 spring-boot 开始你的模块名称。我们可以为你将来自动配置的东西提供官方支持。

作为经验法则,你应该在启动器之后命名组合模块。例如,假设你正在为 "acme" 创建一个启动器,并且将自动配置模块命名为 acme-spring-boot-autoconfigure 和 启动器 acme-spring-boot-starter。如果只有一个模块组合了这两个模块,则命名为 acme-spring-boot-starter。

此外,如果启动器提供了配置键,则为它们使用唯一的命名空间。特别地,不要在 Spring Boot 使用的命名空间中包括你的键(例如 server、management、spring 等等)。如果使用相同的命名空间,我们可以在将来以打破模块的方式修改这些命名空间。

确保触发元数据生成,以便 IDE 辅助也可用于你的键。你可能需要查看生成的元数据(META-INF/spring-configuration-metadata.json),以确保你的键被正确记录。

47.5.2 autoconfigure 模块

autoconfigure 模块包含启动库所必需的所有内容。它还可以包含配置键定义(例如 @ConfigurationProperties)和任何可用于进一步定制组件初始化的回调接口。

[Tip] Tip

你应该将这些依赖项标记为库,以便可以更容易地将 autoconfigure 模块包含在项目中,如果你这样,则不提供库,默认情况下,Spring Boot 退回。

Spring Boot 使用注解处理器来收集元数据文件汇总的自动配置条件(META-INF/spring-autoconfigure-metadata.properties)。如果存在该文件,则用于急于筛选不匹配的自动配置,这将提高启动时间。建议在包含自动配置的模块中添加以下依赖项:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-autoconfigure-processor</artifactId>
	<optional>true</optional>
</dependency>

使用 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"
}

47.5.3 启动器模块

启动器是一个空 jar。它的唯一目的是提供与库一起工作的必要依赖,你可以把它看作是启动必需的自用视图。

不要假设你的启动器添加的项目。如果你自动配置的库通常需要其它启动器,那么也要提及它们。如果可选依赖项的数量很高,则提供适当的默认依赖集可能会很困难,因为你应该避免对常用的库包含不必要的依赖项。换句话说,不应该包含可选的依赖项。

[Note] Note

无论哪种方式,你的启动器必须直接或简介地引用 Spring Boot 启动器(spring-boot-starter)(即,如果你的启动器依赖另一个启动器,则不需添加)。如果项目仅由自定义启动器创建,则 Spring Boot 的核心特性将受到核心启动器的支持。

48. Kotlin 支持

Kotlin 是面向 JVM(和其它平台)的静态类型语言,它允许编写简洁优雅的代码,同时提供与 Java 编写的现有库的互操作性。

Spring Boot 通过支持其它 Spring 项目(如 Spring Framework、Spring Data 和 Reactor)提供 Kotlin 支持。有关的更多详细信息请参阅 Spring Framework Kotlin 支持文档。

从 Spring Boot 和 Kotlin 开始的最简单的方法是遵循这个全面的教程。你可以通过 start.spring.io 创建新的 Kotlin 项目。如果你需要支持,可以自由加入 Kotlin Slack 的 #spring 通道,或者在 Stack Overflow 上使用 spring 和 kotlin 标签进行提问。

48.1 必要条件

Spring Boot 支持 Kotlin 1.2.x。为了使用 Kotlin,org.jetbrains.kotlin:kotlin-stdlib 和 org.jetbrains.kotlin:kotlin-reflect 必须在类路径上。也可以使用 kotlin-stdlib 变体 kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8。

由于默认情况下 Kotlin 类是 final,所以你可能希望配置 kotlin-spring 插件,以便自动打开 Spring 注解类,以便它们可以代理。

Jackson 的 Kotlin 模块是在 Kotlin 中序列化/反序列化 JSON 数据所必需的。它在类路径上找到时自动注册。如果存在 Jackson 和 Kotlin,但 Jackson Kotlin 不存在,则记录警告消息。

[Tip] Tip

如果在 start.spring.io 上引导 Kotlin 项目,默认情况下将提供这些依赖项和插件。

48.2 Null 安全性

Kotlin 的一个关键特征是 null-safety。它在编译时处理 null 值,而不是将问题推迟到运行时并遇到 NullPointerException。这有助于消除常见的 bug 来源,而不必付出包装之类的成本。Kotlin 还允许使用具有可空值的功能结构,如 Kotlin 的 null 安全性综合指南中所描述的。

虽然 Java 不允许在其它类型系统中表达 null 安全性,但是 Spring Framework、Spring Data 和 Reactor 现在通过工具友好注解提供 API 的 null 安全性。默认情况下,Kotlin 中使用的 Java API 类型被认为是宽松 null 检查的平台类型。Kotlin 支持 JSR 305 注解与可空性注解相结合为 Kotlin 中的相关 Spring API 提供了 null 安全性。

JSR 305 检查可以通过添加 -Xjsr305 编译器标志来配置,具有以下选项:-Xjsr305={strict|warn|ignore}。默认行为与 -Xjsr305=warn 相同。从 Spring API 推断的 Kotlin 类型的 strict 值需要考虑到 null 安全性,但需要知道的是 Spring API 可空性声明即使在次要版本之间也可能演化,斌在将来添加更多的检查。

警告:泛型类型参数、varargs 和数组元素可空性尚未得到支持。参见 SPR-15942 的最新信息。还要注意,Spring Boot 自身的 API 还没有注解。

48.3 Kotlin API

48.3.1 runApplication

Spring Boot 提供了一个用 runApplication<MyApplication>(*args) 运行应用程序的惯用方法,如下面的示例所示:

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

@SpringBootApplication
class MyApplication

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

这是 SpringApplication.run(MyApplication::class.java, *args) 替换的一个降级。它还允许定制应用程序,如下面的示例所示:

runApplication<MyApplication>(*args) {
	setBannerMode(OFF)
}

48.3.2 扩展

Kotlin 扩展提供了用附加功能扩展现有类的能力。Spring Boot Kotlin API 利用这些扩展来向现有 API 添加新的 Kotlin 特定的便利。

TestRestTemplate 扩展,类似于 Spring Framework 为 Spring Framework 中的 RestOperations 提供的那些扩展。除此之外,扩展可以利用 Kotlin 归一化类型参数。

48.4 依赖管理

为了避免将不同版本的 Kotlin 依赖性混合到类路径上,提供了以下 Kotlin 依赖关系的依赖管理:

  • kotlin-reflect
  • kotlin-runtime
  • kotlin-stdlib
  • kotlin-stdlib-jdk7
  • kotlin-stdlib-jdk8
  • kotlin-stdlib-jre7
  • kotlin-stdlib-jre8

使用 Maven,Kotlin 版本可以通过 kotlin.version 属性和 kotlin-maven-plugin 提供的插件管理来进行定制。使用 Gradle,Spring Boot 插件自动将 kotlin.version 和 Kotlin 插件的版本对齐。

48.5 @ConfigurationProperties

@ConfigurationProperties 目前只与 lateinit 或可空 var 属性(前者是推荐的)一起工作,因为尚未支持由构造函数初始化的不可变类。

@ConfigurationProperties("example.kotlin")
class KotlinExampleProperties {

	lateinit var name: String

	lateinit var description: String

	val myService = MyService()

	class MyService {

		lateinit var apiToken: String

		lateinit var uri: URI

	}

}
[Tip] Tip

要使用注释处理器生成自己的元数据,kapt 应该配置为 spring-boot-configuration-processor 依赖项。

48.6 测试

虽然可以使用 JUnit 4(spring-boot-starter-test 提供的缺省值)来测试 Kotlin 代码,但建议 JUnit 5。JUnit 5 允许一个测试类被实例化一次,并用于所有类的测试。这使得可以在非静态方法上使用 @BeforeAll 和 @AfterAll 注解,这是适合 Kotlin 的。

若要使用 JUnit 5,请从 spring-boot-starter-test 中排除 junit:junit 依赖,添加 JUnit 5 依赖项,并相应地配置 Maven 或 Gradle 插件。有关的更多细节请参阅 JUnit 5 文档。你还需要将测试实例的生命周期切换到“每个类”。

48.7 资源

48.7.1 进一步阅读

  • Kotlin 语言参考
  • Kotlin Slack (使用专用的 #spring 通道)
  • Stackoverflow 上使用 spring 和 kotlin 标签
  • 浏览器中试用 Kotlin
  • Kotlin 博客
  • Awesome Kotlin
  • 教程: 使用 Spring Boot 和 Kotlin 构建 web 应用程序
  • 使用 Kotlin 开发 Spring Boot 应用程序
  • 使用 Kotlin、Spring Boot 和 PostgreSQL 的一个空间消息
  • Spring Framework 5.0 中引入的 Kotlin 支持
  • Spring Framework 5 Kotlin API, 函数式方式

48.7.2 例子

  • spring-boot-kotlin-demo: regular Spring Boot + Spring Data JPA project
  • mixit: Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB
  • spring-kotlin-fullstack: WebFlux Kotlin fullstack example with Kotlin2js for frontend instead of JavaScript or TypeScript
  • spring-petclinic-kotlin: Kotlin version of the Spring PetClinic Sample Application
  • spring-kotlin-deepdive: a step by step migration for Boot 1.0 + Java to Boot 2.0 + Kotlin

49. 后续内容

如果你想了解更多有关本节中讨论的类的内容,可以查看 Spring Boot API 文档,或者可以直接浏览源代码。如果你有具体的问题,请看 how-to 部分。

如果你对 Spring Boot 的核心特性感到满意,你可以继续并阅读关于生产就绪的特性。