49. 创建自己的自动配置
如果您在开发共享库的公司中工作,或者在开源或商业库中工作,则可能需要开发自己的自动配置。自动配置类可以 Binding 在外部 jar 中,并且仍由 Spring Boot 拾取。
自动配置可以与“启动器”相关联,该“启动器”提供自动配置代码以及您将使用的典型库。我们首先介绍构建自己的自动配置所需的知识,然后 continue 进行创建自定义启动器所需的典型步骤。
Tip
demo project可用来展示如何逐步创建 Starter 者。
49.1 了解自动配置的 Bean
在后台,自动配置是通过标准的@Configuration
类实现的。其他@Conditional
Comments 用于约束何时应应用自动配置。通常,自动配置类使用@ConditionalOnClass
和@ConditionalOnMissingBean
Comments。这样可以确保仅当找到相关的类并且没有声明自己的@Configuration
时,自动配置才适用。
您可以浏览spring-boot-autoconfigure的源代码以查看 Spring 提供的@Configuration
类(请参见META-INF/spring.factories文件)。
49.2 查找自动配置的候选人
Spring Boot 检查发布的 jar 中是否存在META-INF/spring.factories
文件。该文件应在EnableAutoConfiguration
键下列出您的配置类,如以下示例所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
如果需要按特定 Sequences 应用配置,则可以使用@AutoConfigureAfter或@AutoConfigureBefore注解。例如,如果您提供特定于 Web 的配置,则可能需要在WebMvcAutoConfiguration
之后应用您的类。
如果要 Order 某些彼此之间没有直接知识的自动配置,则也可以使用@AutoConfigureOrder
。该 Comments 与常规@Order
Comments 具有相同的语义,但为自动配置类提供了专用 Sequences。
Note
自动配置必须以* only *的方式加载。确保在特定的软件包空间中定义了它们,尤其是它们绝不是组件扫描的目标。
49.3 条件 Comments
您几乎总是希望在自动配置类中包含一个或多个@Conditional
Comments。 @ConditionalOnMissingBean
Comments 是一个常见示例,用于使开发人员在对默认设置不满意的情况下覆盖自动配置。
Spring Boot 包含许多@Conditional
Comments,您可以通过 Comments@Configuration
类或单个@Bean
方法在自己的代码中重用。这些 Comments 包括:
49.3.1 Class 条件
@ConditionalOnClass
和@ConditionalOnMissingClass
注解允许根据是否存在特定类来包含配置。由于 Comments 元数据是通过使用ASM进行解析的,因此即使该类实际上可能未出现在正在运行的应用程序 Classpath 上,您也可以使用value
属性来引用真实的类。如果您希望通过使用String
值来指定类名,则也可以使用name
属性。
Tip
如果您使用@ConditionalOnClass
或@ConditionalOnMissingClass
作为元 Comments 的一部分来组成自己的组合 Comments,则必须使用name
,因为在这种情况下无法引用该类。
49.3.2 Bean 条件
@ConditionalOnBean
和@ConditionalOnMissingBean
Comments 根据是否存在特定的 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
您需要非常注意添加 bean 定义的 Sequences,因为这些条件是根据到目前为止已处理的内容来评估的。因此,我们建议在自动配置类上仅使用@ConditionalOnBean
和@ConditionalOnMissingBean
Comments(因为保证在添加任何用户定义的 Bean 定义后即可加载这些 Comments)。
Note
@ConditionalOnBean
和@ConditionalOnMissingBean
不会阻止@Configuration
类的创建。在类级别使用这些条件与使用 Comments 标记每个包含的@Bean
方法之间的唯一区别是,如果条件不匹配,则前者会阻止@Configuration
类注册为 bean。
49.3.3 财产条件
@ConditionalOnProperty
Comments 允许基于 Spring Environment 属性包含配置。使用prefix
和name
属性来指定应检查的属性。默认情况下,匹配存在且不等于false
的任何属性。您还可以使用havingValue
和matchIfMissing
属性创建更高级的检查。
49.3.4 资源条件
@ConditionalOnResource
Comments 仅在存在特定资源时才包括配置。可以使用通常的 Spring 约定来指定资源,如以下示例所示:file:/home/user/test.dat
。
49.3.5 Web 应用程序条件
根据应用程序是否为“ Web 应用程序”,可以使用@ConditionalOnWebApplication
和@ConditionalOnNotWebApplication
注解包含配置。 Web 应用程序是使用 Spring WebApplicationContext
,定义session
范围或具有StandardServletEnvironment
的任何应用程序。
49.3.6 SpEL 表达条件
@ConditionalOnExpression
注解允许基于SpEL expression的结果包括配置。
49.4 测试您的自动配置
自动配置可能受许多因素影响:用户配置(@Bean
定义和Environment
定制),条件评估(特定库的存在)和其他因素。具体而言,每个测试都应创建定义良好的ApplicationContext
,以表示这些自定义项的组合。 ApplicationContextRunner
提供了一种实现此目标的好方法。
ApplicationContextRunner
通常定义为测试类的一个字段,用于收集基本的通用配置。下面的示例确保始终调用UserServiceAutoConfiguration
:
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(UserServiceAutoConfiguration.class));
Tip
如果必须定义多个自动配置,则无需按与运行应用程序时完全相同的 Sequences 调用它们的声明。
每个测试都可以使用运行器来表示特定的用例。例如,下面的示例调用一个用户配置(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");
});
}
Running 者也可以用来显示ConditionEvaluationReport
。该报告可以INFO
或DEBUG
级别打印。以下示例显示如何在自动配置测试中使用ConditionEvaluationReportLoggingListener
打印报告。
@Test
public void autoConfigTest {
ConditionEvaluationReportLoggingListener initializer = new ConditionEvaluationReportLoggingListener(
LogLevel.INFO);
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(initializer).run((context) -> {
// Do something...
});
}
49.4.1 模拟网络环境
如果您需要测试仅在 Servlet 或 Reactive Web 应用程序上下文中运行的自动配置,请分别使用WebApplicationContextRunner
或ReactiveWebApplicationContextRunner
。
49.4.2 覆盖 Classpath
还可以测试在运行时不存在特定的类和/或程序包时发生的情况。 Spring Boot 附带FilteredClassLoader
,Running 者可以轻松使用。在以下示例中,我们 assert 如果不存在UserService
,则会自动禁用自动配置:
@Test
public void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(UserService.class))
.run((context) -> assertThat(context).doesNotHaveBean("userService"));
}
49.5 创建自己的启动器
库的完整 Spring BootStarter 程序可能包含以下组件:
-
包含自动配置代码的
autoconfigure
模块。 -
starter
模块提供对autoconfigure
模块以及库的依赖关系,以及通常有用的任何其他依赖关系。简而言之,添加启动程序应提供开始使用该库所需的一切。
Tip
如果不需要将这两个问题分开,则可以将自动配置代码和依赖性 Management 结合在一起。
49.5.1 Naming
您应该确保为启动器提供适当的名称空间。即使您使用其他 Maven groupId
,也不要以spring-boot
开头模块名称。将来,我们可能会为您自动配置的内容提供官方支持。
根据经验,您应该在启动器后命名一个组合模块。例如,假设您要为“ acme”创建启动程序,并且将自动配置模块命名为acme-spring-boot-autoconfigure
,而启动程序则命名为acme-spring-boot-starter
。如果只有一个将两者结合的模块,请将其命名为acme-spring-boot-starter
。
另外,如果您的 Starter 者提供了配置密钥,请为其使用唯一的名称空间。特别是,不要在 Spring Boot 使用的名称空间中包含您的密钥(例如server
,management
,spring
等)。如果使用相同的名称空间,将来我们可能会以破坏模块的方式修改这些名称空间。
确保触发元数据生成,以便您的按键也可以使用 IDE 协助。您可能需要查看生成的元数据(META-INF/spring-configuration-metadata.json
),以确保正确记录了您的密钥。
49.5.2 自动配置模块
autoconfigure
模块包含开始使用该库所需的所有内容。它还可能包含配置键定义(例如@ConfigurationProperties
)和可用于进一步自定义组件初始化方式的任何回调接口。
Tip
您应该将对库的依赖关系标记为可选,以便可以更轻松地在项目中包含autoconfigure
模块。如果这样做,则不提供该库,并且默认情况下,Spring Boot 会后退。
Spring Boot 使用 Comments 处理器来收集元数据文件(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"
}
49.5.3 Starter 模块
起动器确实是一个空Jar子。其唯一目的是提供必要的依赖关系以使用库。您可以将其视为对 Starter 所需的看法。
不要对添加了启动器的项目做任何假设。如果您要自动配置的库通常需要其他启动器,请同时提及它们。如果可选依赖项的数量很高,则提供一组适当的* default *依赖项可能会很困难,因为您应避免包括对于库的典型用法而言不必要的依赖项。换句话说,您不应包括可选的依赖项。
Note
无论哪种方式,您的启动程序都必须直接或间接引用核心 Spring Boot 启动程序(spring-boot-starter
)(即,如果您的启动程序依赖于另一个启动程序,则无需添加它)。如果仅使用您的自定义启动器创建项目,则通过使用该核心启动器来兑现 Spring Boot 的核心功能。