【SpringBoot】如何实现一个SpringBoot的sta
简概
官网文档:https://docs.spring.io/spring-boot/docs/2.7.5/reference/htmlsingle/#using.auto-configuration
主要步骤
编写 Java Config
- @Configuration
添加条件
- @Conditional
绑定⾃动配置
- META-INF/spring.factories
条件注解
条件注解
- @Conditional
类条件
- @ConditionalOnClass
- @ConditionalOnMissingClass
属性条件
- @ConditionalOnProperty
Bean 条件
- @ConditionalOnBean
- @ConditionalOnMissingBean
- @ConditionalOnSingleCandidate
资源条件
- @ConditionalOnResource
Web 应⽤条件
- @ConditionalOnWebApplication
- @ConditionalOnNotWebApplication
其他条件
- @ConditionalOnExpression
- @ConditionalOnJava
- @ConditionalOnJndi
详述
自动配置类可以捆绑在外部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
注解上使用before
、beforeName
、after
和afterName
属性或使用特制的@AutoConfigureBefore
和@AutoConfigureBefore
注解。例如,如果您提供特定于web的配置,您的类可能需要在WebMvcAutoConfiguration之后应用。
如果您想为某些彼此间没有直接了解(或者说不清楚彼此间存在)的自动配置指定顺序,您还可以使用@AutoConfigureOrder
。该注解与常规的@Order
注解具有相同的语义,但为自动配置类提供了专用的顺序。
条件注解
您几乎总是希望在自动配置类中包含一个或多个@Conditional
注解。@ConditionalOnMissingBean
注解是一个常见的例子,用于允许开发人员在不满意您的默认值时覆盖自动配置。
Spring Boot包含许多@Conditional
注解,您可以在自己的代码(注解@Configuration
的类或单独的@Bean
方法)上重用这些注解。这些注解包括:
- Class Conditions
- Bean Conditions
- Property Conditions
- Resource Conditions
- Web Application Conditions
- SpEL Expression Conditions
测试自动配置
自动配置可能受到许多因素的影响:用户配置(@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
。报告可以打印INFO
或DEBUG
级别的日志。下面的示例演示如何使用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应用程序上下文中运行的自动配置,可使用WebApplicationContextRunner
或ReactiveWebApplicationContextRunner
。
重写类路径
还可以测试当特定的类和/或包在运行时不存在时会发生什么。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”的自动配置代码的自动配置模块。
- 启动模块,它提供对自动配置模块的依赖项、“acme”以及通常用到的任何其他依赖项。简而言之,添加的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之前不会被处理。
以下是我们内部遵循的一些规则,以确保描述的一致性:
- 不要以“the”或“A”开头。
- 对于布尔类型,以“Whether”或“Enable”开始描述。
- 对于基于集合的类型,在描述开头使用
"Comma-separated list"
- 使用
java.time.Duration
而不是long
,如果默认单位与毫秒不同,则描述默认单位,例如"If a duration suffix is not specified, seconds will be used"
. - 不要在描述中提供默认值,除非必须在运行时确定它。
确保触发元数据生成,这样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的核心特性将因核心启动程序的出现而得到表彰。