自动配置可以与提供自动配置代码的 starter 以及您将使用的类库库相关联。我们首先介绍构建自己的自动配置需要了解的内容,然后我们将继续介绍。

    提示

    在内部,自动配置使用了标准的 @Configuration 类来实现。@Conditional 注解用于约束何时应用自动配置。通常,自动配置类使用 @ConditionalOnClass@ConditionalOnMissingBean 注解。这可确保仅在找到相关类时以及未声明您自己的 @Configuration 时才应用自动配置。

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

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

    注意

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

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

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

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

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

    49.3.1、类条件

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

    此机制不会以相同的方式应用于返回类型是条件的目标的 @Bean 方法:在方法上的条件应用之前,JVM 将加载类和可能处理的方法引用,如果找不到类,将发生失败。

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

    1. @Configuration
    2. // Some conditions
    3. public class MyAutoConfiguration {
    4. // Auto-configured beans
    5. @ConditionalOnClass(EmbeddedAcmeService.class)
    6. static class EmbeddedConfiguration {
    7. @Bean
    8. @ConditionalOnMissingBean
    9. public EmbeddedAcmeService embeddedAcmeService() { ... }
    10. }
    11. }

    提示

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

    49.3.2、Bean 条件

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

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

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

    提示

    注意

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

    49.3.3、属性条件

    @ConditionalOnProperty 注解允许基于 Spring Environment 属性包含配置。使用 prefixname 属性指定需要检查的属性。默认情况下,匹配存在且不等于 false 的所有属性。您还可以使用 havingValuematchIfMissing 属性创建更高级的检查。

    49.3.4、资源条件

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

    49.3.5、Web 应用程序条件

    @ConditionalOnWebApplication@ConditionalOnNotWebApplication 注解在应用程序为 Web 应用程序的情况下是否包含配置。Web 应用程序是使用 Spring WebApplicationContext,定义一个 session 范围或具有 StandardServletEnvironment 的任何应用程序。

    49.3.6、SpEL 表达式条件

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

    49.4、测试自动配置

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

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

    提示

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

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

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

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

    1. @Test
    2. public 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. }

    runner 还可用于展示 ConditionEvaluationReport。报告可以在 INFODEBUG 级别下打印。以下示例展示如何使用 ConditionEvaluationReportLoggingListener 在自动配置测试中打印报表。

    49.4.1、模拟一个 Web 上下文

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

    49.4.2、覆盖 Classpath

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

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

    一个完整的 Spring Boot starter 类库可能包含以下组件:

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

    提示

    49.5.1、命名

    您应该确保为您的 starter 提供一个合适的命名空间。即使您使用其他 Maven groupId,也不要使用 spring-boot 作为模块名称的开头。我们可能会为您以后自动配置的内容提供官方支持。

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

    此外,如果您的 starter 提供配置 key,请为它们使用唯一的命名空间。尤其是,不要将您的 key 包含在 Spring Boot 使用的命名空间中(例如 servermanagementspring 等)。如果您使用了相同的命名空间,我们将来可能会以破坏您的模块的方式来修改这些命名空间。

    确,以便为您的 key 提供 IDE 帮助。您可能想查看生成的元数据(META-INF/spring-configuration-metadata.json)以确保您的 key 记录是否正确。

    49.5.2、autoconfigure 模块

    autoconfigure 模块包含类库开始使用所需的所有内容。它还可以包含配置 key 定义(例如 @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>

    使用 Gradle 4.5 及更早版本时,应在 compileOnly 配置中声明依赖项,如下所示:

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

    1. dependencies {

    49.5.3、Starter 模块

    starter 真的是一个空 jar。它的唯一目的是为使用类库提供必要的依赖项。您可以将其视为使用类库的一切基础。

    不要对添加 starter 的项目抱有假设想法。如果您自动配置的库经常需要其他 starter,请一并声明它们。如果可选依赖项的数量很多,则提供一组适当的默认依赖项可能很难,因为您本应该避免包含对常用库的使用不必要的依赖项。换而言之,您不应该包含可选的依赖项。

    注意

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