如果您在开发共享库的公司中工作,或者在开源或商业库中工作,则可能需要开发自己的自动配置。自动配置类可以捆绑在外部jar中,并且仍由Spring Boot拾取。
自动配置可以与“启动器”相关联,该“启动器”提供自动配置代码以及您将使用的典型库。我们首先介绍您构建自己的自动配置所需的知识,然后继续进行创建自定义启动器所需典型步骤

可以使用一个演示项目来展示如何逐步创建入门程序。

29.1 了解自动配置的Bean

在后台,自动配置是通过标准@Configuration类实现的。其他@Conditional注释用于约束何时应应用自动配置。通常,自动配置类使用@ConditionalOnClass@ConditionalOnMissingBean注释。这样可以确保仅当找到相关的类并且尚未声明自己的类时,自动配置才适用@Configuration
您可以浏览的源代码spring-boot-autoconfigure以查看@ConfigurationSpring提供的类(请参见META-INF/spring.factories文件)。

29.2 查找自动配置候选人

Spring Boot检查META-INF/spring.factories发布的jar中是否存在文件。该文件应在EnableAutoConfiguration键下列出您的配置类,如以下示例所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

自动配置只能以这种方式加载。确保在特定的程序包空间中定义它们,并且决不要将它们作为组件扫描的目标。此外,自动配置类不应启用组件扫描以查找其他组件。@Import应该使用特定的。

如果需要按特定顺序应用配置,则可以使用@AutoConfigureAfter@AutoConfigureBefore注释。例如,如果您提供特定于Web的配置,则可能需要在之后应用类WebMvcAutoConfiguration
如果您要订购某些彼此之间不具有直接了解的自动配置,则也可以使用@AutoConfigureOrder。该注释与常规注释具有相同的语义,@Order但为自动配置类提供了专用顺序。
与标准@Configuration类一样,自动配置类的应用顺序仅会影响其bean的定义顺序。随后创建这些bean的顺序不受影响,并由每个bean的依赖关系和任何@DependsOn关系确定。

29.3 条件注释

您几乎总是希望@Conditional在自动配置类中包含一个或多个注释。该@ConditionalOnMissingBean注释是用来让开发者重写自动配置,如果他们不满意自己的缺省值一个常见的例子。
Spring Boot包含许多@Conditional注释,您可以通过注释@Configuration类或单个@Bean方法在自己的代码中重用它们。这些注释包括:

  • 上课条件
  • 豆条件
  • 物业条件
  • 资源条件
  • Web应用条件
  • SpEL表达条件

    29.3.1 上课条件

    @ConditionalOnClass@ConditionalOnMissingClass注解让@Configuration类基于特定类的存在或不存在被包括在内。由于注释元数据是使用ASM进行解析的value,因此即使该类实际上未真正出现在正在运行的应用程序类路径上,您也可以使用该属性来引用真实的类。name如果您更喜欢通过使用String值来指定类名称,则也可以使用该属性。
    这种机制不适用于@Bean通常以返回类型为条件的目标的方法:在方法的条件适用之前,JVM将加载该类和可能处理的方法引用,如果该类不是当下。
    要处理这种情况,@Configuration可以使用一个单独的类来隔离条件,如以下示例所示:
    1. @Configuration(proxyBeanMethods = false)
    2. // Some conditions
    3. public class MyAutoConfiguration {
    4. // Auto-configured beans
    5. @Configuration(proxyBeanMethods = false)
    6. @ConditionalOnClass(EmbeddedAcmeService.class)
    7. static class EmbeddedConfiguration {
    8. @Bean
    9. @ConditionalOnMissingBean
    10. public EmbeddedAcmeService embeddedAcmeService() { ... }
    11. }
    12. }
    | | 如果您使用元批注@ConditionalOnClass@ConditionalOnMissingClass作为元批注的一部分来组成自己的组合批注,name则在不处理这种情况下,必须使用引用类。 | | :—-: | —- |

29.3.2 豆条件

@ConditionalOnBean@ConditionalOnMissingBean注解让豆基于特定豆的存在或不存在被包括在内。您可以使用该value属性按类型name指定bean或按名称指定bean。该search属性使您可以限制ApplicationContext搜索bean时应考虑的层次结构。
当放置在@Bean方法上时,目标类型默认为方法的返回类型,如以下示例所示:

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

在前面的例子中,myService豆将被创建如果没有类型的豆MyService已经包含在所述ApplicationContext

您需要非常注意添加bean定义的顺序,因为这些条件是根据到目前为止已处理的内容来评估的。因此,我们建议在自动配置类上仅使用@ConditionalOnBean@ConditionalOnMissingBean批注(因为保证在添加任何用户定义的Bean定义后即可加载它们)。
@ConditionalOnBean并且@ConditionalOnMissingBean不要阻止@Configuration类的创建。在类级别使用这些条件@Bean与使用注释标记每个包含的方法之间的唯一区别是,@Configuration如果条件不匹配,则前者会阻止将该类注册为bean。

29.3.3 物业条件

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

29.3.4 资源条件

@ConditionalOnResource注解让配置被包括仅当特定资源是否存在。资源可通过使用通常的弹簧约定来指定,如显示在下面的例子:file:/home/user/test.dat

29.3.5 Web应用条件

@ConditionalOnWebApplication@ConditionalOnNotWebApplication注释,让配置包含依赖于应用程序是否是一个“Web应用程序”。基于Servlet的Web应用程序是使用Spring WebApplicationContext,定义session范围或具有的任何应用程序ConfigurableWebEnvironment。响应式Web应用程序是使用ReactiveWebApplicationContext或具有的任何应用程序ConfigurableReactiveWebEnvironment
@ConditionalOnWarDeployment注解让配置取决于应用是否是被部署到一个容器中的传统WAR应用程序被包括在内。对于嵌入式服务器运行的应用程序,此条件将不匹配。

29.3.6 SpEL表达条件

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

29.4 测试您的自动配置

自动配置可能受到许多因素的影响:用户配置(@Bean定义和Environment自定义),条件评估(特定库的存在)以及其他因素。具体而言,每个测试都应创建定义良好的定义ApplicationContext,以代表这些定制的组合。 ApplicationContextRunner提供了实现此目标的好方法。
ApplicationContextRunner通常定义为测试类的一个字段,以收集基本的通用配置。以下示例确保UserServiceAutoConfiguration始终调用该示例:

  1. private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
  2. .withConfiguration(AutoConfigurations.of(UserServiceAutoConfiguration.class));
如果必须定义多个自动配置,则无需按与运行应用程序时完全相同的顺序调用它们的声明。

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

  1. @Test
  2. void defaultServiceBacksOff() {
  3. this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
  4. assertThat(context).hasSingleBean(UserService.class);
  5. assertThat(context).getBean("myUserService").isSameAs(context.getBean(UserService.class));
  6. });
  7. }
  8. @Configuration(proxyBeanMethods = false)
  9. static class UserConfiguration {
  10. @Bean
  11. UserService myUserService() {
  12. return new UserService("mine");
  13. }
  14. }

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

  1. @Test
  2. void serviceNameCanBeConfigured() {
  3. this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
  4. assertThat(context).hasSingleBean(UserService.class);
  5. assertThat(context.getBean(UserService.class).getName()).isEqualTo("test123");
  6. });
  7. }

跑步者也可以用来显示ConditionEvaluationReport。可以按级别INFODEBUG水平打印报告。以下示例显示了如何ConditionEvaluationReportLoggingListener在自动配置测试中使用来打印报告。

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

29.4.1 模拟Web上下文

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

29.4.2 覆盖类路径

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

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

29.5 创建自己的入门

一个典型的Spring Boot启动器包含用于自动配置和自定义给定技术的基础结构的代码,我们称其为“ acme”。为了使其易于扩展,可以将专用命名空间中的许多配置密钥公开给环境。最后,提供了一个“启动程序”依赖项,以帮助用户尽可能轻松地入门。
具体而言,自定义启动器可以包含以下内容:

  • autoconfigure包含“ acme”的自动配置代码的模块。
  • starter其提供给一个依赖模块autoconfigure模块以及“ACME”,并且通常是有用的任何附加的依赖性。简而言之,添加启动程序应提供开始使用该库所需的一切。

完全没有必要将这两个模块分开。如果“ acme”具有多种功能,选项或可选功能,则最好将自动配置分开,因为您可以清楚地表示某些功能是可选的。此外,您还可以制作一个入门程序,以提供有关那些可选依赖项的意见。同时,其他人只能依靠该autoconfigure模块,并以不同的观点来设计自己的启动器。
如果自动配置相对简单并且不具有可选功能,则将两个模块合并在启动器中绝对是一种选择。

29.5.1 命名

您应该确保为启动器提供适当的名称空间。spring-boot即使使用其他Maven,也不要以模块名称开头groupId。将来,我们可能会为您自动配置的内容提供官方支持。
根据经验,您应该在启动器后命名一个组合模块。例如,假设您要为“ acme”创建启动器,并命名自动配置模块acme-spring-boot和启动器acme-spring-boot-starter。如果您只有一个将两者结合的模块,请命名为acme-spring-boot-starter

29.5.2 配置键

如果您的入门者提供了配置密钥,请为其使用唯一的名称空间。特别是,不包括你的名字空间去春Boot使用键(如servermanagementspring,等)。如果使用相同的名称空间,将来我们可能会以破坏模块的方式修改这些名称空间。根据经验,所有键都以您拥有的名称空间(例如acme)为前缀。
通过为每个属性添加字段javadoc来确保记录了配置键,如以下示例所示:

  1. @ConfigurationProperties("acme")
  2. public class AcmeProperties {
  3. /**
  4. * Whether to check the location of acme resources.
  5. */
  6. private boolean checkLocation = true;
  7. /**
  8. * Timeout for establishing a connection to the acme server.
  9. */
  10. private Duration loginTimeout = Duration.ofSeconds(3);
  11. // getters & setters
  12. }
您仅应将纯文本与@ConfigurationPropertiesJavadoc字段一起使用,因为在将它们添加到JSON之前不会对其进行处理。

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

  • 请勿以“ The”或“ A”开头描述。
  • 对于boolean类型,请从“是否”或“启用”开始描述。
  • 对于基于集合的类型,请以“以逗号分隔的列表”开始描述
  • 如果默认单位与毫秒不同,请使用java.time.Duration而不是long并描述默认单位,例如“如果未指定持续时间后缀,将使用秒”。
  • 除非必须在运行时确定默认值,否则请不要在描述中提供默认值。

确保触发元数据生成,以便IDE协助也可用于您的密钥。您可能需要查看生成的元数据(META-INF/spring-configuration-metadata.json),以确保正确记录了您的密钥。在兼容的IDE中使用自己的启动程序也是验证元数据质量的好主意。

29.5.3 “自动配置”模块

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

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

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

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

如果您直接在应用程序中定义了自动配置,请确保配置,spring-boot-maven-plugin以防止repackage目标将依赖项添加到胖罐中:

  1. <project>
  2. <build>
  3. <plugins>
  4. <plugin>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-maven-plugin</artifactId>
  7. <configuration>
  8. <excludes>
  9. <exclude>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-autoconfigure-processor</artifactId>
  12. </exclude>
  13. </excludes>
  14. </configuration>
  15. </plugin>
  16. </plugins>
  17. </build>
  18. </project>

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

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

对于Gradle 4.6和更高版本,应在annotationProcessor配置中声明依赖项,如以下示例所示:

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

29.5.4 启动模块

起动器确实是一个空罐子。其唯一目的是提供必要的依赖关系以使用库。您可以将其视为对入门所需的看法。
不要对添加了启动器的项目做任何假设。如果您要自动配置的库通常需要其他启动器,请同时提及它们。如果可选依赖项的数量很高,则很难提供一组适当的默认依赖项,因为您应避免包括对于库的典型用法而言不必要的依赖项。换句话说,您不应包括可选的依赖项。

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