创建你自己的自动配置
如果你在开发共享库的公司工作,或者开发开源或商业库,你可能想开发自己的自动配置。自动配置类可以捆绑在外部jar中,但Spring Boot仍然能接收。
自动配置可以关联到一个“Starters”,该Starters提供自动配置代码以及你通常会用到的库。我们首先介绍构建自己自动配置所需的知识,然后进入创建自定义Starters的典型步骤。
理解自动配置豆子
实现自动配置的类会被注释为@AutoConfiguration. 该注释本身被元注释为@Configuration,使自动配置成为标准@Configuration类。 附加@Conditional注释用于限制何时应用自动配置。通常,自动配置类使用@ConditionalOnClass和@ConditionalOnMissingBean附注。
这确保了自动配置仅在找到相关类且你尚未声明自己时生效@Configuration.
你可以浏览 的源代码Spring Boot自动配置去看看@AutoConfigurationSpring提供的课程(参见META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件)。
自动配置候选对象的定位
Spring靴检查是否有META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports请归档到你发布的罐子里。
文件应列出你的配置类,每行一个类名,如下示例所示:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
你可以用角色在导入文件中添加注释。# |
在特殊情况下,自动配置类不是顶层类,例如其类名应用来区分其与包含的类$com.example.Outer$NestedAutoConfiguration. |
自动配置必须通过在导入文件中命名来加载。
确保它们在特定的包空间中定义,并且永远不会成为组件扫描的目标。
此外,自动配置类不应允许组件扫描以查找额外组件。
特定@Import应使用注释代替。 |
如果你的配置需要按特定顺序应用,你可以使用以前,之前的名字,后和名后属性@AutoConfiguration注释或专用@AutoConfigureBefore和@AutoConfigureAfter附注。
例如,如果你提供了网页专用配置,你的类可能需要在之后应用WebMvcAutoConfiguration.
如果你想订购某些自动配置,这些配置之间不应有直接的关联,你也可以使用@AutoConfigureOrder.
该注释与常规注释具有相同的语义@Order注释,但为自动配置类提供了专门的顺序。
与标准一致@Configuration自动配置类应用的顺序仅影响其豆子定义的顺序。
这些豆子随后的生成顺序不受影响,取决于每个豆子的依赖性以及@DependsOn关系。
淘汰和替换自动配置类
你可能需要偶尔弃用自动配置类,提供替代方案。 例如,你可能想更改自动配置类所在的包名。
由于自动配置类可以被引用于以前/后排序和排除你需要添加一个额外文件,告诉 Spring Boot 如何处理替换设备。
要定义替换,创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements文件显示旧类与新类之间的关联。
例如:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration
这AutoConfiguration.imports文件也应更新为只引用替换类。 |
条件注释
你几乎总是想包含一个或多个@Conditional关于你的自动配置类的注释。
这@ConditionalOnMissingBean注释是一个常见例子,允许开发者在对默认设置不满意时覆盖自动配置。
Spring靴包含若干@Conditional你可以在自己的代码中重复使用的注释,通过注释@Configuration级别或个人@Bean方法。
这些注释包括:
班级条件
这@ConditionalOnClass和@ConditionalOnMissingClass注释设为@Configuration课程的包含应根据特定课程的存在与否而定。
由于注释元数据是通过ASM解析的,你可以使用值属性来指代真实的类,尽管该类可能实际上并未出现在运行中的应用程序类路径上。
你也可以使用名称如果你喜欢用 a 来指定类名,则字符串价值。
该机制不适用于@Bean通常返回类型是条件的目标:在方法条件应用之前,JVM已加载该类并可能处理若类不存在将失败的方法引用。
为了处理这种情况,有个单独的方案@Configuration可用类来隔离该条件,如下示例所示:
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.AutoConfiguration;
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;
@AutoConfiguration
// Some conditions ...
public final class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService.class)
public static class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
}
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 ...
class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService::class)
class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
}
如果你使用,@ConditionalOnClass或@ConditionalOnMissingClass作为元注释的一部分,要写自己的注释,你必须使用名称因为在这种情况下,不涉及对该类的指代。 |
Beans状况
这@ConditionalOnBean和@ConditionalOnMissingBean注释可以根据特定豆子的有无来包含一个豆子。你可以使用值属性用于按类型指定豆子,或者名称用名字来指定豆子。 这搜索属性允许你限制应用上下文搜索豆子时应考虑的层级结构。
当放置在@Bean方法,目标类型默认为方法的返回类型,如下示例所示:
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
public final class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SomeService someService() {
return new SomeService();
}
}
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
在前面的例子中,某些服务如果没有 类型的 bean ,就会生成 bean某种服务已经包含在应用上下文.
你需要非常谨慎地选择Beans定义的加入顺序,因为这些条件是基于目前加工的材料进行评估的。因此,我们建议仅使用。@ConditionalOnBean和@ConditionalOnMissingBean自动配置类的注释(因为这些类在添加任何用户自定义的 BEAN 定义后保证会加载)。 |
@ConditionalOnBean和@ConditionalOnMissingBean不要阻止@Configuration类别被创建。在类级层面使用这些条件和标记每个包含条件的唯一区别@Bean带有注释的方法,前者阻止了对@Configuration如果条件不匹配,则被归类为Beans。 |
物业条件
这@ConditionalOnProperty注释允许基于 Spring Environment 属性进行配置。使用前缀和名称属性指定应检查的属性。默认情况下,任何存在且不等于的属性false是匹配的。还有专门的@ConditionalOnBooleanProperty专门为布尔属性设计的注释。使用这两种注释,你还可以通过使用拥有价值和匹配如果缺失属性。
如果在名称属性,所有属性都必须通过测试,条件才会匹配。
资源条件
这@ConditionalOnResource注释允许仅在存在特定资源时才包含配置。资源可以通过通常的 Spring 约定来指定,如下示例所示:文件:/主页/用户/test.dat.
网页应用条件
这@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注释允许根据应用程序是否为网页应用而包含配置。基于servlet的网页应用是指任何使用Spring的应用程序WebApplicationContext定义了一个会期范围,或具有可配置WebEnvironment. 响应式网页应用是指任何使用以下ReactiveWebApplicationContext,或具有一个可配置ReactiveWebEnvironment.
这@ConditionalOnWarDeployment和@ConditionalOnNotWarDeployment注释允许根据应用是否是部署到servlet容器的传统WAR应用而包含配置。但对于与嵌入式Web服务器运行的应用,这一条件不匹配。
SpEL表达条件
这@ConditionalOnExpression注释允许基于 SpEL 表达式的结果进行配置。
| 在表达式中引用豆子会使该豆子在上下文刷新处理中被非常早地初始化。 因此,豆子无法进行后处理(如配置属性绑定),其状态可能不完整。 |
测试你的自动配置
自动配置会受到多种因素影响:用户配置(@Bean定义和环境自定义)、状态评估(特定库的存在)等。
具体来说,每个测试都应该创建一个明确定义的标准应用上下文这代表了这些自定义的结合。应用上下文运行器提供了实现这一点的绝佳方式。
应用上下文运行器在本地镜像中运行测试时不起作用。 |
应用上下文运行器通常定义为测试类中的一个场,用以收集基础的共同配置。
以下示例确保了MyServiceAutoConfiguration总是被调用:
-
Java
-
Kotlin
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
| 如果需要定义多个自动配置,则无需为它们的声明排序,因为它们的调用顺序与运行应用程序时完全相同。 |
每个测试都可以使用运行者来表示特定的用例。
例如,下面的示例调用了一个用户配置(用户配置并检查自动配置是否正确后退。
调用执行提供了可以与AssertJ一起使用的回调上下文。
-
Java
-
Kotlin
@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");
}
}
@Test
fun defaultServiceBacksOff() {
contextRunner.withUserConfiguration(UserConfiguration::class.java)
.run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context).getBean("myCustomService")
.isSameAs(context.getBean(MyService::class.java))
}
}
@Configuration(proxyBeanMethods = false)
internal class UserConfiguration {
@Bean
fun myCustomService(): MyService {
return MyService("mine")
}
}
也可以轻松定制环境如下例所示:
-
Java
-
Kotlin
@Test
void serviceNameCanBeConfigured() {
this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
});
}
@Test
fun serviceNameCanBeConfigured() {
contextRunner.withPropertyValues("user.name=test123").run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context.getBean(MyService::class.java).name).isEqualTo("test123")
}
}
滑动器也可以用来显示状况评估报告.
该报告可打印于以下信息或调试水平。
以下示例展示了如何使用ConditionEvaluationReportLoggingListener在自动配置测试中打印报告。
-
Java
-
Kotlin
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(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
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.assertj.AssertableApplicationContext
import org.springframework.boot.test.context.runner.ApplicationContextRunner
class MyConditionEvaluationReportingTests {
@Test
fun autoConfigTest() {
ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run { context: AssertableApplicationContext? -> }
}
}
模拟网页环境
如果你需要测试只在 servlet 或响应式 Web 应用环境中运行的自动配置,可以使用WebApplicationContextRunner或ReactiveWebApplicationContextRunner分别。
覆盖类路径
也可以测试当某个特定类和/或包在运行时不存在时会发生什么。
Spring靴配备FilteredClassLoader跑者可以轻松使用。
在下面的例子中,我们断言如果我的服务不存在,自动配置已被正确禁用:
-
Java
-
Kotlin
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
@Test
fun serviceIsIgnoredIfLibraryIsNotPresent() {
contextRunner.withClassLoader(FilteredClassLoader(MyService::class.java))
.run { context: AssertableApplicationContext? ->
assertThat(context).doesNotHaveBean("myService")
}
}
创建自己的起始
一个典型的 Spring Boot 启动程序包含用于自动配置和定制某项技术基础设施的代码,我们称之为“acme”。 为了便于扩展,可以在专用命名空间中暴露多个配置键。 最后,提供了一个单一的“起始”依赖,帮助用户尽可能轻松地开始。
具体来说,自定义起始器可以包含以下内容:
-
这
自动配置包含“Acme”自动配置代码的模块。 -
这
起动机提供依赖于自动配置模块以及“Acme”以及其他通常有用的依赖关系。 简而言之,添加入门工具应该能提供开始使用该库所需的一切。
这种分成两个模块的分离绝非必要。
如果“acme”有多种形式、选项或可选功能,那么最好将自动配置分开,因为你可以清楚地表达某些功能是可选的。
此外,你还可以制作一个对可选依赖性发表看法的初始角色。
与此同时,其他人只能依赖自动配置模块化并制作各自不同的起始角色。
如果自动配置相对简单且没有可选功能,将Starters中的两个模块合并绝对是一个选项。
命名
你应该确保为你的初始物提供一个合适的命名空间。
不要用Spring靴即使你用的是不同的Maven组ID.
我们未来可能会为你自动配置的项目提供官方支持。
一般来说,你应该以起始模块的名字来命名合并模块。
例如,假设你正在为“acme”创建一个启动模块,并且你给自动配置模块命名AcmeSpring靴以及起始AcmeSpring Boot器.
如果你只有一个模块结合了这两者,那就给它命名AcmeSpring Boot器.
配置密钥
如果你的起始程序提供了配置密钥,请为它们使用唯一的命名空间。
特别是,不要把你的密钥包含在 Spring Boot 使用的命名空间里(例如服务器,管理,Spring,依此类推)。
如果你使用相同的命名空间,我们未来可能会以破坏模块的方式修改这些命名空间。
经验法则是,在所有键前都用你拥有的命名空间作为前缀(例如)顶点).
确保配置键通过为每个属性添加字段 Javadoc 来记录,如下示例所示:
-
Java
-
Kotlin
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);
// getters/setters ...
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;
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.Duration
@ConfigurationProperties("acme")
class AcmeProperties(
/**
* Whether to check the location of acme resources.
*/
var isCheckLocation: Boolean = true,
/**
* Timeout for establishing a connection to the acme server.
*/
var loginTimeout:Duration = Duration.ofSeconds(3))
你应该只使用纯文本,且@ConfigurationProperties字段 Javadoc,因为它们在添加到 JSON 之前不会被处理。 |
如果你使用,@ConfigurationProperties对于记录类,那么记录组件的描述应通过类级Javadoc标签提供@param(记录类中没有显式实例字段来放置常规字段级 Javadocs。)
以下是我们内部遵循的一些规则,以确保描述一致:
-
不要以“The”或“A”开头描述。
-
为
布尔类型,描述以“Whether”或“Enable”开头。 -
对于基于集合的类型,描述开头应以“逗号分隔列表”开头
-
用
期间而不是长如果默认单位与毫秒不同,请描述,例如“如果未指定时长后缀,则使用秒”。 -
除非必须在运行时确定,否则不要在描述中提供默认值。
确保触发元数据生成,这样你的密钥也能获得IDE辅助。
你可能需要查看生成的元数据(元步兵/spring-configuration-metadata.json确保你的密钥被妥善记录。
在兼容的IDE中使用自己的启动程序也是验证元数据质量的好方法。
“自动配置”模块
这自动配置模块包含启动库所需的一切。
它还可能包含配置密钥定义(例如@ConfigurationProperties)以及任何可用于进一步定制组件初始化方式的回调接口。
你应该将对库的依赖标记为可选,这样你就可以包含自动配置模块化你的项目更轻松。
如果这样作,库就不会被提供,Spring Boot默认会退役。 |
Spring Boot 使用注释处理器在元数据文件中收集自动配置的条件(META-INF/spring-autoconfigure-metadata.properties).
如果有该文件,它会被用来积极过滤不匹配的自动配置,从而提高启动时间。
使用 Maven 构建时,配置编译器插件(3.12.0 或更高版本)以添加内容Spring Boot自动配置处理器到注释处理器路径:
<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用 Gradle,依赖应在注释处理处理器配置,如下示例所示:
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
入门模块
发酵剂其实就是一个空罐子。 它的唯一目的是提供与库协作所需的依赖。 你可以把它看作是对入门所需条件的一种主观观点。
不要对添加起始器的项目做假设。 如果你自动配置的库通常需要其他入门,也要提及它们。 如果可选依赖数量众多,提供合适的默认依赖可能比较困难,因为应避免包含对库的典型使用不必要的依赖。 换句话说,你不应该包含可选依赖。
无论哪种方式,你的起始器必须参考核心的Spring Boot起始器(Spring靴Starters)直接或间接(如果你的起始种子依赖另一个起始种子,无需添加)。
如果项目仅用自定义Starters创建,Spring Boot的核心功能将因核心Starters而得到尊重。 |