文章摘要(AI生成)
9. 创建您自己的自动配置如果您在一家开发共享库的公司工作,或者如果您在开源或商业库中工作,您可能想要开发自己的自动配置。自动配置类可以捆绑在外部 jar 中,并且仍然可以被 Spring Boot 拾取。自动配置可以与提供自动配置代码以及您将使用的典型库的“启动器”相关联。我们首先介绍了构建您自己
9. 创建您自己的自动配置
如果您在一家开发共享库的公司工作,或者如果您在开源或商业库中工作,您可能想要开发自己的自动配置。自动配置类可以捆绑在外部 jar 中,并且仍然可以被 Spring Boot 拾取。
自动配置可以与提供自动配置代码以及您将使用的典型库的“启动器”相关联。我们首先介绍了构建您自己的自动配置所需了解的内容,然后我们继续介绍创建自定义启动器所需的典型步骤。
一个演示项目可用于展示如何逐步创建启动器。
9.1.了解自动配置的 Bean
在幕后,自动配置是通过@AutoConfiguration
注解实现的。这个注解本身是用 @Configuration
元注解的,可以使自动配置成为标准@Configuration
类。添加@Conditional
注解用于限制何时应用自动配置。通常,自动配置类使用@ConditionalOnClass
和@ConditionalOnMissingBean
注解。这确保了自动配置仅在找到相关类并且您没有声明自己的类时适用@Configuration
。
您可以浏览源代码spring-boot-autoconfigure
以查看@Configuration
Spring 提供的类(参见META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件)。
9.2. 定位自动配置候选
Spring Boot 检查发布的 jar中是否存在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件。该文件应列出您的配置类,如以下示例所示:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
您可以通过在此文件中使用#
注释。
自动配置只能 以这种方式加载。确保它们是在特定的包空间中定义的,并且它们永远不是组件扫描的目标。此外,自动配置类不应启用组件扫描以查找其他组件。应该使用 特定的 @Import
来代替。
如果您的配置需要按特定顺序应用,您可以使用@AutoConfigureAfter
或@AutoConfigureBefore
注解。例如,如果您提供特定于 Web 的配置,您的WebMvcAutoConfiguration
类可能需要在.
如果您使用@AutoConfiguration
注解,则可以使用before
、 beforeName
、after
和afterName
属性别名来代替专用注解。
如果您想订购某些彼此不应该有任何直接了解的自动配置,您也可以使用@AutoConfigureOrder
. 该注解与常规注解具有相同的语义,但@Order
为自动配置类提供了专用顺序。
与标准@Configuration
类一样,应用自动配置类的顺序只影响定义它们的 bean 的顺序。随后创建这些 bean 的顺序不受影响,由每个 bean 的依赖关系和任何@DependsOn
定义的关系决定。
9.3. 条件注解
您几乎总是希望@Conditional
在您的自动配置类中包含一个或多个注解。注解是一个常见的@ConditionalOnMissingBean
例子,如果开发人员对你的默认设置不满意,它可以让他们覆盖自动配置。
Spring Boot 包含许多注解,您可以通过注解类或单个方法@Conditional
在自己的代码中重用它们。这些注解包括:@Configuration
@Bean
9.3.1.class条件
@ConditionalOnClass
和@ConditionalOnMissingClass
注解允许根据@Configuration
特定类的存在与否来包含类。由于注解元数据是使用ASM解析的,因此您可以使用该value
属性来引用真实的类,即使该类实际上可能不会出现在正在运行的应用程序类路径中。name
如果您更喜欢使用值指定类名,也可以使用该属性String
。
此机制不适用于@Bean
通常返回类型是条件目标的方法:在方法上的条件适用之前,JVM 将加载类和可能处理的方法引用,如果类不是,则这些方法引用将失败当下。
为了处理这种情况,可以使用一个单独的@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();
}
}
}
如果您使用@ConditionalOnClass
或@ConditionalOnMissingClass
作为元注解的一部分来组成您自己的组合注解,则必须name
在不处理这种情况下使用 as 引用类。
9.3.2. bean条件
@ConditionalOnBean
和注解允许根据@ConditionalOnMissingBean
特定 bean 的存在或不存在来包含 bean。您可以使用该value
属性按类型name
指定bean 或按名称指定bean。该search
属性允许您限制ApplicationContext
在搜索 bean 时应考虑的层次结构。
放置在@Bean
方法上时,目标类型默认为方法的返回类型,如下例所示:
@AutoConfiguration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
在前面的示例中,someService
如果.SomeService``ApplicationContext
您需要非常小心添加 bean 定义的顺序,因为这些条件是根据到目前为止已处理的内容进行评估的。出于这个原因,我们建议仅在自动配置类上使用@ConditionalOnBean
和@ConditionalOnMissingBean
注解(因为保证在添加任何用户定义的 bean 定义后加载这些注解)。
@ConditionalOnBean
并且@ConditionalOnMissingBean
不要阻止@Configuration
创建类。在类级别使用这些条件和用@Bean
注解标记每个包含的方法之间的唯一区别是,如果条件不匹配 ,前者会阻止将类注册为 bean。@Configuration
声明@Bean
方法时,在方法的返回类型中提供尽可能多的类型信息。例如,如果你的 bean 的具体类实现了一个接口,那么 bean 方法的返回类型应该是具体类而不是接口。在使用 bean 条件时,在方法中提供尽可能多的类型信息@Bean
尤为重要,因为它们的评估只能依赖于方法签名中可用的类型信息。
9.3.3. 属性条件
@ConditionalOnProperty
注解允许基于 Spring Environment 属性包含配置。使用prefix
和name
属性指定应检查的属性。默认情况下,匹配任何存在但不等于false
的属性。您还可以使用havingValue
和matchIfMissing
属性创建更高级的检查。
9.3.4.资源条件
@ConditionalOnResource
注解允许仅在存在特定资源时才包含配置。可以使用通常的 Spring 约定来指定资源,如下例所示:file:/home/user/test.dat
9.3.5.网络应用条件
@ConditionalOnWebApplication
和注解允许根据@ConditionalOnNotWebApplication
应用程序是否为“Web 应用程序”来包含配置。基于 servlet 的 Web 应用程序是任何使用 Spring WebApplicationContext
、定义session
范围或具有ConfigurableWebEnvironment
. 反应式 Web 应用程序是任何使用ReactiveWebApplicationContext
或具有ConfigurableReactiveWebEnvironment
.
注解允许根据@ConditionalOnWarDeployment
应用程序是否是部署到容器的传统 WAR 应用程序来包含配置。对于使用嵌入式服务器运行的应用程序,此条件将不匹配。
9.3.6.SpEL 表达条件
@ConditionalOnExpression
注解允许基于SpEL 表达式的结果包含配置。
在表达式中引用 bean 将导致该 bean 在上下文刷新处理中很早就被初始化。结果,bean 将不适合进行后处理(例如配置属性绑定),并且其状态可能不完整。
9.4.测试您的自动配置
自动配置可能受到许多因素的影响:用户配置(定义@Bean
和定制Environment
)、条件评估(特定库的存在)等。具体来说,每个测试都应该创建一个定义良好ApplicationContext
的,代表这些定制的组合。 ApplicationContextRunner
提供了实现这一目标的好方法。
ApplicationContextRunner
通常被定义为测试类的一个字段,用于收集基本的、通用的配置。以下示例确保始终调用MyServiceAutoConfiguration
:
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
如果必须定义多个自动配置,则无需对它们的声明进行排序,因为它们的调用顺序与运行应用程序时完全相同。
每个测试都可以使用运行器来表示特定的用例。例如,下面的示例调用了用户配置 ( 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");
});
}
Runner
也可用于显示ConditionEvaluationReport
. 报告可以在INFO
或DEBUG
水平打印。以下示例显示了如何使用ConditionEvaluationReportLoggingListener
打印自动配置测试中的报告。
class MyConditionEvaluationReportingTests {
@Test
void autoConfigTest() {
new ApplicationContextRunner()
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
9.4.1.模拟 Web 上下文
如果您需要测试仅在 servlet 或响应式 Web 应用程序上下文中运行的自动配置,请分别使用WebApplicationContextRunner
或ReactiveWebApplicationContextRunner
。
9.4.2. 重写类路径
还可以测试在运行时不存在特定类和/或包时会发生什么。Spring Boot 附带一个runner 可以轻松使用的FilteredClassLoader
。在以下示例中,我们断言如果MyService
不存在,则自动配置被正确禁用:
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
9.5.创建自己的启动器(starter)
一个典型的 Spring Boot 启动器包含自动配置和自定义给定技术的基础设施的代码,我们称之为“acme”。为了使其易于扩展,可以将专用命名空间中的许多配置键暴露给环境。最后,提供了一个“starter”依赖项来帮助用户尽可能轻松地开始。
具体来说,自定义启动器可以包含以下内容:
- 包含“acme”的自动配置代码的
autoconfigure
模块。 - 提供对
starter
模块的依赖关系的autoconfigure
模块以及“acme”和通常有用的任何其他依赖关系。简而言之,添加启动器应该提供开始使用该库所需的一切。
两个模块中的这种分离绝不是必要的。如果“acme”有多种风格、选项或可选功能,那么最好将自动配置分开,因为您可以清楚地表达某些功能是可选的事实。此外,您还可以制作一个启动器来提供有关这些可选依赖项的意见。同时,其他人只能依靠autoconfigure
模块,制作自己的不同意见的starter。
如果自动配置相对简单并且没有可选功能,那么在启动器中合并两个模块绝对是一种选择。
9.5.1.命名
您应该确保为您的启动器提供适当的命名空间。即使您使用不同的 Maven groupId
,也不要使用以spring-boot
开头的模块名称。以便我们将来可能会为您自动配置的内容提供官方支持。
根据经验,您应该在启动器之后命名组合模块。例如,假设您正在为“acme”创建一个启动器,并且您命名自动配置模块acme-spring-boot
和启动器acme-spring-boot-starter
。如果您只有一个模块将两者结合起来,请将其命名为acme-spring-boot-starter
.
9.5.2. 配置键(ConfigurationProperties)
如果您的启动器提供配置键,请为它们使用唯一的命名空间。特别是,不要将您的键包含在 Spring Boot 使用的命名空间中(例如server
、management
、spring
等)。如果您使用相同的命名空间,我们将来可能会以破坏您的模块的方式修改这些命名空间。根据经验,在所有键前面加上您自己的命名空间(例如acme
)。
确保通过为每个属性添加字段 javadoc 来记录配置键,如以下示例所示:
@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 ...
}
您应该只使用带有@ConfigurationProperties
字段的纯文本Javadoc ,因为它们在添加到 JSON 之前不会被处理。
以下是我们在内部遵循的一些规则,以确保描述一致:
- 不要以“The”或“A”开始描述。
- 对于
boolean
类型,以“Whether”或“Enable”开始描述。 - 对于基于集合的类型,以“Comma-separated list”开始描述
- 如果默认单位与毫秒不同,则使用
java.time.Duration
而不是long
描述默认单位,例如“If a duration suffix is not specified, seconds will be used”。 - 除非必须在运行时确定,否则不要在描述中提供默认值。
确保触发元数据生成,以便您的密钥也可以使用 IDE 帮助。您可能需要查看生成的元数据 ( META-INF/spring-configuration-metadata.json
) 以确保正确记录您的密钥。在兼容的 IDE 中使用您自己的启动器也是验证元数据质量的好主意。
9.5.3.“自动配置”模块
autoconfigure
模块包含开始使用该库所需的一切。它还可能包含配置键定义(例如@ConfigurationProperties
)和任何回调接口,可用于进一步自定义组件的初始化方式。
您应该将库的依赖项标记为可选,以便您可以更轻松地将autoconfigure
模块包含在项目中。如果您这样做,则不会提供该库,并且默认情况下,Spring Boot 会退出。
Spring Boot 使用注解处理器来收集元数据文件 ( META-INF/spring-autoconfigure-metadata.properties
) 中的自动配置条件。如果该文件存在,它将用于急切地过滤不匹配的自动配置,这将缩短启动时间。建议在包含自动配置的模块中添加以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
如果您在应用程序中直接定义了自动配置,请确保配置了spring-boot-maven-plugin
以防止repackage
目标将依赖项添加到 fat 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 4.5 及更早版本,应在compileOnly
配置中声明依赖项,如以下示例所示:
dependencies {
compileOnly "org.springframework.boot:spring-boot-autoconfigure-processor"
}
对于 Gradle 4.6 及更高版本,应在annotationProcessor
配置中声明依赖项,如下例所示:
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
9.5.4.Starter模块
启动器实际上是一个空罐子。它的唯一目的是提供必要的依赖项以使用该库。您可以将其视为对入门所需内容的固执己见。
不要对添加启动器的项目做出假设。如果您要自动配置的库通常需要其他启动器,请同时提及它们。如果可选依赖项的数量很高,则提供一组适当的默认依赖项可能会很困难,因为您应该避免包含对于库的典型使用而言不必要的依赖项。换句话说,您不应该包含可选依赖项。
无论哪种方式,您的 starter 都必须直接或间接引用核心 Spring Boot starter ( spring-boot-starter
)(如果您的 starter 依赖于另一个 starter,则无需添加它)。如果仅使用您的自定义启动器创建项目,则 Spring Boot 的核心功能将因核心启动器的存在而受到尊重。
10. Kotlin 支持
Kotlin是一种针对 JVM(和其他平台)的静态类型语言,它允许编写简洁优雅的代码,同时提供与用 Java 编写的现有库的互操作性。
Spring Boot 通过利用其他 Spring 项目(如 Spring Framework、Spring Data 和 Reactor)中的支持来提供 Kotlin 支持。有关更多信息,请参阅Spring Framework Kotlin 支持文档。
开始使用 Spring Boot 和 Kotlin 的最简单方法是遵循这个综合教程。您可以使用start.spring.io创建新的 Kotlin 项目。如果您需要支持,请随时加入Kotlin Slack的#spring 频道,或者在Stack Overflow上使用spring
和kotlin
标签提出问题。
10.1.要求
Spring Boot 至少需要 Kotlin 1.3.x,并通过依赖管理来管理合适的 Kotlin 版本。要使用 Kotlin,org.jetbrains.kotlin:kotlin-stdlib
且org.jetbrains.kotlin:kotlin-reflect
必须存在于类路径中。也可以使用kotlin-stdlib
的变体kotlin-stdlib-jdk7
和kotlin-stdlib-jdk8
。
由于Kotlin 类默认为 final,您可能需要配置kotlin-spring插件以自动打开带有 Spring 注解的类,以便它们可以被代理。
在 Kotlin中序列化/反序列化 JSON 数据需要Jackson 的 Kotlin 模块。在类路径中找到它时会自动注册。如果 Jackson 和 Kotlin 存在但 Jackson Kotlin 模块不存在,则会记录一条警告消息。
如果在start.spring.io 上引导 Kotlin 项目,则默认提供这些依赖项和插件。
10.2. 空值安全
Kotlin 的主要功能之一是null-safety。它在编译时处理null
值,而不是将问题推迟到运行时并遇到NullPointerException
. 这有助于消除常见的错误来源,而无需使用Optional
. Kotlin 还允许使用具有可为空值的函数构造,如本Kotlin 中空安全综合指南中所述。
尽管 Java 不允许在其类型系统中表达 null 安全性,但 Spring Framework、Spring Data 和 Reactor 现在通过工具友好的注解为其 API 提供 null 安全性。默认情况下,Kotlin 中使用的 Java API 的类型被识别为放宽空检查的 平台类型。Kotlin 对 JSR 305 注解和可空性注解的支持为 Kotlin 中的相关 Spring API 提供了空值安全性。
可以通过添加-Xjsr305
带有以下选项的编译器标志来配置 JSR 305 检查:-Xjsr305={strict|warn|ignore}
. 默认行为与-Xjsr305=warn
相同。strict
值需要在从 Spring API 推断的 Kotlin 类型中考虑空安全性,但应在知道 Spring API 可空性声明甚至在次要版本之间演变并且将来可能会添加更多检查的情况下使用该值)。
尚不支持泛型类型参数、可变参数和数组元素可空性。有关最新信息,请参阅SPR-15942 。另请注意,Spring Boot 自己的 API尚未注解。
10.3. Kotlin API
10.3.1. 运行应用程序
Spring Boot 提供了一种惯用的方式来运行runApplication<MyApplication>(*args)
应用程序,如以下示例所示:
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
这是SpringApplication.run(MyApplication::class.java, *args)
的内部替代. 它还允许自定义应用程序,如以下示例所示:
runApplication<MyApplication>(*args) {
setBannerMode(OFF)
}
10.3.2. 扩展
Kotlin扩展提供了使用附加功能扩展现有类的能力。Spring Boot Kotlin API 利用这些扩展为现有 API 添加新的 Kotlin 特定便利。
提供了类似于 Spring Framework RestOperations
为Spring Framework 提供的TestRestTemplate
扩展。除其他外,这些扩展使利用 Kotlin 实体化类型参数成为可能。
10.4. 依赖管理
为了避免在类路径上混合不同版本的 Kotlin 依赖项,Spring Boot 导入了 Kotlin BOM。
使用 Maven,可以通过设置kotlin-maven-plugin
提供的kotlin.version
属性来自定义 Kotlin . 通过 Gradle,Spring Boot 插件会自动与Kotlin 插件的kotlin.version
版本对齐。
Spring Boot 还通过导入 Kotlin Coroutines BOM 来管理 Coroutines 依赖项的版本。可以通过设置kotlin-coroutines.version
属性来自定义版本。
如果一个 Kotlin 项目在start.spring.io上至少有一个反应性依赖项,则默认情况下会提供org.jetbrains.kotlinx:kotlinx-coroutines-reactor
依赖项。
10.5. @ConfigurationProperties
当@ConfigurationProperties
与@ConstructorBinding
具有不可变属性的支持类结合val
使用时,如下例所示:
@ConstructorBinding
@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
val name: String,
val description: String,
val myService: MyService) {
data class MyService(
val apiToken: String,
val uri: URI
)
}
要使用注解处理器 生成您自己的元数据,kapt
应配置spring-boot-configuration-processor
依赖项。请注意,由于 kapt 提供的模型的限制,某些功能(例如检测默认值或不推荐使用的项目)无法正常工作。
10.6.测试
虽然可以使用 JUnit 4 来测试 Kotlin 代码,但 JUnit 5 默认提供并推荐使用。JUnit 5 允许一个测试类被实例化一次,并被重用于该类的所有测试。这使得在非静态方法上使用@BeforeAll
和@AfterAll
注解成为可能,这非常适合 Kotlin。
要模拟 Kotlin 类,建议使用MockK。如果您需要Mockk
等效的 Mockito 特定@MockBean
和@SpyBean
注解,您可以使用提供类似@MockkBean
和@SpykBean
注解的SpringMockK。
10.7.资源
10.7.1. 进一步阅读
- Kotlin 语言参考
- Kotlin Slack(带有专用的#spring 频道)
- Stackoverflow
spring
和kotlin
标签 - 在浏览器中试用 Kotlin
- 博客
- 很棒的
- 教程:使用 Spring Boot 和 Kotlin 构建 Web 应用程序
- 使用 Kotlin 开发 Spring Boot 应用程序
- 带有 Kotlin、Spring Boot 和 PostgreSQL 的地理空间信使
- 在 Spring Framework 5.0 中引入 Kotlin 支持
- Spring Framework 5 Kotlin API,函数式方式
10.7.2. 例子
- spring-boot-kotlin-demo:常规 Spring Boot + Spring Data JPA 项目
- mixit:Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB
- spring-kotlin-fullstack:WebFlux Kotlin fullstack 示例,前端使用 Kotlin2js 而不是 JavaScript 或 TypeScript
- spring-petclinic-kotlin:Spring PetClinic 示例应用程序的 Kotlin 版本
- spring-kotlin-deepdive:从 Boot 1.0 + Java 到 Boot 2.0 + Kotlin 的逐步迁移
- spring-boot-coroutines-demo : 协程示例项目
11.接下来要读什么
如果您想了解有关本节中讨论的任何类的更多信息,请参阅Spring Boot API 文档,或者您可以直接浏览源代码。如果您有具体问题,请参阅操作方法部分。
评论区