此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
Kotlin 中的春季项目
本节提供了一些开发 Spring 项目的具体提示和建议 在 Kotlin 中。
默认情况下的最终
默认情况下,Kotlin 中的所有类和成员函数都是final
.
这open
modifier 与 Java 的final
:它允许其他人继承自此
类。这也适用于成员函数,因为它们需要标记为open
被覆盖。
虽然 Kotlin 的 JVM 友好型设计通常与 Spring 无摩擦,但这个特定的 Kotlin 功能
如果不考虑这一事实,则可以阻止应用程序启动。这是因为
春豆(如@Configuration
带注释的类,默认情况下需要在运行时扩展以进行技术
原因)通常由 CGLIB 代理。解决方法是添加一个open
关键字和
由 CGLIB 代理的 Spring Bean 的成员函数,它可以
很快就会变得痛苦,并且违背了保持代码简洁和可预测的 Kotlin 原则。
还可以通过使用@Configuration(proxyBeanMethods = false) .
看proxyBeanMethods Javadoc了解更多详情。 |
幸运的是,Kotlin 提供了一个kotlin-spring
插件(预配置的kotlin-allopen
plugin)自动打开类
以及它们的成员函数,用于使用以下内容之一进行注释或元注释的类型
附注:
-
@Component
-
@Async
-
@Transactional
-
@Cacheable
元注释支持意味着使用@Configuration
,@Controller
,@RestController
,@Service
或@Repository
会自动打开,因为这些
注释是用@Component
.
一些涉及代理和 Kotlin 编译器自动生成最终方法的用例需要额外的
关心。例如,具有属性的 Kotlin 类将生成相关的final getter 和 setter。挨次
为了能够代理相关的方法,类型级别@Component 注释应优先于方法级别@Bean 在
命令让这些方法由kotlin-spring 插件。一个典型的用例是@Scope 及其受欢迎的@RequestScope 专业化。 |
start.spring.io 启用
这kotlin-spring
插件。因此,在实践中,您可以编写 Kotlin Bean
无需任何额外open
关键字,就像在 Java 中一样。
Spring Framework 文档中的 Kotlin 代码示例没有明确指定open 在类及其成员函数上。这些示例是为项目编写的
使用kotlin-allopen 插件,因为这是最常用的设置。 |
使用不可变类实例进行持久化
在 Kotlin 中,声明只读属性很方便,并且被认为是最佳实践 在主构造函数中,如以下示例所示:
class Person(val name: String, val age: Int)
您可以选择添加这data
关键词使编译器自动从声明的所有属性派生以下成员
在主构造函数中:
-
equals()
和hashCode()
-
toString()
形式的"User(name=John, age=42)"
-
componentN()
与属性按声明顺序对应的函数 -
copy()
功能
如以下示例所示,这允许轻松更改单个属性,即使Person
属性是只读的:
data class Person(val name: String, val age: Int)
val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
常见的持久化技术(例如 JPA)需要默认构造函数,从而阻止
一种设计。幸运的是,对于这个“默认构造函数地狱”,有一个解决方法,
由于 Kotlin 提供了kotlin-jpa
为使用 JPA 注释的类生成合成无参数构造函数的插件。
如果您需要将这种机制用于其他持久性技术,您可以配置
这kotlin-noarg
插件。
从 Kay 发布系列开始,Spring Data 支持 Kotlin 不可变类实例和
不需要kotlin-noarg 插件,如果模块使用 Spring Data 对象映射
(如MongoDB、Redis、Cassandra等)。 |
注入依赖项
有利于构造函数注入
我们的建议是尝试使用val
只读(和
不可为空)属性,
如以下示例所示:
@Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
)
具有单个构造函数的类会自动自动连接其参数。
这就是为什么不需要显式的@Autowired constructor 在所示示例中
以上。 |
如果确实需要使用字段注入,可以使用lateinit var
构建
如以下示例所示:
@Component
class YourBean {
@Autowired
lateinit var mongoTemplate: MongoTemplate
@Autowired
lateinit var solrClient: SolrClient
}
内部函数名称 mangling
Kotlin 函数与internal
可见性修饰符具有
当编译成 JVM 字节码时,它们的名字会被破坏,这在按名称注入依赖项时会产生副作用。
例如,这个 Kotlin 类:
@Configuration
class SampleConfiguration {
@Bean
internal fun sampleBean() = SampleBean()
}
转换为编译后的 JVM 字节码的 Java 表示:
@Configuration
@Metadata(/* ... */)
public class SampleConfiguration {
@Bean
@NotNull
public SampleBean sampleBean$demo_kotlin_internal_test() {
return new SampleBean();
}
}
因此,表示为 Kotlin 字符串的相关 bean 名称为"sampleBean\$demo_kotlin_internal_test"
,
而不是"sampleBean"
对于常规public
函数用例。确保在注入时使用被破坏的名称
这样的 bean 按名称,或添加@JvmName("sampleBean")
以禁用名称修改。
注入配置属性
在 Java 中,您可以使用注释(例如@Value("${property}")
). 但是,在 Kotlin 中,是用于字符串插值的保留字符。$
因此,如果您希望使用@Value
comments 时,您需要通过编写$
@Value("\${property}")
.
如果你使用 Spring Boot,你可能应该使用@ConfigurationProperties 而不是@Value 附注。 |
或者,您可以通过声明 以后PropertySourcesPlaceholderConfigurer
豆:
@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
}
您可以支持组件(例如 Spring Boot 执行器或@LocalServerPort
) 使用标准${…}
语法以及使用自定义%{…}
语法声明多个PropertySourcesPlaceholderConfigurer
bean,如以下示例 显示:
@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
setIgnoreUnresolvablePlaceholders(true)
}
@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
此外,可以通过将 这spring.placeholder.escapeCharacter.default
属性通过 JVM 系统属性(或通过SpringProperties
机制)。
已检查的异常
Java 和 Kotlin 异常处理非常接近,主要区别在于 Kotlin 将所有异常视为未检查的异常。但是,当使用代理对象(例如类或方法用@Transactional
),抛出的已检查异常将默认包装在 一UndeclaredThrowableException
.
要像在 Java 中那样获得抛出的原始异常,应该用@Throws
显式指定抛出的检查异常(例如@Throws(IOException::class)
).
注释数组属性
Kotlin 注解大多类似于 Java 注解,但数组属性(即
在 Spring 中广泛使用)的行为有所不同。如 Kotlin 文档中所述,您可以省略
这value
attribute name,与其他属性不同,并将其指定为vararg
参数。
要理解这意味着什么,请考虑@RequestMapping
(这是最广泛的
使用 Spring 注释)作为示例。此 Java 注释声明如下:
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
// ...
}
典型用例@RequestMapping
是将处理程序方法映射到特定路径
和方法。在 Java 中,您可以为注释数组属性指定单个值
它会自动转换为数组。
这就是为什么一个人可以写@RequestMapping(value = "/toys", method = RequestMethod.GET)
或@RequestMapping(path = "/toys", method = RequestMethod.GET)
.
但是,在 Kotlin 中,您必须编写@RequestMapping("/toys", method = [RequestMethod.GET])
或@RequestMapping(path = ["/toys"], method = [RequestMethod.GET])
(方括号需要
使用命名数组属性指定)。
此特定method
属性(最常见的一个)是
使用快捷方式注释,例如@GetMapping
,@PostMapping
,等。
如果@RequestMapping method 属性,则所有 HTTP 方法都会
匹配,而不仅仅是GET 方法。 |
申报地点差异
对于某些用例,在用 Kotlin 编写的 Spring 应用程序中处理泛型类型可能需要了解 Kotlin 声明站点方差,允许在声明类型时定义方差,这在仅支持 use-site 的 Java 中是不可能的 方差。
例如,声明List<Foo>
在 Kotlin 中,在概念上等同于java.util.List<? extends Foo>
因为kotlin.collections.List
被声明为interface List<out E> : kotlin.collections.Collection<E>
.
需要通过使用out
Kotlin 关键字,
例如,当编写org.springframework.core.convert.converter.Converter
从 Kotlin 类型到 Java 类型。
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
// ...
}
转换任何类型的对象时,可以使用星形投影代替*
out Any
.
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
// ...
}
Spring Framework 尚未利用声明站点方差类型信息来注入 bean, 订阅spring-framework#22313跟踪相关 进展。 |
测试
如果您使用的是 Spring Boot,请参阅此相关文档。 |
构造函数注入
如专用部分所述,
JUnit Jupiter (JUnit 5) 允许 bean 的构造函数注入,这在 Kotlin 中非常有用
为了使用val
而不是lateinit var
.您可以使用@TestConstructor(autowireMode = AutowireMode.ALL)
为所有参数启用自动布线。
您还可以将默认行为更改为ALL 在junit-platform.properties 文件,并带有spring.test.constructor.autowire.mode = all 财产。 |
@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(
val orderService: OrderService,
val customerService: CustomerService) {
// tests that use the injected OrderService and CustomerService
}
PER_CLASS
生命周期
Kotlin 允许您在反引号 () 之间指定有意义的测试函数名称。
使用 JUnit Jupiter (JUnit 5),Kotlin 测试类可以使用`
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
注释,以启用测试类的单个实例化,这允许使用@BeforeAll
和@AfterAll
非静态方法的注释,非常适合 Kotlin。
您还可以将默认行为更改为PER_CLASS 在junit-platform.properties 文件,并带有junit.jupiter.testinstance.lifecycle.default = per_class 财产。 |
以下示例演示了@BeforeAll
和@AfterAll
非静态方法的注释:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTests {
val application = Application(8181)
val client = WebClient.create("http://localhost:8181")
@BeforeAll
fun beforeAll() {
application.start()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@AfterAll
fun afterAll() {
application.stop()
}
}
类似规范的测试
您可以使用 JUnit 5 和 Kotlin 创建类似规范的测试。 以下示例显示了如何执行此作:
class SpecificationLikeTests {
@Nested
@DisplayName("a calculator")
inner class Calculator {
val calculator = SampleCalculator()
@Test
fun `should return the result of adding the first number to the second number`() {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
@Test
fun `should return the result of subtracting the second number from the first number`() {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
}