对于最新稳定版本,请使用 Spring Boot 4.0.4spring-doc.cadn.net.cn

创建您自己的自动配置

如果你在一家开发共享库的公司工作,或者你在开发开源或商业库,你可能希望开发自己的自动配置。</br> Auto-configuration类可以被打包到外部jar中,并且仍然会被Spring Boot识别。</br>spring-doc.cadn.net.cn

自动配置可以关联到一个“starter”,该 starter 不仅提供自动配置代码,还包含你通常会与之一起使用的典型库。 我们首先介绍构建自己的自动配置所需了解的内容,然后继续讲解创建自定义 starter 的典型步骤spring-doc.cadn.net.cn

理解自动配置的 Bean

实现自动配置的类使用 @AutoConfiguration 注解。 该注解本身是元注解,使用了 @Configuration,使自动配置成为标准的 @Configuration 类。 额外的 @Conditional 注解用于限制自动配置的适用条件。 通常,自动配置类会使用 @ConditionalOnClass@ConditionalOnMissingBean 注解。 这确保了仅当找到相关类且您尚未声明自己的 @Configuration 时,才会应用自动配置。spring-doc.cadn.net.cn

定位自动配置候选项

Spring Boot 在您发布的jar文件中检查 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。 该文件应列出您的配置类,每行一个类名,如下例所示:spring-doc.cadn.net.cn

com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
您可以在导入文件中使用 # 字符添加注释。
在auto-配置类不是顶级类的情况下,其类名应使用$与包含类分隔,例如com.example.Outer$NestedAutoConfiguration
自动配置必须通过在导入文件中命名来加载。 请确保它们定义在特定的包空间中,并且永远不要成为组件扫描的目标。 此外,自动配置类不应启用组件扫描来查找额外的组件。 应使用特定的 @Import 注解代替。

如果您的配置需要按特定顺序应用,您可以在 @AutoConfiguration 注解上使用 beforebeforeNameafterafterName 属性,或使用专用的 @AutoConfigureBefore@AutoConfigureAfter 注解。 例如,如果您提供特定于 Web 的配置,您的类可能需要在 WebMvcAutoConfiguration 之后应用。spring-doc.cadn.net.cn

如果您想对某些自动配置进行排序,而这些配置彼此之间不应有任何直接依赖,您也可以使用 @AutoConfigureOrder。 该注解与常规的 @Order 注解具有相同的语义,但为自动配置类提供了专门的排序顺序。spring-doc.cadn.net.cn

与标准 @Configuration 类一样,自动配置类的应用顺序仅影响其 Bean 的定义顺序。 这些 Bean 随后被创建的顺序不受影响,而是由每个 Bean 的依赖关系以及任何 @DependsOn 关系决定。spring-doc.cadn.net.cn

弃用并替换自动配置类

您可能需要偶尔弃用自动配置类并提供替代方案。<br/> 例如,您可能希望更改自动配置类所在的包名。spring-doc.cadn.net.cn

自从自动配置类可能在 before/after 排序和 excludes 中被引用,你需要添加一个额外的文件来告诉 Spring Boot 如何处理替换。 为了定义替换内容,请创建一个 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements 文件,在其中指明旧类与新类之间的关联。spring-doc.cadn.net.cn

com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration
AutoConfiguration.imports 文件也应更新为引用替换类。

条件注解

您几乎总是希望在自动配置类中包含一个或多个 @Conditional 注解。 @ConditionalOnMissingBean 注解是一个常见的示例,用于允许开发者在不满意默认配置时覆盖自动配置。spring-doc.cadn.net.cn

Spring Boot 包含许多 @Conditional 注解,您可以通过注解 @Configuration 类或单独的 @Bean 方法,在自己的代码中重复使用这些注解。 这些注解包括:spring-doc.cadn.net.cn

类条件

@ConditionalOnClass@ConditionalOnMissingClass 注解允许根据特定类的存在或缺失来包含 @Configuration 类。 由于注解元数据是使用 ASM 解析的,因此您可以使用 value 属性引用实际的类,即使该类可能并未实际出现在正在运行的应用程序类路径中。 如果您更喜欢使用 String 值来指定类名,也可以使用 name 属性。spring-doc.cadn.net.cn

此机制并不以相同的方式应用于 @Bean 方法,在这些方法中,通常返回类型是条件的目标:在应用于方法的条件生效之前,JVM 将加载该类并可能处理方法引用,如果该类不存在,这些操作将会失败。spring-doc.cadn.net.cn

要处理此场景,可以使用单独的 @Configuration 类来隔离条件,如下例所示:spring-doc.cadn.net.cn

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.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 ...
class MyAutoConfiguration {

	// Auto-configured beans ...
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(SomeService::class)
	class SomeServiceConfiguration {

		@Bean
		@ConditionalOnMissingBean
		fun someService(): SomeService {
			return SomeService()
		}

	}

}
如果您使用 @ConditionalOnClass@ConditionalOnMissingClass 作为元注解的一部分来组合您自己的组合注解,则必须使用 name,因为在这种情况下引用类是不被处理的。

Bean 条件

@ConditionalOnBean@ConditionalOnMissingBean 注解允许根据特定 Bean 的存在或缺失来包含某个 Bean。 您可以使用 value 属性按类型指定 Bean,或使用 name 按名称指定 Bean。 search 属性让您在搜索 Bean 时可以限制应考虑的 ApplicationContext 层次结构。spring-doc.cadn.net.cn

当放置在 @Bean 方法上时,目标类型默认为该方法的返回类型,如下例所示:spring-doc.cadn.net.cn

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.AutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean

@AutoConfiguration
class MyAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	fun someService(): SomeService {
		return SomeService()
	}

}

在前面的示例中,如果 ApplicationContext 中尚未包含类型为 SomeService 的 bean,则将创建 someService bean。spring-doc.cadn.net.cn

您需要非常小心地添加 Bean 定义的顺序,因为这些条件是基于目前已处理的内容进行评估的。 因此,我们建议在自动配置类上仅使用 @ConditionalOnBean@ConditionalOnMissingBean 注解(因为这些注解保证会在任何用户定义的 Bean 定义被添加之后加载)。
@ConditionalOnBean@ConditionalOnMissingBean 并不会阻止 @Configuration 类的创建。 在类级别使用这些条件与为每个包含的 @Bean 方法添加注解之间的唯一区别在于:前者会在条件不匹配时阻止将 @Configuration 类注册为 Bean。
在声明 @Bean 方法时,应在方法的返回类型中提供尽可能多的类型信息。 例如,如果您的 Bean 的具体类实现了一个接口,那么该 Bean 方法的返回类型应为具体类,而不是接口。 在 @Bean 方法中提供尽可能多的类型信息尤为重要,尤其是在使用 Bean 条件时,因为它们的评估只能依赖于方法签名中可用的类型信息。

属性条件

@ConditionalOnProperty 注解允许根据 Spring Environment 属性来包含配置。 使用 prefixname 属性来指定需要检查的属性。 默认情况下,任何存在且不等于 false 的属性都会匹配。 还有一个专用的 @ConditionalOnBooleanProperty 注解,专门用于布尔类型的属性。 通过这两个注解,您还可以使用 havingValuematchIfMissing 属性创建更高级的检查。spring-doc.cadn.net.cn

如果在name属性中指定了多个名称,所有属性都必须通过测试条件才能匹配。spring-doc.cadn.net.cn

资源条件

@ConditionalOnResource 注解允许仅在存在特定资源时才包含配置。 资源可以使用常规的 Spring 约定来指定,如下例所示:file:/home/user/test.datspring-doc.cadn.net.cn

Java 后端开发技术 - Spring 框架应用条件

@ConditionalOnWebApplication@ConditionalOnNotWebApplication 注解允许根据应用程序是否为 Web 应用程序来包含配置。 基于 Servlet 的 Web 应用程序是指任何使用 Spring WebApplicationContext、定义 session 作用域或具有 ConfigurableWebEnvironment 的应用程序。 响应式 Web 应用程序是指任何使用 ReactiveWebApplicationContext 或具有 ConfigurableReactiveWebEnvironment 的应用程序。spring-doc.cadn.net.cn

@ConditionalOnWarDeployment@ConditionalOnNotWarDeployment 注解允许根据应用程序是否为部署到 Servlet 容器的传统 WAR 应用程序来包含配置。 对于使用嵌入式 Web 服务器运行的应用程序,此条件将不匹配。spring-doc.cadn.net.cn

SPEL 表达式条件

@ConditionalOnExpression 注解允许根据 SpEL 表达式 的结果来包含配置。spring-doc.cadn.net.cn

引用表达式中的bean会导致该bean在上下文刷新处理过程中很早就被初始化。 因此,该bean将无法参与后续处理(例如配置属性绑定),其状态可能不完整。

测试您的自动配置

自动配置可能受到多种因素的影响:用户配置(@Bean 定义和 Environment 自定义)、条件评估(特定库的存在)以及其他因素。 具体来说,每个测试都应创建一个明确定义的 ApplicationContext,以代表这些自定义的组合。 ApplicationContextRunner 提供了一种实现此目标的绝佳方式。spring-doc.cadn.net.cn

ApplicationContextRunner 在原生镜像中运行测试时不起作用。

ApplicationContextRunner 通常被定义为测试类的一个字段,用于收集基础且通用的配置。 以下示例确保始终调用 MyServiceAutoConfigurationspring-doc.cadn.net.cn

	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
		.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
	val contextRunner = ApplicationContextRunner()
		.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
如果需要定义多个自动配置,则无需对它们的声明进行排序,因为它们在被调用时将与运行应用程序时相同的顺序执行。

每个测试都可以使用运行程序来表示特定的用例。 例如,下面的示例调用了用户配置(UserConfiguration)并检查自动配置是否适当退避。 调用run提供了一个回调上下文,可以与AssertJ一起使用。spring-doc.cadn.net.cn

	@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,如下例所示:spring-doc.cadn.net.cn

	@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")
		}
	}

运行器也可用于显示 ConditionEvaluationReport。 报告可以在 INFODEBUG 级别打印。 以下示例展示了如何在自动配置测试中使用 ConditionEvaluationReportLoggingListener 来打印报告。spring-doc.cadn.net.cn

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 应用程序上下文中运行的自动配置,请分别使用 WebApplicationContextRunnerReactiveWebApplicationContextRunnerspring-doc.cadn.net.cn

覆盖类路径

也可以测试在运行时缺少特定类和/或包时会发生什么情况。 Spring Boot 提供了一个 FilteredClassLoader,运行器可以轻松使用它。 在以下示例中,我们断言如果 MyService 不存在,则自动配置会被正确禁用:spring-doc.cadn.net.cn

	@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")
			}
	}

创建您自己的 Starter

一个典型的Spring BootStarters包含用于自动配置和自定义特定技术基础架构的代码,让我们称之为"acme"。 为了使其易于扩展,在一个专用命名空间中可以暴露若干配置键。 最后,提供了一个单一的"Starters"依赖项,以帮助用户尽可能轻松地开始使用。spring-doc.cadn.net.cn

具体而言,自定义Starters可以包含以下内容:spring-doc.cadn.net.cn

  • The autoconfigure 模块包含spring-doc.cadn.net.cn

  • The starter 模块提供了一个依赖关系到 autoconfigure 模块,以及“acme”和其他通常有用的相关依赖。 简而言之,在项目中添加该Starters模块应该可以提供开始使用该库所需的一切。spring-doc.cadn.net.cn

此分离在两个模块之间并非必要。 如果“acme”有几种风味、选项或可选功能,那么将自动配置分离出来会更好,因为这样可以清楚地表达某些特性是可选的。 此外,您可以创建一个Starters来对这些可选依赖关系提出自己的观点。 同时,其他人也可以仅依赖于autoconfigure模块并构建自己的Starters,带有不同的观点。spring-doc.cadn.net.cn

如果自动配置相对简单且没有可选功能,则将两个模块合并到Starters中肯定是一个选项。spring-doc.cadn.net.cn

当测试“acme”功能时,您可能需要为特定测试准备自定义配置。 例如,您可以提供一种方法用内存中的替代品替换外部依赖项。 为此目的可以创建一个单独的测试作用域Starters,遵循相同的原则。spring-doc.cadn.net.cn

命名

您应该确保为您的Starters提供一个适当的命名空间。 不要以spring-boot开始您的模块名称,即使您使用不同的MavengroupId。我们将来可能会为您提供自动配置项的官方支持。spring-doc.cadn.net.cn

作为经验之谈,你应该将组合模块命名为Starters的名字。 例如,假设你正在创建一个名为"acme"的Starters,并且命名自动配置模块为acme-spring-boot和Starters为acme-spring-boot-starter。 如果只有一个模块结合了两者,则命名为acme-spring-boot-starterspring-doc.cadn.net.cn

如果"acme"也有一个测试作用域的Starters,请将其命名为acme-spring-boot-starter-testspring-doc.cadn.net.cn

配置键

如果您的Starters提供配置键,请为它们使用唯一的命名空间。 特别地,不要将您的键包含在 Spring Boot 使用的命名空间中(例如 servermanagementspring 等)。 如果您使用相同的命名空间,在未来我们可能会以打破您模块的方式修改这些命名空间。 一般来说,将所有您的键都前缀为一个您拥有的命名空间(例如 acme)。spring-doc.cadn.net.cn

确保配置键得到文档化,在每个属性字段添加Javadoc注释,例如如下示例:spring-doc.cadn.net.cn

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 提供记录组件的描述(记录类中没有显式的实例字段来放置常规的字段级别 Javadoc)。spring-doc.cadn.net.cn

这里是我们内部遵循的一些规则,以确保描述的一致性:spring-doc.cadn.net.cn

请确保触发元数据生成,以便在您的键上也能获得IDE辅助。点击此处了解详情。 您还可能希望审查生成的元数据(META-INF/spring-configuration-metadata.json),以确保您的键已正确记录。 使用与兼容IDE一起的自定义Starters也是一个好主意,以验证元数据的质量。spring-doc.cadn.net.cn

“autoconfigure”模块

autoconfigure 模块包含了开始使用该库所需的一切内容。 它还可能包含配置键定义(例如 @ConfigurationProperties)以及任何可用于进一步自定义组件初始化方式的回调接口。spring-doc.cadn.net.cn

您应该将对库的依赖标记为可选,以便更容易地在项目中包含 autoconfigure 模块。 如果这样做,该库不会被提供,并且默认情况下 Spring Boot 会退回到备用方案。

Spring Boot 使用注解处理器来收集自动配置中的条件信息(在元数据文件 META-INF/spring-autoconfigure-metadata.properties 中)。 如果该文件存在,则会使用它来积极地过滤不匹配的自动配置,从而提高启动时间。spring-doc.cadn.net.cn

当使用Maven构建时,请配置编译插件(版本3.12.0或更高版本),以添加spring-boot-autoconfigure-processor到注解处理器路径中:spring-doc.cadn.net.cn

<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,依赖项应在 annotationProcessor 配置中声明,如下例所示:spring-doc.cadn.net.cn

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

启动模块

Starters其实是一个空的jar。 它唯一的目的是提供与库一起工作的必要依赖项。 您可以将其视为对启动所需内容的一种意见。spring-doc.cadn.net.cn

不要对添加了您的Starters的项目做任何假设。 如果您正在自动配置的库通常需要其他Starters,也请提及它们。 如果可选依赖项的数量很多,提供一组合适的默认依赖项可能会很困难,因为您应该避免包含在库的典型使用中不必要的依赖项。 换句话说,您不应该包含可选依赖项。spring-doc.cadn.net.cn

要么直接,要么间接地引用核心Spring BootStarters(spring-boot-starter)。如果你的Starters依赖于其他Starters,则无需单独添加核心Starters。 如果仅使用自定义Starters创建项目,则核心Starters的存在将确保Spring Boot的核心功能得到遵循。