创建自己的自动配置

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

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

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

了解自动配置的Bean

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

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

找到自动配置候选者

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

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

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

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

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

条件注释

您几乎总是希望@Conditional在自动配置类中包含一个或多个注释。该@ConditionalOnMissingBean注释是用来让开发者重写自动配置,如果他们不满意自己的缺省值一个常见的例子。

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

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

Class Conditions

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

此机制不适用于@Bean通常返回类型是条件的目标的方法:在方法的条件适用之前,JVM将加载类和可能处理的方法引用,如果类不是当下。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
//一些条件
public class MyAutoConfiguration {

//自动配置的bean

@Configuration
@ConditionalOnClass(EmbeddedAcmeService.class)
static class EmbeddedConfiguration {
@Bean
@ConditionalOnMissingBean
public EmbeddedAcmeService embeddedAcmeService() { ... }
}
}

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

Bean Conditions

@ConditionalOnBean@ConditionalOnMissingBean注解让豆基于特定豆的存在或不存在被包括在内。您可以使用该value 属性按类型name指定bean 或按名称指定bean。该search 属性允许您限制ApplicationContext搜索Bean时应考虑的层次结构。

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

1
2
3
4
5
6
@Configuration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() { ... }
}

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

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

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

Property Conditions

@ConditionalOnProperty注解让配置基于Spring的环境属性被包括在内。使用prefixname属性指定应检查的属性。默认情况下,false匹配存在且不相等的任何属性。您还可以使用havingValuematchIfMissing属性创建更高级的检查。

Resource Conditions

@ConditionalOnResource注解让配置被包括仅当特定资源是否存在。可以使用常用的Spring约定来指定资源,如以下示例所示:

1
file:/home/user/test.dat

Web Application Conditions

@ConditionalOnWebApplication@ConditionalOnNotWebApplication注释,让配置取决于应用程序是否是一个“Web应用程序”被包括在内。Web应用程序是使用Spring WebApplicationContext,定义session范围或具有的任何应用程序StandardServletEnvironment

spEL Conditions

@ConditionalOnExpression注解让配置基于一个的结果被包括使用SpEL表达

测试自动配置

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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,如下例所示:

1
2
3
4
5
6
7
@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");
});
}

Runner也可以用来显示ConditionEvaluationReport。报告可以打印INFODEBUG级别打印。以下示例显示如何使用ConditionEvaluationReportLoggingListener 在自动配置测试中打印报表。

1
2
3
4
5
6
7
8
9
@Test
public void autoConfigTest {
ConditionEvaluationReportLoggingListener initializer =
new ConditionEvaluationReportLoggingListener(LogLevel.INFO);
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(initializer).run((context) -> {
// Do something...
});
}

模拟Web上下文

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

覆盖Classpath

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

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

创建自己的启动器

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

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

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

命名

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

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

此外,如果您的启动器提供配置密钥,请为它们使用唯一的命名空间。特别是,不包括你在春天开机使用的命名空间键(如 servermanagementspring,等)。如果您使用相同的命名空间,我们将来可能会以破坏您的模块的方式修改这些命名空间。

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

autoconfigure模块

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

您应该将库的依赖项标记为可选,以便您可以autoconfigure更轻松地将模块包含在项目中。如果以这种方式执行,则不提供库,默认情况下,Spring Boot会退出。

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

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>

对于Gradle 4.5及更早版本,应在compileOnly 配置中声明依赖项,如以下示例所示:

1
2
3
dependencies {
compileOnly "org.springframework.boot:spring-boot-autoconfigure-processor"
}

使用Gradle 4.6及更高版本时,应在annotationProcessor 配置中声明依赖项,如以下示例所示:

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

启动器模块

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

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

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