Creating Your Own Auto-configuration

如果你在一家开发共享库的公司工作,或者你在一个开源库或商业库上工作,你可能想要开发自己的自动化配置。自动化配置类可以捆绑在外部 jar 中,并且仍然会被 Spring Boot 提取。 自动化配置可以关联到 “starter”,既提供自动化配置代码,也提供你将与该代码一起使用的典型库。我们首先介绍生成自己的自动化配置所需的知识,然后继续介绍 typical steps required to create a custom starter

Understanding Auto-configured Beans

实现自动化配置的类用 @AutoConfiguration 进行注解。此注解本身用 @Configuration 进行元注解,使自动化配置成为标准 @Configuration 类。其他 @Conditional 注解用于约束自动化配置的应用时机。通常,自动化配置类使用 @ConditionalOnClass@ConditionalOnMissingBean 注解。这确保了仅在找到相关类且你未声明自己的 @Configuration 时才应用自动化配置。

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

Locating Auto-configuration Candidates

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

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

你可以使用 # 字符向导入文件添加注释。

自动化配置必须通过在导入文件中命名来 only 加载。确保它们在特定软件包空间中定义,并且它们永远不会成为组件扫描的目标。此外,自动化配置类不应启用组件扫描以查找其他组件。而应该使用特定的 @Import 注解。

如果需要按特定顺序应用你的配置,则可以在 @AutoConfiguration 注解或专用 @AutoConfigureBefore@AutoConfigureAfter 注解中使用 {code-spring-boot-autoconfigure-src}/AutoConfiguration.java 的 beforebeforeNameafterafterName 属性。例如,如果你提供特定于 Web 的配置,则可能需要在 WebMvcAutoConfiguration 之后应用你的类。

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

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

Condition Annotations

你几乎总想在自动化配置类上包含一个或多个 @Conditional 注解。@ConditionalOnMissingBean 注解是一个常见示例,用于允许开发人员在对默认值不满意时覆盖自动化配置。

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

Class Conditions

@ConditionalOnClass@ConditionalOnMissingClass 注释根据特定类的存在或不存在将 @Configuration 类包含在内。由于使用 ASM 解析注释元数据,因此您可以使用 value 属性引用实际类,即使该类可能实际上未出现在正在运行的应用程序类路径中。如果您更愿意使用 String 值指定类名,则还可以使用 name 属性。

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

要处理此场景,可以使用单独的 @Configuration 类来隔离条件,如下例所示:

如果你使用 @ConditionalOnClass@ConditionalOnMissingClass 作为元注释的一部分来编写自己的组合注释,则必须在这种情况中使用 name,因为它不处理所引用的类。

Bean Conditions

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

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

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

你需要非常仔细地考虑 bean 定义的添加顺序,因为这些条件是根据到目前为止处理的内容进行评估的。因此,我们建议仅对自动配置类使用 @ConditionalOnBean@ConditionalOnMissingBean 注释(因为在添加任何用户定义的 bean 定义之后,这些注释保证加载)。

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

在声明 @Bean 方法时,在方法的返回类型中尽可能提供更多类型信息。例如,如果你的 bean 的具体类实现了接口,则 bean 方法的返回类型应该是具体类而不是该接口。在 @Bean 方法中尽可能提供更多类型信息非常重要,因为它们的评估只能依赖于方法签名中可用的类型信息。

Property Conditions

@ConditionalOnProperty 注释允许基于 Spring Environment 属性包含配置。使用 prefixname 属性指定应检查的属性。默认情况下,将匹配任何存在且不等于 false 的属性。你还可以通过使用 havingValuematchIfMissing 属性创建更高级的检查。

Resource Conditions

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

Web Application Conditions

@ConditionalOnWebApplication@ConditionalOnNotWebApplication 注释允许根据应用程序是否是 Web 应用程序包含配置。基于 servlet 的 Web 应用程序是指使用 Spring WebApplicationContext、定义 session 作用域或有 ConfigurableWebEnvironment 的任何应用程序。反应式 Web 应用程序是指使用 ReactiveWebApplicationContext 或有 ConfigurableReactiveWebEnvironment 的任何应用程序。

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

SpEL Expression Conditions

@ConditionalOnExpression 注释允许根据 {url-spring-framework-docs}/core/expressions.html[SpEL 表达式] 的结果包含配置。

在表达式中引用 bean 将导致在上下文字符串处理非常早地初始化该 bean。因此,该 bean 将不符合进行后期处理(例如配置属性绑定)的条件,并且其状态可能不完整。

Testing your Auto-configuration

自动配置会受到许多因素的影响:用户配置(@Bean 定义和 Environment 自定)、条件评估(特定库的存在)等。具体来说,每个测试都应该创建一个明确定义的 ApplicationContext,它表示那些自定的组合。ApplicationContextRunner 提供了一个实现这一目标的好方法。

在原生映像中运行测试时,ApplicationContextRunner 不起作用。

ApplicationContextRunner`通常定义为测试类的一个字段,用于收集基础、通用配置。以下示例确保总是调用 `MyServiceAutoConfiguration

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

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

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

该 runner 还可用于显示 ConditionEvaluationReport。该报告可以在 `INFO`或 `DEBUG`级别打印。以下示例展示了如何使用 `ConditionEvaluationReportLoggingListener`在自动配置测试中打印报告。

Simulating a Web Context

如果您需要测试仅在 servlet 或响应式 web 应用程序上下文中运行的自动配置,请分别使用 WebApplicationContextRunnerReactiveWebApplicationContextRunner

Overriding the Classpath

还可以测试在运行时不存在特定类和/或包时会发生的情况。Spring Boot 随附一个 FilteredClassLoader,该 runner 可以轻松使用它。在以下示例中,我们断言如果 MyService 不存在,则自动配置将被正确实用禁用:

Creating Your Own Starter

一个典型的 Spring Boot starter 包含用于自动配置和自定义给定技术基础设施的代码,我们称之为“acme”。为了使其易于扩展,可以将专用命名空间中的一些配置键公开给环境。最后,提供一个“starter”依赖项,以帮助用户尽可能轻松地入门。

具体来说,自定义 starter 可以包含以下内容:

  • 包含“acme”自动配置代码的 `autoconfigure`模块。

  • `starter`模块向 `autoconfigure`模块以及“acme”和任何其他通常有用的依赖项提供依赖项。简而言之,添加 starter 应提供使用该库所需的一切。

这两个模块中的分离绝不是必需的。如果“acme”有几个类型、选项或可选特性,那么最好将自动配置分开,因为您可以清楚地表示某些特性是可选的。此外,您可以创建 starter 来说明对这些可选依赖项的观点。同时,其他人只能依赖 `autoconfigure`模块并用不同的观点制定自己的 starter。

如果自动配置相对简单且没有可选特性,则将 starter 中的两个模块合并肯定是一种选择。

Naming

您应确保为 starter 提供适当的命名空间。不要以 `spring-boot`开头模块名称,即使您使用不同的 Maven `groupId`也是如此。我们可能在将来为要在其中进行自动配置的事物提供官方支持。

一般而言,您应当以启动器命名合并模块。例如,假定您正在为“acme”创建启动器,并将自动配置模块命名为 acme-spring-boot,将启动器命名为 acme-spring-boot-starter。如果您只有一个模块将两者结合在一起,那么应将它命名为 acme-spring-boot-starter

Configuration keys

如果您的启动器提供配置键,请为它们使用唯一的命名空间。特别是,请勿将您的键包含在 Spring Boot 使用的命名空间中(例如 servermanagementspring`等)。如果您使用相同的命名空间,我们可能会将来以破坏您的模块的方式修改这些命名空间。一般而言,使用您自己的命名空间为所有键加上前缀(例如 `acme)。

确保通过为每个属性添加字段 javadoc 文档化配置键,如下面的示例所示:

您应该只将纯文本与 `@ConfigurationProperties`字段 Javadoc 一起使用,因为在将它们添加到 JSON 之前不会对其进行处理。

以下是在内部遵循的一些规则以确保描述一致:

  • 不要以“The”或“A”作为描述的开头。

  • 对于 `boolean`类型,请以“Whether”或“Enable”作为描述的开头。

  • 对于基于集合的类型,请以“Comma-separated list”作为描述的开头。

  • 使用 java.time.Duration`而不是 `long,并描述默认单位,如果它不同于毫秒,例如“如果不指定持续时间后缀,则将使用秒”。

  • 除非必须在运行时确定默认值,否则不要在描述中提供它。

确保 trigger meta-data generation,以使 IDE 帮助也适用于您的键。您可能需要查看生成的元数据 (META-INF/spring-configuration-metadata.json) 以确保您的键已正确记录。在兼容的 IDE 中使用您自己的启动器也是验证元数据质量的好主意。

The “autoconfigure” Module

autoconfigure`模块包含开始使用该库所需的一切。它还可能包含配置键定义(例如 `@ConfigurationProperties)和任何可用于进一步自定义组件初始化方式的回调接口。

您应将对该库的依赖项标记为可选的,以便可以更轻松地在项目中包含 `autoconfigure`模块。如果您这样做,则不会提供该库,并且默认情况下,Spring Boot 会进行备份。

Spring Boot 使用注释处理器在元数据文件 (META-INF/spring-autoconfigure-metadata.properties) 中收集自动配置的条件。如果该文件存在,则它将用于热切地过滤不匹配的自动配置,这将改善启动时间。

在使用 Maven 构建时,建议在包含自动配置的模块中添加以下依赖项:

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

如果您已在应用程序中直接定义自动配置,请确保配置 `spring-boot-maven-plugin`以防止 `repackage`目标向 uber jar 中添加依赖项:

<project>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.springframework.boot</groupId>
							<artifactId>spring-boot-autoconfigure-processor</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

对于 Gradle,应在 `annotationProcessor`配置中声明依赖项,如下面的示例所示:

dependencies {
	annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}

Starter Module

启动器实际上是一个空 jar。它的唯一目的是提供与该库协同工作的必要依赖项。您可以将其视为开始所需内容的一种观点。

请不要对添加启动程序的项目做出假设。如果您要自动配置的库通常需要其他启动程序,请也提及它们。如果可选依赖项的数量很大,则提供一组适当的 `@`1 依赖项可能很困难,因为您应该避免包含对于库的典型用途来说不必要的依赖项。换句话说,您不应该包含可选的依赖项。

无论哪种方式,您的启动程序都必须直接或间接引用核心 Spring Boot 启动程序 (`@`2)(如果您的启动程序依赖另一个启动程序,则无需添加它)。如果只创建了自定义启动程序的项目,Spring Boot 的核心功能将通过核心启动程序的存在得到尊重。