springboot2.6.2系列教程之创建您自己的自动配置-8

创建您自己的自动配置

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

自动配置可以与提供自动配置代码以及您将使用的典型库的“启动器”相关联。

了解自动配置的 Bean

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

定位自动配置候选

META-INF/spring.factoriesSpring Boot 检查发布的 jar中是否存在文件。该文件应在键下列出您的配置类EnableAutoConfiguration,如以下示例所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

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

如果您的配置需要按特定顺序应用,您可以使用@AutoConfigureAfteror注释。@AutoConfigureBefore例如,如果您提供特定于 Web 的配置,您的类可能需要在WebMvcAutoConfiguration.

如果您想排序自动配置,您也可以使用@AutoConfigureOrder. 该注释与常规注释具有相同的语义,@Order但为自动配置类提供了专用顺序。

与标准@Configuration类一样,应用自动配置类的顺序只影响定义它们的 bean 的顺序。随后创建这些 bean 的顺序不受影响,由每个 bean 的依赖关系和任何@DependsOn关系决定。

条件注释

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

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

类条件

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

此机制不适用于@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();
        }

    }

}
Bean条件

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

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

@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {

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

}
属性条件

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

资源条件

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

web条件

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

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

测试您的自动配置

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

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

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

每个测试都可以使用运行器来表示特定的用例。例如,下面的示例调用用户配置 ( UserConfiguration) 并检查自动配置是否正确退出。Invokingrun提供了一个回调上下文,可以与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打印自动配置测试中的报告。

class MyConditionEvaluationReportingTests {

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

}

创建自己的启动器

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

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

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

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

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

命名

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

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

配置键

如果您的启动器提供配置键,请为它们使用唯一的命名空间。特别是,不要将您的键包含在 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 ...

}

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

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

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

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>
入门模块

启动器实际上是一个空罐子。它的唯一目的是提供必要的依赖项以使用该库。您可以将其视为对入门所需内容的固执己见。

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

自动配置案例

创建一个acme-spring-boot-starter名称的maven项目

项目
acme-spring-boot-starter
- src/main
    java
        AcmeServer.java  需要被实例化的服务类
        AcmeServerProperties.java 配置信息属性类
        AcmeServiceAutoConfiguration.java 自动配置类
    resources
        META-INF/spring.factories 配置自动配置的属性文件
pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.oysept</groupId>
    <artifactId>aspectlog-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <!-- springboot版本信息 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
		
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
		
        <!-- 自定义配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

spring-boot-configuration-processor作用:会在源数据文件(META-INF/spring-autoconfigure-metadata.properties)中自动扫描加载和自动配置有关的条件。也就是说,当编写starter时,会读取自动配置的条件,写入源数据文件中。

服务类
public class AcmeServer {
    private String name;

    public String sayServerName(){
        return "I'm " + name + "! ";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
属性类

用于加载yml或properties中的属性

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

@ConfigurationProperties(prefix = "acme")
public class AcmeServerProperties {

    private static final String NAME = "server0";

    private String name = NAME;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
自动配置文件
@Configuration
@EnableConfigurationProperties(AcmeServerProperties.class)//声明开启属性注入
@ConditionalOnClass(AcmeServer.class)//判断类在类路径中是否存在,只有存在时才符合条件
@ConditionalOnProperty(prefix = "acme",value = "enabled",matchIfMissing = true)
public class AcmeServiceAutoConfiguration {

    @Autowired
    private AcmeServerProperties misServiceProperties;

    @Bean(name = "acmeServer")
    @ConditionalOnMissingBean(AcmeServer.class)//当容器中没有这个Bean就自动配置这个Bean
    public AcmeServer mistraService(){
        AcmeServer misService = new AcmeServer();
        misService.setName(misServiceProperties.getName());
        return misService;
    }
}

在创建如下路径文件src/main/resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.common.autoconfigure.acme.AcmeServiceAutoConfiguration
测试

使用mvn install方式把该模块自动打包

创建一个springboot项目pom依赖上面的jar

@SpringBootApplication
@RestController
public class DemoApplication {

	@Autowired
	private AcmeServer acmeService;

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@RequestMapping("/")
	public Object index(){
		return "hello:"+acmeService.getName();
	}
}

配置文件添加debug=true可以查看自动配置类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕布辕门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值