此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Boot 3.3.4! |
此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Boot 3.3.4! |
如果您在开发共享库的公司工作,或者如果您从事开源或商业库的工作,则可能需要开发自己的自动配置。 自动配置类可以捆绑在外部 jar 中,并且仍然由 Spring Boot 拾取。
Auto-configuration 可以与 “starter” 相关联,该 “starter” 提供 auto-configuration 代码以及您将与之一起使用的典型 libraries。 我们首先介绍构建自己的自动配置所需了解的内容,然后我们继续介绍创建自定义Starters所需的典型步骤。
了解自动配置的 Bean
实现自动配置的类用 .
这个 Comments 本身是 元 Comments 的,使自动配置成为标准类。
其他注释用于限制何时应应用自动配置。
通常,自动配置类使用 和 annotations。
这可确保仅在找到相关类且您尚未声明自己的类时应用自动配置。@AutoConfiguration
@Configuration
@Configuration
@Conditional
@ConditionalOnClass
@ConditionalOnMissingBean
@Configuration
你可以浏览spring-boot-autoconfigure
的源代码来查看 Spring 提供的类(参见META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件)。@AutoConfiguration
查找 Auto-configuration Candidate
Spring Boot 检查已发布的 jar 中是否存在文件。
该文件应列出您的配置类,每行一个类名,如以下示例所示:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
您可以使用该字符向 imports 文件添加注释。# |
自动配置只能通过在 imports 文件中命名来加载。
确保它们在特定的包空间中定义,并且它们永远不会成为组件扫描的目标。
此外,自动配置类不应启用组件扫描来查找其他组件。
应改用特定注释。@Import |
如果您的配置需要按特定顺序应用,则可以在注释上使用 、 和 属性,或者使用 dedicated and 注释。
例如,如果您提供特定于 Web 的配置,则可能需要在 .before
beforeName
after
afterName
@AutoConfiguration
@AutoConfigureBefore
@AutoConfigureAfter
WebMvcAutoConfiguration
如果要订购某些不应直接了解彼此的自动配置,也可以使用 。
该 annotation 与常规 annotation 具有相同的语义,但为 auto-configuration 类提供了专用的顺序。@AutoConfigureOrder
@Order
与标准类一样,应用自动配置类的顺序仅影响其 bean 的定义顺序。
这些 bean 的后续创建顺序不受影响,并且由每个 bean 的依赖关系和任何关系决定。@Configuration
@DependsOn
弃用和替换自动配置类
您可能需要偶尔弃用 auto-configuration 类并提供替代方案。 例如,您可能希望更改 auto-configuration 类所在的软件包名称。
由于可能在 / ordering 和 中引用自动配置类,因此您需要添加一个额外的文件,告诉 Spring Boot 如何处理替换。
要定义替换项,请创建一个文件,指示旧类和新类之间的链接。before
after
excludes
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements
例如:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration
还应更新该文件以仅引用替换类。AutoConfiguration.imports |
您可以使用该字符向 imports 文件添加注释。# |
自动配置只能通过在 imports 文件中命名来加载。
确保它们在特定的包空间中定义,并且它们永远不会成为组件扫描的目标。
此外,自动配置类不应启用组件扫描来查找其他组件。
应改用特定注释。@Import |
还应更新该文件以仅引用替换类。AutoConfiguration.imports |
条件注释
您几乎总是希望在 auto-configuration 类中包含一个或多个 Comments。
注解是一个常见的示例,用于允许开发人员在对您的默认值不满意时覆盖自动配置。@Conditional
@ConditionalOnMissingBean
Spring Boot 包含许多 Comments,您可以通过 Comments 类或单个方法在自己的代码中重用这些 Comments。
这些注释包括:@Conditional
@Configuration
@Bean
类条件
和 注解允许根据特定类的存在与否来包含类。
由于注释元数据是使用 ASM 解析的,因此您可以使用该属性来引用实际类,即使该类实际上可能并未出现在正在运行的应用程序类路径上。
如果您希望通过使用值来指定类名,也可以使用该属性。@ConditionalOnClass
@ConditionalOnMissingClass
@Configuration
value
name
String
此机制不适用于通常返回类型是条件目标的方法:在方法的条件应用之前,JVM 将加载类和可能处理的方法引用,如果类不存在,则这些引用将失败。@Bean
若要处理此方案,可以使用单独的类来隔离条件,如以下示例所示:@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 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 name |
Bean 条件
和 注解允许根据特定 bean 的存在与否来包含 bean。
您可以使用该属性按类型指定 bean 或按名称指定 bean。
该属性允许您限制搜索 bean 时应考虑的层次结构。@ConditionalOnBean
@ConditionalOnMissingBean
value
name
search
ApplicationContext
当放置在方法上时,目标类型默认为方法的返回类型,如以下示例所示:@Bean
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public 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()
}
}
在前面的示例中,如果 .someService
SomeService
ApplicationContext
您需要非常小心 bean 定义的添加顺序,因为这些条件是根据到目前为止已处理的内容进行评估的。
因此,我们建议仅在自动配置类上使用 and 注释(因为可以保证在添加任何用户定义的 bean 定义后加载这些 Comments)。@ConditionalOnBean @ConditionalOnMissingBean |
@ConditionalOnBean 并且不会阻止创建类。
在类级别使用这些条件与使用 Comments 标记每个包含的方法之间的唯一区别是,如果条件不匹配,前者会阻止将类注册为 Bean。@ConditionalOnMissingBean @Configuration @Bean @Configuration |
声明方法时,请在方法的 return 类型中提供尽可能多的类型信息。
例如,如果 Bean 的具体类实现了一个接口,则 Bean 方法的返回类型应该是具体类,而不是接口。
在使用 Bean 条件时,在方法中提供尽可能多的类型信息尤为重要,因为它们的评估只能依赖于方法签名中可用的类型信息。@Bean @Bean |
物业条件
该注释允许根据 Spring Environment 属性包含配置。
使用 and 属性指定应检查的属性。
默认情况下,将匹配存在且不等于的任何属性。
您还可以使用 和 属性创建更高级的检查。@ConditionalOnProperty
prefix
name
false
havingValue
matchIfMissing
如果属性中给出了多个名称,则所有属性都必须通过测试才能匹配条件。name
资源条件
该注释允许仅在存在特定资源时包含配置。
可以使用通常的 Spring 约定指定资源,如以下示例所示:。@ConditionalOnResource
file:/home/user/test.dat
Web 应用程序条件
和 annotations 允许根据应用程序是否为 Web 应用程序来包含配置。
基于 servlet 的 Web 应用程序是使用 Spring 、定义范围或具有 .
反应式 Web 应用程序是使用 . 或具有 .@ConditionalOnWebApplication
@ConditionalOnNotWebApplication
WebApplicationContext
session
ConfigurableWebEnvironment
ReactiveWebApplicationContext
ConfigurableReactiveWebEnvironment
和 注释允许根据应用程序是否是部署到 servlet 容器的传统 WAR 应用程序来包含配置。
对于使用嵌入式 Web 服务器运行的应用程序,此条件将不匹配。@ConditionalOnWarDeployment
@ConditionalOnNotWarDeployment
SPEL 表达式条件
该 Comments 允许根据 SPEL 表达式的结果包含配置。@ConditionalOnExpression
在表达式中引用 bean 将导致该 bean 在上下文刷新处理中非常早地初始化。 因此,bean 将不符合后处理条件(例如配置属性绑定),并且其状态可能不完整。 |
如果使用 或作为元注释的一部分来编写自己的组合注释,则必须使用,因为在这种情况下不会处理对类的引用。@ConditionalOnClass @ConditionalOnMissingClass name |
您需要非常小心 bean 定义的添加顺序,因为这些条件是根据到目前为止已处理的内容进行评估的。
因此,我们建议仅在自动配置类上使用 and 注释(因为可以保证在添加任何用户定义的 bean 定义后加载这些 Comments)。@ConditionalOnBean @ConditionalOnMissingBean |
@ConditionalOnBean 并且不会阻止创建类。
在类级别使用这些条件与使用 Comments 标记每个包含的方法之间的唯一区别是,如果条件不匹配,前者会阻止将类注册为 Bean。@ConditionalOnMissingBean @Configuration @Bean @Configuration |
声明方法时,请在方法的 return 类型中提供尽可能多的类型信息。
例如,如果 Bean 的具体类实现了一个接口,则 Bean 方法的返回类型应该是具体类,而不是接口。
在使用 Bean 条件时,在方法中提供尽可能多的类型信息尤为重要,因为它们的评估只能依赖于方法签名中可用的类型信息。@Bean @Bean |
在表达式中引用 bean 将导致该 bean 在上下文刷新处理中非常早地初始化。 因此,bean 将不符合后处理条件(例如配置属性绑定),并且其状态可能不完整。 |
测试您的自动配置
自动配置可能受许多因素影响:用户配置(定义和自定义)、条件评估(存在特定库)等。
具体来说,每个测试都应该创建一个定义明确的 Well Defined,用于表示这些自定义项的组合。 提供了实现此目的的好方法。@Bean
Environment
ApplicationContext
ApplicationContextRunner
ApplicationContextRunner 在本机映像中运行测试时不起作用。 |
ApplicationContextRunner
通常定义为 Test 类的字段,用于收集基本、通用的配置。
以下示例确保始终调用该 API:MyServiceAutoConfiguration
-
Java
-
Kotlin
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
如果必须定义多个 auto-configurations,则无需对它们的声明进行排序,因为它们的调用顺序与运行应用程序时的顺序完全相同。 |
每个测试都可以使用运行程序来表示特定的使用案例。
例如,下面的示例调用用户配置 () 并检查自动配置是否正确退出。
调用提供了可与 一起使用的回调上下文。UserConfiguration
run
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")
}
}
还可以轻松自定义 ,如以下示例所示:Environment
-
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")
}
}
运行程序还可用于显示 .
报表可以打印 或 级别。
以下示例显示如何使用 在 auto-configuration tests 中打印报告。ConditionEvaluationReport
INFO
DEBUG
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? -> }
}
}
模拟 Web 上下文
如果需要测试仅在 servlet 或反应式 Web 应用程序上下文中运行的自动配置,请分别使用 or。WebApplicationContextRunner
ReactiveWebApplicationContextRunner
覆盖 Classpath
还可以测试当特定类和/或包在运行时不存在时会发生什么。
Spring Boot 附带了一个 Runner 可以轻松使用。
在以下示例中,我们断言如果不存在,则正确禁用自动配置:FilteredClassLoader
MyService
-
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")
}
}
ApplicationContextRunner 在本机映像中运行测试时不起作用。 |
如果必须定义多个 auto-configurations,则无需对它们的声明进行排序,因为它们的调用顺序与运行应用程序时的顺序完全相同。 |
创建您自己的 Starter
典型的 Spring Boot Starters包含用于自动配置和自定义给定技术基础设施的代码,我们称之为 “acme”。 为了使其易于扩展,可以将专用命名空间中的许多 Configuration Key 公开给环境。 最后,提供了一个 “starter” 依赖项,以帮助用户尽可能轻松地入门。
具体来说,自定义Starters可以包含以下内容:
-
包含 “acme” 的自动配置代码的模块。
autoconfigure
-
为模块提供依赖项的模块以及 “acme” 和通常有用的任何其他依赖项。 简而言之,添加 starter 应该提供开始使用该库所需的一切。
starter
autoconfigure
这种分为两个模块的做法是完全没有必要的。
如果 “acme” 有多种风格、选项或可选功能,那么最好将自动配置分开,因为您可以清楚地表达某些功能是可选的。
此外,您还可以制作一个 starter 来提供有关这些可选依赖项的意见。
与此同时,其他人可以只依赖该模块,并制作自己的不同意见的Starters。autoconfigure
如果自动配置相对简单且没有可选功能,那么在 starter 中合并两个模块绝对是一种选择。
命名
您应该确保为 starter 提供适当的命名空间。
不要以 , 以 开头的模块名称,即使您使用不同的 Maven 也是如此。
将来,我们可能会为您自动配置的事物提供官方支持。spring-boot
groupId
根据经验,您应该在 starter 之后命名组合模块。
例如,假设您正在为 “acme” 创建一个Starters,并将 auto-configure 模块命名为 starter 和 starter 。
如果您只有一个模块将两者组合在一起,请将其命名为 。acme-spring-boot
acme-spring-boot-starter
acme-spring-boot-starter
配置键
如果您的 starter 提供 Configuration Key,请为它们使用唯一的命名空间。
特别是,不要在 Spring Boot 使用的名称空间中包含您的键(例如、、、等)。
如果你使用相同的命名空间,我们将来可能会以破坏你的模块的方式修改这些命名空间。
根据经验,请在所有键前加上您拥有的 namespace (例如 )。server
management
spring
acme
通过为每个属性添加字段 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))
您应该只使用带有字段 Javadoc 的纯文本,因为它们在添加到 JSON 之前不会进行处理。@ConfigurationProperties |
如果你使用 with record class,那么记录组件的描述应该通过类级 Javadoc 标签提供(record 类中没有明确的实例字段来放置常规的字段级 Javadocs)。@ConfigurationProperties
@param
以下是我们在内部遵循的一些规则,以确保描述一致:
-
请勿以 “The” 或 “A” 开头描述。
-
对于类型,请以 “Whether” 或 “Enable” 开始描述。
boolean
-
对于基于集合的类型,以 “Comma-separated list” 开始描述
-
如果默认单位与毫秒不同,请使用 instead instead and 并描述默认单位,例如“如果未指定持续时间后缀,则将使用秒”。
java.time.Duration
long
-
除非必须在运行时确定,否则不要在描述中提供默认值。
确保触发元数据生成,以便 IDE 帮助也可用于您的密钥。
您可能需要查看生成的元数据 () 以确保您的密钥已正确记录。
在兼容的 IDE 中使用您自己的 starter 也是验证元数据质量的好主意。META-INF/spring-configuration-metadata.json
“autoconfigure” 模块
该模块包含开始使用该库所需的一切。
它还可能包含配置键定义(例如 )和可用于进一步自定义组件初始化方式的任何回调接口。autoconfigure
@ConfigurationProperties
您应该将库的依赖项标记为可选,以便可以更轻松地将模块包含在项目中。
如果这样做,则不会提供库,并且默认情况下, Spring Boot 会退缩。autoconfigure |
Spring Boot 使用 Comments 处理器在元数据文件 () 中收集自动配置的条件。
如果存在该文件,则使用它来紧急过滤不匹配的自动配置,这将缩短启动时间。META-INF/spring-autoconfigure-metadata.properties
使用 Maven 构建时,建议在包含 auto-configurations 的模块中添加以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
如果您直接在应用程序中定义了 auto-configurations,请确保配置 以防止目标将依赖项添加到 uber jar 中:spring-boot-maven-plugin
repackage
<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"
}
入门模块
Starters真的是一个空罐子。 它的唯一目的是提供使用库所需的依赖项。 您可以将其视为对开始所需内容的固执己见的看法。
不要对添加起始项的项目做出假设。 如果要自动配置的库通常需要其他启动程序,请同时提及它们。 如果可选依赖项的数量很高,则可能很难提供一组适当的默认依赖项,因为您应该避免包含对于库的典型使用不必要的依赖项。 换句话说,您不应包含可选依赖项。
无论哪种方式,你的 starter 都必须直接或间接引用核心 Spring Boot starter () (如果你的 starter 依赖于另一个 starter,则无需添加它)。
如果项目是仅使用您的自定义Starters创建的,则 Spring Boot 的核心功能将因核心Starters的存在而得到尊重。spring-boot-starter |
您应该只使用带有字段 Javadoc 的纯文本,因为它们在添加到 JSON 之前不会进行处理。@ConfigurationProperties |
您应该将库的依赖项标记为可选,以便可以更轻松地将模块包含在项目中。
如果这样做,则不会提供库,并且默认情况下, Spring Boot 会退缩。autoconfigure |
无论哪种方式,你的 starter 都必须直接或间接引用核心 Spring Boot starter () (如果你的 starter 依赖于另一个 starter,则无需添加它)。
如果项目是仅使用您的自定义Starters创建的,则 Spring Boot 的核心功能将因核心Starters的存在而得到尊重。spring-boot-starter |