返回介绍

4.27 创建自己的自动配置

发布于 2021-03-17 13:07:52 字数 8504 浏览 959 评论 0 收藏 0

如果您在开发共享库的公司工作,或者您在开源或商业库中工作,则可能需要开发自己的自动配置。 自动配置类可以捆绑在外部jar中,仍然可以通过Spring Boot获取。

自动配置可以与“启动器”相关联,该“启动器”提供自动配置代码以及您将使用它的典型库。 我们首先介绍了构建自己的自动配置需要了解的内容,然后我们将继续介绍创建自定义启动器所需的典型步骤。

可以使用演示项目来展示如何逐步创建启动器。

4.27.1 了解自动配置的Bean

在幕后,使用标准的@Configuration类实现自动配置。 额外的@Conditional注释用于约束何时应用自动配置。 通常,自动配置类使用@ConditionalOnClass和@ConditionalOnMissingBean注释。 这可确保仅在找到相关类时以及未声明自己的@Configuration时才应用自动配置。

您可以浏览spring-boot-autoconfigure的源代码,以查看Spring提供的@Configuration类(请参阅META-INF/spring.factories文件)

4.27.2 匹配自动配置候选人

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

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

必须以这种方式加载自动配置。 确保它们在特定的包空间中定义,并且它们永远不是组件扫描的目标。 此外,自动配置类不应启用组件扫描以查找其他组件。 应该使用特定的@Import代替。

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

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

4.27.3 条件注解

您几乎总是希望在自动配置类中包含一个或多个@Conditional注释。 @ConditionalOnMissingBean注释是一个常见示例,用于允许开发人员如果对您的默认值不满意,则覆盖自动配置。

Spring Boot包含许多@Conditional注释,您可以通过注释@Configuration类或单独的@Bean方法在您自己的代码中重用它们。 这些注释包括:

  • Class条件
  • Bean条件
  • Property条件
  • Resource条件
  • Web Application条件
  • SpEL表达式条件

4.27.3.1 Class条件

@ConditionalOnClass和@ConditionalOnMissingClass注释允许根据特定类的存在与否来包含@Configuration类。 由于使用ASM解析注释元数据这一事实,您可以使用value属性来引用真实类,即使该类实际上可能不会出现在正在运行的应用程序类路径中。 如果您希望使用String值指定类名,也可以使用name属性。

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

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

@Configuration
// Some conditions
public class MyAutoConfiguration {

	// Auto-configured beans

	@Configuration
	@ConditionalOnClass(EmbeddedAcmeService.class)
	static class EmbeddedConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public EmbeddedAcmeService embeddedAcmeService() { ... }

	}

}

如果使用@ConditionalOnClass或@ConditionalOnMissingClass作为元注释的一部分来组成自己的组合注释,则必须使用name作为引用类,在这种情况下不处理。

4.27.3.2 Bean条件

@ConditionalOnBean和@ConditionalOnMissingBean注释允许根据特定bean的存在与否包含bean。可以使用value属性按类型指定bean或按名称指定bean。Search属性允许您限制在搜索bean时应该考虑的ApplicationContext层次结构。

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

@Configuration
public class MyAutoConfiguration {

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

}

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

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

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

4.27.3.3 Property条件

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

4.27.3.4 Resource条件

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

4.27.3.5 Web Application条件

@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注释允许包含配置,具体取决于应用程序是否为“Web应用程序”。 Web应用程序是使用Spring WebApplicationContext,定义会话范围或具有StandardServletEnvironment的任何应用程序。

4.26.3.6 SpEL表达式条件

@ConditionalOnExpression批注允许根据SpEL表达式的结果包含配置。

4.27.4 测试您的自动配置

自动配置可能受许多因素的影响:用户配置(@Bean定义和环境定制),条件评估(存在特定库)等。 具体而言,每个测试都应该创建一个定义良好的ApplicationContext,它表示这些自定义的组合。 ApplicationContextRunner提供了一种实现它的好方法。

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

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

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

每个测试都可以使用运行器来表示特定的用例。 例如,下面的示例调用用户配置(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");
	}

}

也可以轻松自定义环境,如以下示例所示:

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

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

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

4.27.4.1 模拟Web上下文

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

4.27.4.2 覆盖Classpath

还可以测试在运行时不存在特定类和/或包时发生的情况。 Spring Boot附带了一个可以由Runner轻松使用的FilteredClassLoader。 在以下示例中,我们声明如果UserService不存在,则会正确禁用自动配置:

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

4.27.5 创建您自己的Starter

库的完整Spring Boot启动程序可能包含以下组件:

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

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

4.27.5.1 命名

您应该确保为您的启动器提供适当的命名空间。 即使您使用其他Maven groupId,也不要使用spring-boot启动模块名称。 我们可能会为您将来自动配置的内容提供官方支持。

根据经验,您应该在启动器后命名组合模块。 例如,假设您正在为“acme”创建启动器,并且您将自动配置模块命名为acme-spring-boot-autoconfigure和starter acme-spring-boot-starter。 如果您只有一个组合两者的模块,请将其命名为acme-spring-boot-starter。

此外,如果您的启动器提供配置密钥,请为它们使用唯一的命名空间。 特别是,不要将您的密钥包含在Spring Boot使用的命名空间中(例如server, management, spring等)。 如果您使用相同的命名空间,我们将来可能会以破坏您的模块的方式修改这些命名空间。

确保触发元数据生成,以便为您的密钥提供IDE帮助。 您可能希望查看生成的元数据(META-INF/spring-configuration-metadata.json)以确保正确记录您的密钥。

4.27.5.2 自动配置模块

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

您应该将库的依赖项标记为可选,以便您可以更轻松地在项目中包含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"
}

4.27.5.3 Starter模块

起动器真的是一个空jar。 它的唯一目的是提供必要的依赖项来使用库。 您可以将其视为对入门所需内容的一种看法。

不要对添加启动器的项目做出假设。 如果您自动配置的库通常需要其他启动器,请同时提及它们。 如果可选依赖项的数量很高,则提供一组适当的默认依赖项可能很难,因为您应该避免包含对典型库的使用不必要的依赖项。 换句话说,您不应该包含可选的依赖项。

无论哪种方式,您的启动器必须直接或间接引用核心Spring Boot启动器(spring-boot-starter)(即如果您的启动器依赖于另一个启动器,则无需添加它)。 如果只使用自定义启动器创建项目,则Spring Boot的核心功能将通过核心启动器的存在来实现。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文