29.创建自己的自动配置
如果您在开发共享库的公司中工作,或者在开源或商业库中工作,则可能需要开发自己的自动配置。 自动配置类可以捆绑在外部jar中,并且仍由Spring Boot拾取。
不要对添加了启动器的项目做出假设。 如果您要自动配置的库通常需要其他启动器,请同时提及它们。 如果可选依赖项数量很多,那么提供一组适当的默认依赖项可能会很困难,因为您应避免包括对于库的典型用法而言不必要的依赖项。 换句话说,您不应包括可选的依赖项。
无论哪种方式,您的启动程序都必须直接或间接引用核心Spring Boot启动程序(spring-boot-starter)(即,如果您的启动程序依赖于另一个启动程序,则无需添加它)。 如果仅使用您的自定义启动器创建了一个项目,则该核心启动器的存在将兑现Spring Boot的核心功能。
29.1。 了解自动配置的Bean
在后台,自动配置是通过标准@Configuration类实现的。 其他@Conditional批注用于约束何时应应用自动配置。 通常,自动配置类使用@ConditionalOnClass和@ConditionalOnMissingBean批注。 这样可以确保仅当找到相关的类并且没有声明自己的@Configuration时,才应用自动配置。
您可以浏览spring-boot-autoconfigure的源代码以查看Spring提供的@Configuration类(请参阅META-INF / spring.factories文件)。
29.2。 查找自动配置候选人
Spring Boot检查发布的jar中是否存在META-INF / spring.factories文件。 该文件应在EnableAutoConfiguration键下列出您的配置类,如以下示例所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
自动配置只能以这种方式加载。 确保在特定的程序包空间中定义它们,并且决不要将它们作为组件扫描的目标。 此外,自动配置类不应启用组件扫描以查找其他组件。 应使用特定的@Imports代替。
如果需要按特定顺序应用配置,可以使用@AutoConfigureAfter或@AutoConfigureBefore注释。例如,如果您提供特定于web的配置,您的类可能需要在WebMvcAutoConfiguration之后应用。
如果您想排序某些不应该相互有任何直接联系的自动配置,您也可以使用@AutoConfigureOrder。该注释与常规的@Order注释具有相同的语义,但为自动配置类提供了专用的顺序。
与标准的@Configuration类一样,应用自动配置类的顺序只影响其bean的定义顺序。这些bean随后创建的顺序不受影响,由每个bean的依赖关系和任何@DependsOn关系决定。
29.3。 Condition 注释
您几乎总是希望在自动配置类中包含一个或多个@Conditional批注。 @ConditionalOnMissingBean注释是一个常见示例,用于使开发人员在对默认设置不满意的情况下覆盖自动配置。
Spring Boot包含许多@Conditional注释,您可以通过注释@Configuration类或单独的@Bean方法在自己的代码中重用这些注释。这些注释包括
Class Conditions
Bean Conditions
Property Conditions
Resource Conditions
Web Application Conditions
SpEL Expression Conditions
29.3.1。 Class Conditions
@ConditionalOnClass和@ConditionalOnMissingClass注释允许根据特定类的存在或不存在来包含@Configuration类。由于注释元数据是通过ASM解析的,所以您可以使用value属性来引用真实的类,即使该类实际上可能不会出现在运行的应用程序类路径上。如果希望通过字符串值指定类名,也可以使用name属性。
这种机制并不以同样的方式应用于@Bean方法,而@Bean方法的返回类型通常是condition的目标:在condition生效之前,JVM将加载类并可能处理方法引用,如果不存在类,这些引用将失败。
要处理这个场景,可以使用一个单独的@Configuration类来隔离条件,如下面的示例所示
@Configuration(proxyBeanMethods = false)
// Some conditions
public class MyAutoConfiguration {
// Auto-configured beans
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EmbeddedAcmeService.class)
static class EmbeddedConfiguration {
@Bean
@ConditionalOnMissingBean
public EmbeddedAcmeService embeddedAcmeService() { ... }
}
}
如果您使用@ConditionalOnClass或@ConditionalOnMissingClass作为元注释的一部分来组成自己的组合注释,那么在这种情况下,您必须使用name来引用类。
29.3.2。 Bean Conditions
@ConditionalOnBean和@ConditionalOnMissingBean注释允许根据特定bean的存在或不存在来包含一个bean。您可以使用value属性按类型指定bean,或使用名称指定bean。search属性允许您限制搜索bean时应该考虑的ApplicationContext层次结构。
当放置在@Bean方法上时,目标类型默认为该方法的返回类型,如下面的示例所示
@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() { ... }
}
在前面的示例中,如果ApplicationContext中还没有包含myService类型的bean,那么将创建myService bean。
您需要非常小心添加bean定义的顺序,因为这些条件是根据到目前为止处理的内容来评估的。出于这个原因,我们建议在自动配置类上只使用@ConditionalOnBean和@ConditionalOnMissingBean注释(因为它们保证在添加任何用户定义的bean定义之后才会加载)。
@ConditionalOnBean和@ConditionalOnMissingBean并不阻止创建@Configuration类。在类级别上使用这些条件与用注释标记每个包含的@Bean方法之间的唯一区别是,前者防止在条件不匹配时将@Configuration类注册为bean。
29.3.3。 Property Conditions
@ConditionalOnProperty注释允许根据Spring环境属性包含配置。使用前缀和名称属性来指定应该检查的属性。默认情况下,任何存在且不等于false的属性都被匹配。还可以使用havingValue和matchIfMissing属性创建更高级的检查。
29.3.4。 Web Application Conditions
@ConditionalOnResource批注仅在存在特定资源时才包括配置。 可以使用通常的Spring约定来指定资源,如以下示例所示:file:/home/user/test.dat。
29.3.5。 Web Application Conditions
@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注释允许根据应用程序是否是web应用程序包含配置。基于servlet的web应用程序是使用Spring WebApplicationContext、定义会话范围或具有ConfigurableWebEnvironment的任何应用程序。反应性web应用程序是任何使用ReactiveWebApplicationContext或具有可配置的ereactivewebenvironment的应用程序。
@ConditionalOnWarDeployment注释允许根据应用程序是否是部署到容器上的传统WAR应用程序来包含配置。此条件与使用嵌入式服务器运行的应用程序不匹配。
29.3.6。SpEL Conditions 表达
@ConditionalOnExpression注释允许根据SpEL表达式的结果包含配置。
29.4. Testing your Auto-configuration
自动配置可能受到许多因素的影响:用户配置(@Bean定义和环境定制)、条件评估(特定库的存在),以及其他因素。具体地说,每个测试都应该创建一个定义良好的ApplicationContext,它代表了这些定制的组合。ApplicationContextRunner提供了一个很好的方法来实现这个。
ApplicationContextRunner通常被定义为测试类的一个字段,以收集基本的公共配置。下面的示例确保始终调用UserServiceAutoConfiguration
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(UserServiceAutoConfiguration.class));
如果必须定义多个自动配置,则不需要按照与运行应用程序时完全相同的顺序来调用它们的声明。
每个测试都可以使用运行器来代表一个特定的用例。例如,下面的示例调用一个用户配置(UserConfiguration)并检查自动配置是否正确返回。调用run提供了一个可与AssertJ一起使用的回调上下文。
@Test
void defaultServiceBacksOff() {
this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(UserService.class);
assertThat(context).getBean("myUserService").isSameAs(context.getBean(UserService.class));
});
}
@Configuration(proxyBeanMethods = false)
static class UserConfiguration {
@Bean
UserService myUserService() {
return new UserService("mine");
}
}
也可以轻松自定义 Environment,如以下示例所示:
@Test
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。可以在INFO级别或调试级别打印报表。下面的示例演示如何使用ConditionEvaluationReportLoggingListener在自动配置测试中打印报告。
@Test
public void autoConfigTest {
ConditionEvaluationReportLoggingListener initializer = new ConditionEvaluationReportLoggingListener(
LogLevel.INFO);
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer(initializer).run((context) -> {
// Do something...
});
}
29.4.1。 模拟Web上下文
如果您需要测试一个只在Servlet或响应性web应用程序上下文中运行的自动配置,则分别使用WebApplicationContextRunner或ReactiveWebApplicationContextRunner。
29.4.2。 覆盖类路径
还可以测试在运行时不存在特定的类和/或程序包时发生的情况。 Spring Boot附带有FilteredClassLoader,runner可以轻松使用。 在下面的示例中,我们断言,如果不存在UserService,则会自动禁用自动配置:
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(UserService.class))
.run((context) -> assertThat(context).doesNotHaveBean("userService"));
}
29.5。 创建自己的starter
典型的 Spring Boot starter含用于自动配置和自定义特定技术的基础架构,我们将其称为“acme”。为了使其易于扩展,可以将专用名称空间中的许多configuration keys公开给环境。最后,提供了一个单一的“启动器”依赖项来帮助用户尽可能轻松地启动。
具体而言,一个自定义 starter 可以包含以下内容:
包含“acme”的自动配置代码的autoconfigure模块。
starter模块提供对autoconfigure模块以及“acme”的依赖关系,以及通常有用的任何其他依赖关系。简而言之,添加启动器应该能够提供开始使用该库所需的一切。
在两个模块中分离是没有必要的。如果“acme”有几种特点、选项或可选特性,那么最好将自动配置分离开来,因为您可以清楚地表示某些特性是可选的这一事实。此外,您还可以编写一个启动程序,提供关于这些可选依赖项的意见。与此同时,其他人只能依靠autoconfigure模块,以不同的观点打造自己的starter。
如果自动配置相对简单并且不具有可选功能,则将两个模块合并在启动器中绝对是一种选择。
29.5.1。 Naming
您应该确保为启动器提供一个适当的名称空间。即使使用不同的Maven groupId,也不要使用spring-boot模块名。我们可能会为你将来自动配置的东西提供官方支持。
根据经验,应该在启动器之后命名组合模块。例如,假设您正在为“acme”创建一个启动器,并将自动配置模块命名为acme-spring-boot,将启动器命名为acme-spring-boot-starter。如果只有一个模块组合了这两个模块,则将其命名为acme-spring-boot-starter。
29.5.2。 Configuration keys(配置键)
如果您的启动器提供了配置键,则为它们使用唯一的名称空间。特别是,不要在Spring引导使用的名称空间(如server、management、Spring等)中包含您的键。如果您使用相同的名称空间,我们可能在将来以破坏您的模块的方式修改这些名称空间。作为经验法则,使用您拥有的名称空间作为所有键的前缀(例如acme)。
通过为每个属性添加字段javadoc来确保记录了配置键,如以下示例所示:
@ConfigurationProperties("acme")
public class AcmeProperties {
/**
* Whether to check the location of acme resources.
*/
private boolean checkLocation = true;
/**
* Timeout for establishing a connection to the acme server.
*/
private Duration loginTimeout = Duration.ofSeconds(3);
// getters & setters
}
您应该只对@ConfigurationProperties字段Javadoc使用纯文本,因为它们在被添加到JSON之前没有被处理。
下面是我们在内部遵循的一些规则,以确保描述是一致的
请勿以“ The”或“ A”开头描述。
对于 boolean类型,请以"Whether" 或者 "Enable"开头.。
对于基于集合的类型,请以“以逗号分隔的列表”开始描述
使用java.time.Duration而不是long,如果它不同于毫秒,请描述默认单位,例如 “如果未指定持续时间后缀,则将使用秒”。
除非必须在运行时确定默认值,否则请不要在描述中提供默认值。
确保触发元数据生成,以便IDE协助也可用于您的密钥。 您可能需要查看生成的元数据(META-INF / spring-configuration-metadata.json),以确保正确记录了您的密钥。 在兼容的IDE中使用自己的启动程序也是验证元数据质量的好主意。
29.5.3。 “自动配置”模块
autoconfigure模块包含开始使用库所需的所有内容。它还可能包含配置键定义(如@ConfigurationProperties)和任何可用于进一步定制组件初始化方式的回调接口。
您应该将库的依赖项标记为可选,以便您可以更容易地在项目中包含autoconfigure模块。如果您这样做,则不提供库,并且在默认情况下,Spring引导将退出。
Spring Boot使用注释处理器来收集元数据文件(META-INF / spring-autoconfigure-metadata.properties)中自动配置的条件。 如果存在该文件,它将用于急切过滤不匹配的自动配置,这将缩短启动时间。 建议在包含自动配置的模块中添加以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
如果您在应用程序中直接定义了自动配置,请确保配置spring-boot-maven-plugin,以防止重新打包目标将依赖项添加到fat 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 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"
}
29.5.4。 Starter 模块
启动器实际上是一个空罐子。它的唯一目的是提供使用库所需的依赖关系。你可以把它看作是一种对开始需要做什么的固执己见的观点。
不要对添加了启动器的项目做任何假设。 如果您要自动配置的库通常需要其他启动器,请同时提及它们。 如果可选依赖项数量很多,那么提供一组适当的默认依赖项可能会很困难,因为您应避免包括对于库的典型用法而言不必要的依赖项。 换句话说,您不应包括可选的依赖项。
无论哪种方式,您的启动程序都必须直接或间接引用核心Spring Boot启动程序(spring-boot-starter)(即,如果您的启动程序依赖于另一个启动程序,则无需添加它)。 如果创建的项目仅使用您的自定义启动程序,则通过使用该核心启动器来兑现Spring Boot的核心功能。