Java学习笔记

【SpringBoot】如何实现一个SpringBoot的sta

2022-11-19  本文已影响0人  嘟null

简概

官网文档:https://docs.spring.io/spring-boot/docs/2.7.5/reference/htmlsingle/#using.auto-configuration

https://docs.spring.io/spring-boot/docs/2.7.5/reference/htmlsingle/#features.developing-auto-configuration

主要步骤

编写 Java Config

添加条件

绑定⾃动配置

条件注解

条件注解

类条件

属性条件

Bean 条件

资源条件

Web 应⽤条件

其他条件

详述

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

自动配置类可以捆绑在外部jar中,并依旧可以被Spring Boot获取。

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

理解Auto-configured Beans

实现自动配置的类使用@AutoConfiguration注解。该注解本身使用@Configuration进行元注解,使自动配置成为标准的@Configuration类。可以通过附加@Conditional注解用于约束何时应用自动配置。通常,自动配置类使用@ConditionalOnClass@ConditionalOnMissingBean注解。这确保了自动配置只在找到相关类并且没有声明自己的@Configuration时才会应用。

你可以浏览 spring-boot-autoconfigure 的源代码来查看Spring提供的@AutoConfiguration类(参见 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件)。

定位 Auto-configuration “人选”

SpringBoot 检查你发布的jar包中META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file是否存在。该文件应该列出你的配置类,每行一个类名,如下例所示:

com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

可以使用#字符向导入文件添加注释。

只能通过在导入文件中命名来加载自动配置。确保它们定义在特定的包空间中,并且它们永远不是组件扫描的目标。此外,自动配置类不应该启用组件扫描来查找其他组件,应该使用特定的@Imports

如果您的配置需要以特定的顺序应用,您可以在@AutoConfiguration注解上使用beforebeforeNameafterafterName属性或使用特制的@AutoConfigureBefore@AutoConfigureBefore注解。例如,如果您提供特定于web的配置,您的类可能需要在WebMvcAutoConfiguration之后应用。

如果您想为某些彼此间没有直接了解(或者说不清楚彼此间存在)的自动配置指定顺序,您还可以使用@AutoConfigureOrder。该注解与常规的@Order注解具有相同的语义,但为自动配置类提供了专用的顺序。

条件注解

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

Spring Boot包含许多@Conditional注解,您可以在自己的代码(注解@Configuration的类或单独的@Bean方法)上重用这些注解。这些注解包括:

测试自动配置

自动配置可能受到许多因素的影响:用户配置(@Bean定义和环境自定义)、条件评估(特定库的存在),以及其他。具体地说,每个测试都应该创建一个定义良好的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");
    });
}

runner还可以用来显示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...
            });
    }

}

模拟Web上下文

如果需要测试只在servlet或响应式web应用程序上下文中运行的自动配置,可使用WebApplicationContextRunnerReactiveWebApplicationContextRunner

重写类路径

还可以测试当特定的类和/或包在运行时不存在时会发生什么。Spring引导附带一个FilteredClassLoader,运行程序可以很容易地使用它。在下面的例子中,我们断言如果MyService不存在,自动配置将被正确禁用:

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

创建自己的starter

一个典型的Spring Boot启动程序包含自动配置和定制给定技术基础结构的代码,我们称之为“acme”。为了使其易于扩展,可以向环境公开专用名称空间中的许多配置键。最后,提供了一个“starter”依赖项,以帮助用户尽可能容易地启动。

具体来说,一个自定义starter可以包含以下内容:

这两个模块的分离是没有必要的。如果“acme”有几种风格、选项或可选特性,那么最好将自动配置分开,因为您可以清楚地表达一些特性是可选的事实。此外,您还可以编写一个“starter”,提供关于这些可选依赖项的意见。同时,其他人只能依靠自动配置模块,用不同的意见制作自己的“starter”。

如果自动配置相对简单,没有可选特性,那么合并启动器中的两个模块绝对是一个选择。

“starter”的命名

您应该确保为“starter”程序提供适当的名称空间。不要以spring-boot开头模块名,即使使用不同的Maven groupId。我们可能会在未来为您自动配置的东西提供官方支持。

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

配置的键名

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

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

确保触发元数据生成,这样IDE辅助也可以用于键。你可能想要检查生成的元数据(META-INF/spring-configuration-metadata.json),以确保你的键被正确记录。在兼容的IDE中使用自己的 starter 也是验证元数据质量的好方法。

“自动配置”模块

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

您应该将库的依赖项标记为可选的,这样您就可以更容易地在项目中包含自动配置模块。如果这样做,则不会提供库,默认情况下,Spring Boot会退出。

Spring Boot使用注解处理器在元数据文件(META-INF/spring-autoconfigure-metadata.properties)中收集自动配置的条件。如果该文件存在,它将用于主动过滤不匹配的自动配置,这将提高启动时间。

使用Maven构建时,建议在包含自动配置的模块中添加以下依赖项:

<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中,依赖项应该在annotationProcessor配置中声明,如下例所示:

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

Starter 模块

starter其实是一个空的jar。它的唯一目的是提供使用库所需的依赖项。你可以把它看作是一种关于如何开始的固执的观点。

不要对添加starter的项目做任何假设。如果要自动配置的库通常需要其他启动程序,也要提到它们。如果可选依赖项的数量很高,那么提供一组适当的默认依赖项可能会比较困难,因为您应该避免包含对库的典型使用来说不必要的依赖项。换句话说,您不应该包含可选的依赖项。

无论哪种方式,你的starter必须直接或间接引用核心Spring Boot starter(spring-boot-starter,但如果你的starter依赖于另一个starter,就不需要添加它)。如果一个项目只使用你自定义的starter创建,Spring Boot的核心特性将因核心启动程序的出现而得到表彰。

上一篇 下一篇

猜你喜欢

热点阅读