SpringBoot学习(十一)创建自己的自动配置和Kotlin支持
一、创建自己的自动配置
如果您在开发共享库的公司工作,或者在开源或商业库上工作,您可能希望开发自己的自动配置。自动配置类可以绑定在外部jar中,并且仍然可以通过Spring Boot获得。
自动配置可以与一个“启动器”相关联,启动器提供自动配置代码以及您将与其一起使用的典型库。我们首先介绍构建您自己的自动配置所需的知识,然后介绍创建自定义启动程序所需的典型步骤。
有一个演示项目可以展示如何一步一步地创建初学者。https://github.com/snicoll/spring-boot-master-auto-configuration
1.理解自动配置bean
在底层,自动配置是用标准的@Configuration类实现的。附加的@条件注释用于在应用自动配置时进行约束。通常,自动配置类使用@ConditionalOnClass和@ConditionalOnMissingBean注释。这确保了只有在找到相关类以及您没有声明自己的@Configuration时才会应用自动配置。
您可以浏览Spring -boot-autoconfigure的源代码,以查看Spring提供的@Configuration类(参见META-INF/ Spring)。工厂文件)。
2.候选人的自动定位
Spring Boot会在你发布的jar包中检查是否存在META-INF/spring.factories文件的存在。该文件应该在EnableAutoConfiguration键下列出您的配置类,如下面的示例所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
自动配置只能以这种方式加载。确保它们是在特定的包空间中定义的,并且它们绝不是组件扫描的目标。此外,自动配置类不应启用组件扫描以查找其他组件。应该使用特定的@ import。
如果需要按特定顺序应用配置,可以使用@AutoConfigureAfter或@AutoConfigureBefore注释。例如,如果您提供了特定于web的配置,那么您的类可能需要在WebMvcAutoConfiguration之后应用。
如果您想排序某些自动配置,它们之间不应该有任何直接的相互了解,您也可以使用@AutoConfigureOrder。该注释与常规的@Order注释具有相同的语义,但为自动配置类提供了专用的顺序。
3.条件注释
您几乎总是希望在自动配置类中包含一个或多个@Conditional
注释。@ConditionalOnMissingBean注释是一个常见的示例,它用于允许开发人员在不满意您的默认设置时覆盖自动配置。
Spring Boot包含许多@Conditional
注释,您可以通过注释@Configuration类或单个@Bean方法在自己的代码中重用这些注释。这些注释包括:
3.1类条件
@ConditionalOnClass和@ConditionalOnMissingClass注释允许基于特定类的存在或不存在来包含@Configuration类。由于注释元数据是使用ASM解析的,所以您可以使用value属性来引用实际的类,即使这个类实际上可能不会出现在正在运行的应用程序类路径上。如果喜欢使用字符串值指定类名,也可以使用name属性。
这种机制对@Bean方法的应用方式不同,@Bean方法的返回类型通常是条件的目标:在方法上的条件应用之前,JVM将加载类和可能处理的方法引用,如果类不存在,这些引用将失败。
为了处理这种情况,可以使用一个单独的@Configuration类来隔离条件,如下面的示例所示:
@Configuration(proxyBeanMethods = false) // Some conditions public class MyAutoConfiguration { // Auto-configured beans @Configuration(proxyBeanMethods = false) @ConditionalOnClass(EmbeddedAcmeService.class) static class EmbeddedConfiguration { @Bean @ConditionalOnMissingBean public EmbeddedAcmeService embeddedAcmeService() { ... } } }
如果您使用@ConditionalOnClass或@ConditionalOnMissingClass作为元注释的一部分来组成您自己的复合注释,那么在这种情况下,您必须使用名称来引用类。
3.2 Bean条件
@ConditionalOnBean和@ConditionalOnMissingBean注释允许根据特定bean的存在或不存在来包含bean。可以使用value属性按类型指定bean,也可以使用name指定bean的名称。search属性允许您限制在搜索bean时应该考虑的ApplicationContext层次结构。
当放置在@Bean方法上时,目标类型默认为方法的返回类型,如下面的示例所示:
@Configuration(proxyBeanMethods = false) public class MyAutoConfiguration { @Bean @ConditionalOnMissingBean public MyService myService() { ... } }
在前面的示例中,如果ApplicationContext中还没有包含类型为myService的bean,则将创建myService bean。
您需要非常小心添加bean定义的顺序,因为这些条件是根据到目前为止所处理的内容来评估的。出于这个原因,我们建议在自动配置类上只使用@ConditionalOnBean和@ConditionalOnMissingBean注释(因为这些注释保证在添加任何用户定义的bean定义之后加载)。
@ConditionalOnBean和@ConditionalOnMissingBean不会阻止创建@Configuration类。在类级别使用这些条件与用注释标记每个包含的@Bean方法之间的惟一区别是,前者防止在条件不匹配时将@Configuration类注册为bean。
3.3属性条件
@ConditionalOnProperty注释允许基于Spring环境属性进行配置。使用前缀和名称属性指定应该检查的属性。默认情况下,任何存在且不等于false的属性都会被匹配。还可以使用havingValue和matchIfMissing属性创建更高级的检查。
3.4资源条件
@ConditionalOnResource注释只允许在存在特定资源时才包含配置。可以使用常见的Spring约定来指定资源,如下面的示例所示:file:/home/user/test.dat。
3.5web应用条件
根据应用程序是否是“web应用程序”,@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注释允许包括配置。基于servlet的web应用程序是任何使用Spring WebApplicationContext、定义会话范围或具有ConfigurableWebEnvironment的应用程序。响应性web应用程序是任何使用ReactiveWebApplicationContext的应用程序,或具有ConfigurableReactiveWebEnvironment的应用程序。
3.6SpEL表达式条件
@ConditionalOnExpression注释允许基于SpEL表达式的结果进行配置。
4.测试你的自动配置
自动配置可能受到许多因素的影响:用户配置(@Bean定义和环境自定义)、条件评估(特定库的存在)等等。具体来说,每个测试应该创建一个定义良好的ApplicationContext,该上下文表示那些自定义的组合。ApplicationContextRunner提供了一个很好的方法来实现这一点。
ApplicationContextRunner通常定义为测试类的一个字段,用于收集基本的公共配置。下面的示例确保总是调用UserServiceAutoConfiguration:
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(UserServiceAutoConfiguration.class));
如果必须定义多个自动配置,则不需要按照与运行应用程序时完全相同的顺序调用它们的声明。
每个测试都可以使用运行器来表示一个特定的用例。例如,下面的示例调用用户配置(UserConfiguration)并检查自动配置是否正确地回退。调用run提供了一个可以与Assert4J一起使用的回调上下文。
@Test void defaultServiceBacksOff() { this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(UserService.class); assertThat(context).getBean("myUserService").isSameAs(context.getBean(UserService.class)); }); } @Configuration(proxyBeanMethods = false) static class UserConfiguration { @Bean UserService myUserService() { return new UserService("mine"); } }
也可以轻松定制环境,如下例所示:
@Test void serviceNameCanBeConfigured() { this.contextRunner.withPropertyValues("user.name=test123").run((context) -> { assertThat(context).hasSingleBean(UserService.class); assertThat(context.getBean(UserService.class).getName()).isEqualTo("test123"); }); }
运行程序还可以用来显示状态评估报告。报告可以在INFO或DEBUG级别打印。下面的示例演示如何使用ConditionEvaluationReportLoggingListener在自动配置测试中打印报告。
@Test public void autoConfigTest { ConditionEvaluationReportLoggingListener initializer = new ConditionEvaluationReportLoggingListener( LogLevel.INFO); ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withInitializer(initializer).run((context) -> { // Do something... }); }
4.1模拟Web上下文
如果您需要测试仅在Servlet或反应性web应用程序上下文中操作的自动配置,请分别使用WebApplicationContextRunner或ReactiveWebApplicationContextRunner。
4.2重写类路径
还可以测试当特定类和/或包在运行时不存在时会发生什么。Spring Boot附带一个FilteredClassLoader,很容易被运行器使用。在下面的例子中,我们断言如果UserService不存在,自动配置将被正确禁用:
@Test void serviceIsIgnoredIfLibraryIsNotPresent() { this.contextRunner.withClassLoader(new FilteredClassLoader(UserService.class)) .run((context) -> assertThat(context).doesNotHaveBean("userService")); }
5创建自己的启动器Starter
一个完整的Spring启动程序库可能包含以下组件:
- 包含自动配置代码的autoconfigure模块。
- 为autoconfigure模块提供依赖项的starter模块,以及通常有用的库和任何附加依赖项。简单地说,添加starter应该可以提供开始使用该库所需的一切。
如果不需要将自动配置代码和依赖项管理分离开来,则可以将它们合并到一个模块中。
5.1命名
您应该确保为启动程序提供适当的名称空间。即使使用不同的Maven groupId,也不要用spring-boot启动模块名。我们可能会提供官方支持的东西,你自动配置在未来。
根据经验,您应该以starter命名组合模块。例如,假设您正在为“acme”创建一个启动程序,并将自动配置模块命名为acme-spring-boot-autoconfigure,将启动程序命名为acme-spring-boot-starter。如果只有一个模块组合了这两个模块,那么将它命名为acme-spring-boot-starter。
5.2配置键
如果初学者提供了配置键,请为它们使用惟一的名称空间。特别是,不要在Spring Boot使用的名称空间(如服务器、管理、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”开头。
- 对于布尔类型,以“Whether”或“Enable”开始描述。
- 对于基于集合的类型,以“逗号分隔的列表”开始描述
- 使用java.time。持续时间,而不是很长,并描述默认单位,如果它不同于毫秒,例如。“如果没有指定持续时间后缀,将使用秒”。
- 除非必须在运行时确定,否则不要在描述中提供默认值。
确保触发元数据生成,以便您的密钥也可以使用IDE帮助。您可能需要查看生成的元数据(META-INF/spring-configuration-metadata.json),以确保您的键得到了适当的文档记录。在一个兼容的IDE中使用您自己的starter也是验证元数据质量的一个好主意。
5.3自动配置模块
autoconfigure模块包含启动库所需的所有内容。它还可能包含配置键定义(比如@ConfigurationProperties)和任何回调接口,这些回调接口可用于进一步定制如何初始化组件。
您应该将库的依赖项标记为可选,以便能够更轻松地在项目中包含autoconfigure模块。如果这样做,就不提供这个库,并且默认情况下,Spring引导会回退。
Spring Boot使用一个注释处理器来收集元数据文件(META-INF/spring-autoconfigure-metada.properties)中的自动配置条件。如果存在该文件,则使用它来急切地筛选不匹配的自动配置,这将改进启动时间。建议在包含自动配置的模块中添加以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
在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" }
5.4启动器模块
启动器实际上是一个空罐子。它的惟一目的是提供与库一起工作所需的依赖项。你可以把它看作是开始需要做的事情的一种固执己见的观点。
不要对添加启动程序的项目做任何假设。如果您正在自动配置的库通常需要其他初学者,也要提到他们。如果可选依赖项的数量很高,那么提供一组正确的默认依赖项可能会很困难,因为您应该避免包含对于库的典型使用来说不必要的依赖项。换句话说,您不应该包含可选的依赖项。
无论哪种方式,您的启动程序必须直接或间接地引用核心Spring启动程序(spring-boot-starter)(即,如果启动程序依赖于另一个启动程序,则不需要添加它)。如果只使用自定义启动器创建项目,Spring Boot的核心特性将得到核心启动器的支持。
二、Kotlin支持
Kotlin是一种针对JVM(和其他平台)的静态类型化语言,它允许编写简洁而优雅的代码,同时提供与用Java编写的现有库的互操作性。
Spring Boot通过利用其他Spring项目(如Spring Framework、Spring Data和Reactor)中的支持来提供Kotlin支持。有关更多信息,请参见Spring框架Kotlin支持文档。
从Spring Boot和Kotlin开始的最简单方法是遵循这个全面的教程。您可以通过start.spring.io创建新的Kotlin项目。如果您需要支持,请随意加入Kotlin Slack的#spring通道,或者使用Stack Overflow上的spring和Kotlin标签提问。
1.Requirements
Spring Boot支持Kotlin 1.3.x来使用Kotlin,在类路径上必须配置org.jetbrains.kotlin:kotlin-stdlib和
org.jetbrains.kotlin:kotlin-reflect。kotlin-stdlib可以使用
kotlin-stdlib-jdk7和
kotlin-stdlib-jdk8。
由于Kotlin类在默认情况下是final,所以您可能希望配置Kotlin -spring plugin,以便自动打开带spring注释的类,以便对它们进行代理。
在Kotlin中序列化/反序列化JSON数据需要Jackson的Kotlin模块。当在类路径中找到它时,它会自动注册。如果Jackson和Kotlin存在,但是Jackson Kotlin模块不存在,则会记录一条警告消息。
默认情况下,如果在start.spring.io上启动Kotlin项目,就会提供这些依赖项和插件。
2.null安全
Kotlin的一个主要特点是null安全。它在编译时处理null值,而不是将问题延迟到运行时并遇到NullPointerException。这有助于消除常见的bug源,而无需支付可选之类的包装器的成本。Kotlin还允许使用可为空值的函数构造,如Kotlin中的空安全综合指南所述。
尽管Java不允许在其类型系统中表达空安全,但Spring框架、Spring数据和反应器现在通过工具友好的注释提供其API的空安全。在默认情况下,Kotlin中使用的Java api类型被认为是平台类型,对其空值检查是不严格的。Kotlin对JSR 305注释的支持与可空性注释相结合,为Kotlin中相关的Spring API提供了空安全性。
可以通过以下选项添加-Xjsr305编译器标志来配置JSR 305检查:-Xjsr305={strict|warn|ignore}。默认行为与-Xjsr305=warn相同。在从Spring API推断出的Kotlin类型中,需要考虑到严格值的空安全性,但是在使用时应该考虑到Spring API的可空性声明甚至可以在较小的版本之间演化,并且将来可能会添加更多的检查)。
注:不支持泛型类型参数、变量和数组元素可空性。有关最新信息,请参见sp -15942。还要注意,Spring Boot自己的API还没有注释。
3.Kotlin API
3.1启动应用
Spring Boot提供了一种使用runApplication<MyApplication>(*args)运行应用程序的惯用方法,如下例所示:
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class MyApplication fun main(args: Array<String>) { runApplication<MyApplication>(*args) }
SpringApplication.run(MyApplication::class.java, *args)
.的一个完全替代。它还允许自定义的应用程序,如下面的例子所示:
runApplication<MyApplication>(*args) {
setBannerMode(OFF)
}
3.2扩展
Kotlin扩展提供了使用附加功能扩展现有类的能力。Spring Boot Kotlin API利用这些扩展为现有API添加新的Kotlin特有的便利。
TestRestTemplate扩展,类似于Spring Framework中为RestOperations提供的那些扩展。在其他方面,扩展使得利用Kotlin具体化的类型参数成为可能。
4.依赖管理
为了避免在类路径上混合不同版本的Kotlin依赖项,Spring Boot导入了Kotlin BOM。
使用Maven, Kotlin版本可以通过Kotlin进行定制。为kotlin-maven-plugin提供了版本属性和插件管理。使用Gradle, Spring Boot插件会自动对齐kotlin。版本与版本的Kotlin插件。
Spring Boot还通过导入Kotlin协同程序BOM来管理协同程序依赖项的版本。该版本可以通过kotlin-coroutines.version进行定制。
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模型中提供的限制,一些特性(如检测默认值或废弃项)无法工作。
6.测试
虽然可以使用JUnit 4来测试Kotlin代码,但JUnit 5是默认提供的,建议使用它。JUnit 5允许对测试类进行一次实例化,并对该类的所有测试进行重用。这使得在非静态方法上使用@BeforeClass和@AfterClass注释成为可能,这非常适合Kotlin。
JUnit 5是默认的,并且提供了与JUnit 4向后兼容的老式引擎。如果您不使用它,请排除org.junit.vintange:junit-vintage-engine。您还需要将测试实例生命周期切换到“每个类”。
要模拟Kotlin类,建议使用MockK。如果您需要与特定的@MockBean和@SpyBean注释等价的Mockk,您可以使用SpringMockK,它提供了类似的@MockkBean和@SpykBean注释。
29.7. Resources
29.7.1. Further reading
-
Kotlin Slack (with a dedicated #spring channel)
-
Tutorial: building web applications with Spring Boot and Kotlin
-
A Geospatial Messenger with Kotlin, Spring Boot and PostgreSQL
29.7.2. Examples
-
spring-boot-kotlin-demo: regular Spring Boot + Spring Data JPA project
-
mixit: Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB
-
spring-kotlin-fullstack: WebFlux Kotlin fullstack example with Kotlin2js for frontend instead of JavaScript or TypeScript
-
spring-petclinic-kotlin: Kotlin version of the Spring PetClinic Sample Application
-
spring-kotlin-deepdive: a step by step migration for Boot 1.0 + Java to Boot 2.0 + Kotlin
-
spring-boot-coroutines-demo: Coroutines sample project