IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring Boot 框架学习 - 创建您自己的自动配置 -> 正文阅读

[Java知识库]Spring Boot 框架学习 - 创建您自己的自动配置

9、创建您自己的自动配置

对应官网地址:https://docs.spring.io/spring-boot/docs/2.6.2/reference/htmlsingle/#features.developing-auto-configuration

如果您在开发共享库的公司工作,或者如果您在开源或商业库上工作,您可能希望开发自己的自动配置。 自动配置类可以捆绑在外部 jar 中,并且仍然可以由 Spring Boot 获取。

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

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

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 方法在自己的代码中重用它们。 这些注解包括:

  • Class Conditions

  • Bean Conditions

  • Property Conditions

  • Resource Conditions

  • Web Application Conditions

  • SpEL Expression Conditions

在这里插入图片描述

9.3.1 Class 条件

@ConditionalOnClass@ConditionalOnMissingClass 注解让 @Configuration 类根据特定类的存在或不存在而被包含在内。 由于注解元数据是使用 ASM 解析的,因此您可以使用 value 属性来引用真实的类,即使该类实际上可能不会出现在正在运行的应用程序类路径中。 如果您更喜欢使用 String 值指定类名,也可以使用 name 属性。【注意:使用 name 属性时,需要的是类的全限定名 如:com.tyu.example.pojo.User

这种机制不适用于 @Bean 方法,其中通常返回类型是条件的目标:在方法上的条件应用之前,JVM 将加载类和可能处理的方法引用,如果该类不存在,这将失败。

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

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.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 作为元注解的一部分来组成您自己的组合注解,则必须使用名称作为引用类的名称,在这种情况下不会被处理。

9.3.2 Bean 条件

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

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

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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、定义会话范围或具有 ConfigurableWebEnvironment 的应用程序。 响应式 Web 应用程序是任何使用 ReactiveWebApplicationContext 或具有 ConfigurableReactiveWebEnvironment 的应用程序。

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

9.3.6 SpEL 表达式条件

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

9.3.7 使用例子

下面我们以 @ConditionalOnMissingBean 为例,代码如下:

@Configuration
@ConditionalOnMissingBean(name = "tom") // 当不存在 tom bean 时
public class MyConfig {

    @Bean
    public User user01() {
        return new User("张三",12);
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }

}

启动类:

@SpringBootApplication
public class WebApplication {


    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(WebApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:"+tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中tom22组件:"+tom22);
    }

}

运行结果:
在这里插入图片描述
我们把上面的代码修改一下:

@ConditionalOnBean(name = "tom") // 存在 tom bean 时

我们来看一下运行结果:
在这里插入图片描述
使用 @ConditionalOnBean("tom") 是只有当 tom bean 存在时配置才会生效,此时可以在打印的结果中搜索一下是否 @Configuration 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 在自动配置测试中打印报告。

import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

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 附带了一个 FilteredClassLoader,可以很容易地被跑步者使用。 在下面的例子中,我们断言如果 MyService 不存在,自动配置被正确禁用:

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

9.5 创建您自己的启动器

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

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

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

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

如果自动配置比较简单,没有可选功能,在starter中合并两个模块绝对是一个选择。

9.5.1 命名

你应该确保为你的 starter 提供一个合适的命名空间。 不要用 spring-boot 开始你的模块名称,即使你使用不同的 Maven groupId。 将来我们可能会为您自动配置的东西提供官方支持。

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

9.5.2 配置键

如果您的 starter 提供配置密钥,请为它们使用唯一的命名空间。 特别是,不要在 Spring Boot 使用的命名空间(例如 servermanagementspring 等)中包含您的键。 如果您使用相同的命名空间,我们将来可能会以破坏您的模块的方式修改这些命名空间。 根据经验,使用您拥有的命名空间(例如 acme)作为所有键的前缀。

确保通过为每个属性添加字段 javadoc 来记录配置键,如以下示例所示:

import java.time.Duration;

import org.springframework.boot.context.properties.ConfigurationProperties;

@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);

    public boolean isCheckLocation() {
        return this.checkLocation;
    }

    public void setCheckLocation(boolean checkLocation) {
        this.checkLocation = checkLocation;
    }

    public Duration getLoginTimeout() {
        return this.loginTimeout;
    }

    public void setLoginTimeout(Duration loginTimeout) {
        this.loginTimeout = loginTimeout;
    }

}

您应该只使用带有 @ConfigurationProperties 字段 Javadoc 的纯文本,因为它们在被添加到 JSON 之前不会被处理。

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

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

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

9.5.3 “自动配置”模块

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 目标将依赖项添加到胖 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 启动器模块

启动器实际是一个空的 jar。它的唯一目的是提供必要的依赖来使用库。 您可以将其视为对开始所需内容的一种固执己见。

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

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

文章参考:https://docs.spring.io/spring-boot/docs/2.6.2/reference/htmlsingle/#features.developing-auto-configuration

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-01 13:44:53  更:2022-01-01 13:45:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:54:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码