首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring boot 如何构建自定义的 Starter

Spring boot 如何构建自定义的 Starter

作者头像
花花Binki
发布2024-10-06 15:02:04
发布2024-10-06 15:02:04
6030
举报
文章被收录于专栏:岚的工作随笔岚的工作随笔

构建 Auto-configuration

如果您在开发共享,开源或商业库的公司工作,那么大概率会需要专属的Auto-configuration。它一般出现在外部 jar 中,并且仍然交由 Spring boot 装配。

“starter” 就是这样一个由常用类库和自动配置代码所组成的,所以下面首先介绍构建Auto-configuration所需要了解的内容,其次是构建自定义Starter所需的常规步骤。

理解 Auto-configured Beans

如果你想让某类实现自动配置,可以使用@AutoConfiguration。内部构成如下

代码语言:java
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {
  • @Configuration 主要注解
  • @Conditional 注解用于标识生效条件
  • @ConditionalOnClass和@ConditionalOnMissingBean是用来在未声明的情况下时的配置。

详情参见spring-boot-autoconfigure下的org.springframework.boot.autoconfigure.AutoConfiguration

查找 Auto-configuration Candidate

Spring Boot 检查已发布的 jar 中是否存在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。

该文件应列出您的配置类,每行一个类名,如以下示例所示:

代码语言:properties
复制
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

⛱️TIP 您可以使用#字符向imports文件添加注释。 📒Note 自动配置只能通过在 imports 文件中命名来加载。用来确保它们在特定的包空间中定义,并且它们永远不会成为组件扫描的目标。此外,自动配置类不应启用组件扫描来查找其他组件。应改用特定的 @Import 注释。

如果您的配置需要按特定顺序应用,则可以在 @AutoConfiguration 注释用 @AutoConfigureBefore和 @AutoConfigureAfter 注释上使用 beforebeforeNameafter 和 afterName 属性。例如,如果你提供特定于 Web 的配置,则可能需要在 WebMvcAutoConfiguration 之后应用你的类。

如果要排序某些不应直接了解彼此的自动配置,也可以使用 @AutoConfigureOrder。该注解与常规 @Order 注解具有相同的语义,但为 auto-configuration 类提供了专用的顺序。

与标准 @Configuration 类一样,应用自动配置类的顺序仅影响其 bean 的定义顺序。这些 bean 的后续创建顺序不受影响,并且由每个 bean 的依赖关系和任何 @DependsOn 关系决定。

@ Conditional 注释

您几乎总是希望在 auto-configuration 类中包含一个或多个 @Conditional 注解。@ConditionalOnMissingBean 注解是一个常见的示例,用于允许开发人员在对您的默认值不满意时覆盖自动配置。

Spring Boot 包含许多@Conditional注释,您可以通过注释@Configuration类或单个@Bean方法在自己的代码中重用这些注释。这些注释包括:

  • Class Conditions
  • Bean Conditions Bean
  • Property Conditions
  • Resource Conditions
  • Web Application Conditions
  • SpEL Expression Conditions

Class Conditions

@ConditionalOnClass 和 @ConditionalOnMissingClass 注释允许根据特定类的存在与否包含@Configuration类。由于注释元数据是使用 ASM 解析的,因此您可以使用 value 属性来引用实际类,即使该类实际上可能并未出现在正在运行的应用程序类路径上。如果您希望使用 String 值来指定类名,也可以使用 name 属性。

ASM是一个通用的 Java 字节码操作和分析框架。它可用于修改现有类或直接以二进制形式动态生成类。ASM 提供了一些常见的字节码转换和分析算法,从中可以构建自定义的复杂转换和代码分析工具。ASM 提供与其他 Java 字节码框架类似的功能,但侧重于性能。因为它的设计和实现是尽可能小和最快的,所以它非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。

此机制不适用于通常返回类型是条件目标的@Bean方法:在方法的条件应用之前,JVM 将加载类和可能处理的方法引用,如果类不存在,则这些引用将失败。

若要处理此方案,可以使用单独的 @Configuration 类来隔离条件,如以下示例所示:

代码语言:java
复制
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,因为在这种情况下不会处理引用类。

Bean Conditions Bean

@ConditionalOnBean 和 @ConditionalOnMissingBean 注释允许根据特定 bean 的存在与否来包含 bean。您可以使用 value 属性按类型指定 bean,或使用 name 按名称指定 bean。search 属性允许您限制搜索 bean 时应考虑的ApplicationContext层次结构。

当放置在 @Bean 方法上时,目标类型默认为该方法的返回类型,如以下示例所示:

代码语言:java
复制
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 方法中提供尽可能多的类型信息尤为重要,因为它们的评估只能依赖于方法签名中可用的类型信息。

Property Conditions

@ConditionalOnProperty 注释允许根据 Spring Environment 属性包含配置。使用 prefix 和 name 属性指定应检查的属性。默认情况下,将匹配存在且不等于 false 的任何属性。您还可以使用 havingValue 和 matchIfMissing 属性创建更高级的检查。

如果在 name 属性中给出了多个名称,则所有属性都必须通过测试才能匹配条件。

Resource Conditions

@ConditionalOnResource 注释允许仅在存在特定资源时包含配置。可以使用通常的 Spring 约定来指定资源,如以下示例所示:file:/home/user/test.dat

Web Application Conditions

@ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication annotation 允许根据应用程序是否为 Web 应用程序来包含配置。基于 Servlet 的 Web 应用程序是使用 Spring WebApplicationContext、定义会话范围或具有ConfigurableWebEnvironment的任何应用程序。反应式 Web 应用程序是使用 ReactiveWebApplicationContext 或具有 ConfigurableReactiveWebEnvironment .

@ConditionalOnWarDeployment 和 @ConditionalOnNotWarDeployment annotations 允许根据应用程序是否是部署到 servlet 容器的传统 WAR 应用程序来包含配置。对于使用嵌入式 Web 服务器运行的应用程序,此条件将不匹配。

SpEL Expression Conditions

@ConditionalOnExpression 注释允许根据 SPEL 表达式的结果包含配置。

📒Note 在表达式中引用 bean 将导致该 bean 在上下文刷新处理中非常早地初始化。因此,bean 将不符合后处理条件(例如配置属性绑定),并且其状态可能不完整。

测试您的自动配置

自动配置可能受许多因素影响:用户配置 (@Bean 定义和环境自定义)、条件评估 (存在特定库) 等。具体来说,每个测试都应该创建一个定义明确的 ApplicationContext 来表示这些 customizations 的组合。ApplicationContextRunner 提供了一种实现此目的的好方法。

警告 ApplicationContextRunner 在本机映像中运行测试时不起作用。

ApplicationContextRunner 通常定义为测试类的字段,用于收集基本的通用配置。下面的示例确保始终调用 MyServiceAutoConfiguration

代码语言:java
复制
	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
		.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));

️Tip 每个测试都可以使用运行程序来表示特定的使用案例。例如,下面的示例调用用户配置 (UserConfiguration) 并检查自动配置是否正确回退。调用 run 提供可与 AssertJ 一起使用的回调上下文。

代码语言:java
复制
	@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,如以下示例所示:

代码语言:java
复制
	@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 中打印报告。

代码语言:java
复制
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...
			});
	}

}

模拟 Web 上下文

如果你需要测试仅在 servlet 或反应式 Web 应用程序上下文中运行的自动配置,请分别使用WebApplicationContextRunner或 ReactiveWebApplicationContextRunner 。

覆盖 Classpath

还可以测试当特定类和/或包在运行时不存在时会发生什么。Spring Boot 附带了一个FilteredClassLoader,运行程序可以很容易地使用它。在下面的示例中,我们断言如果 MyService 不存在,则正确禁用自动配置:

代码语言:java
复制
	@Test
	void serviceIsIgnoredIfLibraryIsNotPresent() {
		this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
			.run((context) -> assertThat(context).doesNotHaveBean("myService"));
	}

创建您自己的 Starter

典型的 Spring Boot 启动器包含用于自动配置和自定义给定技术基础设施的代码,我们称之为 “acme”。为了使其易于扩展,可以将专用命名空间中的许多 Configuration Key 公开给环境。最后,提供了一个 “starter” 依赖项,以帮助用户尽可能轻松地入门。

具体来说,自定义启动器可以包含以下内容:

  • 包含 “acme” 的自动配置代码的 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

配置 keys

如果您的 starter 提供 Configuration Key,请为它们使用唯一的命名空间。特别是,不要在 Spring Boot 使用的名称空间(例如 servermanagementspring 等)中包含您的键。如果你使用相同的命名空间,我们将来可能会以破坏你的模块的方式修改这些命名空间。根据经验,请在所有键前加上您拥有的命名空间(例如 acme)。

通过为每个属性添加字段 Javadoc 来确保记录配置键,如以下示例所示:

代码语言:java
复制
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)。

以下是我们在内部遵循的一些规则,以确保描述一致:

  • 请勿以 “The” 或 “A” 开头描述。
  • 对于布尔类型,请以 “Whether” 或 “Enable” 开始描述。
  • 对于基于集合的类型,以 “Comma-separated list” 开始描述
  • 使用 java.time.Duration 而不是 long,如果默认单位与毫秒不同,请描述默认单位,例如“如果未指定 duration 后缀,则将使用秒”。
  • 除非必须在运行时确定,否则不要在描述中提供默认值。

确保触发元数据生成,以便 IDE 帮助也可用于您的密钥。您可能需要查看生成的元数据 () META-INF/spring-configuration-metadata.json ,以确保您的密钥已正确记录。在兼容的 IDE 中使用您自己的 starter 也是验证元数据质量的好主意。

“autoconfigure” 模块

autoconfigure 模块包含开始使用该库所需的一切。它还可能包含 configuration key definition(例如 @ConfigurationProperties)和任何可用于进一步自定义组件初始化方式的回调接口。

️Tip 您应该将库的依赖项标记为可选,以便可以更轻松地将 autoconfigure 模块包含在项目中。如果这样做,则不会提供库,并且默认情况下, Spring Boot 会退缩。

Spring Boot 使用 Comments 处理器在元数据文件 ( ) 中收集有关自动配置的条件 META-INF/spring-autoconfigure-metadata.properties 。如果存在该文件,则使用它来紧急过滤不匹配的自动配置,这将缩短启动时间。

使用 Maven 构建时,建议在包含 auto-configurations 的模块中添加以下依赖项:

代码语言:xml
复制
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-autoconfigure-processor</artifactId>
	<optional>true</optional>
</dependency>

如果您直接在应用程序中定义了自动配置,请确保配置 spring-boot-maven-plugin 以防止重新打包目标将依赖项添加到 uber jar 中:

代码语言:xml
复制
<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 配置中声明依赖项,如以下示例所示:

代码语言:java
复制
dependencies {
	annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}

Starter 模块

Starter真的是一个空Jar。它的唯一目的是提供使用库所需的依赖项。您可以将其视为对开始所需内容的固执己见的看法。

不要对添加起始项的项目做出假设。如果要自动配置的库通常需要其他启动程序,请同时提及它们。如果可选依赖项的数量很高,则可能很难提供一组适当的默认依赖项,因为您应该避免包含对于库的典型使用不必要的依赖项。换句话说,您不应包含可选依赖项。

📒Note

无论哪种方式,你的 starter 都必须直接或间接地引用核心 Spring Boot starter(spring-boot-starter)(如果你的 starter 依赖于另一个 starter,则无需添加它)。如果项目是仅使用您的自定义启动器创建的,则 Spring Boot 的核心功能将因核心启动器的存在而得到尊重。

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构建 Auto-configuration
    • 理解 Auto-configured Beans
    • 查找 Auto-configuration Candidate
    • @ Conditional 注释
      • Class Conditions
      • Bean Conditions Bean
      • Property Conditions
      • Resource Conditions
      • Web Application Conditions
      • SpEL Expression Conditions
    • 测试您的自动配置
      • 模拟 Web 上下文
  • 创建您自己的 Starter
    • 命名
    • 配置 keys
    • “autoconfigure” 模块
    • Starter 模块
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档