ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

20220516 Core Features - 9. Creating Your Own Auto-configuration

2022-06-08 08:00:16  阅读:164  来源: 互联网

标签:Core Own 启动器 Features 配置 boot bean 自动 注解


前言

文档地址

如果您在一家开发共享库的公司工作,或者如果您在开源或商业库中工作,您可能想要开发自己的自动配置。自动配置类可以捆绑在外部 jar 中,并且仍然可以被 Spring Boot 拾取。

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

一个可用于展示如何逐步创建启动器的 演示项目

9.1. 理解自动配置的 Bean

在底层,自动配置是通过标准 @Configuration 类实现的。附加的 @Conditional 注解用于限制何时应用自动配置。通常,自动配置类使用 @ConditionalOnClass@ConditionalOnMissingBean 注解。这确保了自动配置仅在找到相关类并且您没有声明自己的 @Configuration 类时生效。

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

9.2. 定位自动配置候选

Spring Boot 检查发布的 jar中是否存在 META-INF/spring.factories 文件。该文件应在 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 关系决定。

9.3. 条件注解

您几乎总是希望在您的自动配置类中包含一个或多个 @Conditional 注解。@ConditionalOnMissingBean 注解是一个常见的例子,如果开发人员对你的默认设置不满意,它可以让他们覆盖自动配置。

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

9.3.1. Class 条件

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

这种机制并不适用于返回类型是条件目标的 @Bean 方法:在方法条件应用之前,JVM 将加载类和潜在的处理过的方法引用,如果类不存在,这些引用将失败。

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

@Configuration(proxyBeanMethods = false)
// Some conditions ...
public class MyAutoConfiguration {

    // Auto-configured beans ...

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(SomeService.class)
    public static class SomeServiceConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public SomeService someService() {
            return new SomeService();
        }

    }

}

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

9.3.2. Bean 条件

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

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

@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }

}

在前面的示例中,如果 ApplicationContext 中没有包含类型为 SomeService 的 bean ,那么将创建 someService bean 。

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

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

声明 @Bean 方法时,在方法的返回类型中提供尽可能多的类型信息。例如,如果你的 bean 的具体类实现了一个接口,那么 bean 方法的返回类型应该是具体类而不是接口。在使用 bean 条件时,在 @Bean 方法中提供尽可能多的类型信息尤为重要,因为它们的评估只能依赖于方法签名中可用的类型信息。

9.3.3. Property 条件

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

9.3.4. Resource 条件

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

9.3.5. Web Application 条件

@ConditionalOnWebApplication@ConditionalOnNotWebApplication 注解允许根据应用程序是否为“Web 应用程序”来包含配置。基于 servlet 的 Web 应用程序是任何使用 Spring WebApplicationContext 、定义 session 作用域或具有ConfigurableWebEnvironment 。反应式 Web 应用程序是任何使用 ReactiveWebApplicationContext 或具有 ConfigurableReactiveWebEnvironment

@ConditionalOnWarDeployment 注解允许根据应用程序是否是部署到容器的传统 WAR 应用程序来包含配置。对于使用嵌入式服务器运行的应用程序,此条件将不匹配。

9.3.6. SpEL 表达条件

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

在表达式中引用 bean 将导致该 bean 在上下文刷新处理中很早就被初始化。因此,bean 不会进行后处理(例如配置属性绑定),并且其状态可能不完整。

9.4. 测试您的自动配置

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

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

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
        .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));

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

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

@Test
void defaultServiceBacksOff() {
    this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
    });
}

@Configuration(proxyBeanMethods = false)
static class UserConfiguration {

    @Bean
    MyService myCustomService() {
        return new MyService("mine");
    }

}

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

@Test
void serviceNameCanBeConfigured() {
    this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
    });
}

运行期也可用于显示 ConditionEvaluationReport 。报告可以在 INFODEBUG 级别输出。以下示例显示了如何使用 ConditionEvaluationReportLoggingListener 输出自动配置测试中的报告。

class MyConditionEvaluationReportingTests {

    @Test
    void autoConfigTest() {
        new ApplicationContextRunner()
            .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
            .run((context) -> {
                    // Test something...
            });
    }

}

9.4.1. 模拟 Web 上下文

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

9.4.2. 覆盖类路径

还可以测试在运行时不存在特定类或包时会发生什么。Spring Boot 附带一个 runner 可以轻松使用的 FilteredClassLoader 。在以下示例中,我们断言如果不存在 MyService ,则自动配置被正确禁用:

@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
    this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
            .run((context) -> assertThat(context).doesNotHaveBean("myService"));
}

9.5. 创建自己的启动器

一个典型的 Spring Boot 启动器包含自动配置和自定义给定技术的基础设施的代码,我们称之为 acme 。为了使其易于扩展,可以将专用命名空间中的许多配置键暴露给环境。最后,提供了一个 Starter 依赖项来帮助用户尽可能轻松地开始。

具体来说,自定义启动器可以包含以下内容:

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

两个模块中的这种分离绝不是必要的。如果 acme 有多种风格、选项或可选功能,那么最好将自动配置分开,因为您可以清楚地表达某些功能是可选的事实。此外,您还可以制作一个启动器来提供有关这些可选依赖项的意见。同时,其他人只能依靠 autoconfigure 模块,制作自己的不同意见的 starter 。

如果自动配置相对简单并且没有可选功能,那么在启动器中合并两个模块绝对是一种选择。

9.5.1. 命名

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

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

9.5.2. 配置键

如果您的启动器提供配置键,请为它们使用唯一的命名空间。特别是,不要将您的键包含在 Spring Boot 使用的命名空间中(例如 servermanagementspring 等)。如果您使用相同的命名空间,我们将来可能会以破坏您的模块的方式修改这些命名空间。根据经验,在所有键前面加上您拥有的命名空间(例如 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 之前不会被处理

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

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

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

9.5.3. autoconfigure 模块

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

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

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 以防止 repackage 目标将依赖项添加到 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"
}

9.5.4. Starter 模块

启动器实际上是一个空 jar 。它的唯一目的是提供必要的依赖项以使用该库。您可以把它看作是一种固执己见的观点,即开始时需要做什么。

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

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

标签:Core,Own,启动器,Features,配置,boot,bean,自动,注解
来源: https://www.cnblogs.com/huangwenjie/p/16354167.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有