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