对于最新稳定版本,请使用Spring Framework 7.0.1spring-doc.cadn.net.cn

Kotlin的春季项目

本节提供了一些针对春季项目开发的具体提示和建议 在Kotlin。spring-doc.cadn.net.cn

最终默认淘汰

默认情况下,Kotlin中的所有类和成员函数最后. 这打开类上的修饰符与Java的相反最后:这让其他人能够继承这份工作 类。这同样适用于成员函数,因为它们需要标记为打开被覆盖。spring-doc.cadn.net.cn

虽然Kotlin的JVM友好设计通常与Spring无摩擦,但这个特定的Kotlin功能 如果不考虑这一事实,可能会阻止申请启动。这是因为 春季豆(如@Configuration带注释的类,默认需要在运行时为技术用途进行扩展 原因)通常由CGLIB代理。解决方法是添加一个打开每个类别和关键词 春豆的成员函数,由CGLIB代理,该函数 很快变得痛苦,且违背了 Kotlin 保持代码简洁和可预测的原则。spring-doc.cadn.net.cn

也可以通过以下方式避免配置类的CGLIB代理@Configuration(proxyBeanMethods = false). 看proxyBeanMethodsJavadoc更多细节请阅读。

幸运的是,Kotlin提供了Kotlin-斯普林插件(预配置的Kotlin-奥尔开放插件)自动打开类 以及其成员函数,用于注释或元注释的类型,通常用以下任一 附注:spring-doc.cadn.net.cn

元注释支持意味着标注为@Configuration,@Controller,@RestController,@Service@Repository自动打开,因为这些 注释通过元注释@Component.spring-doc.cadn.net.cn

某些涉及代理和 Kotlin 编译器自动生成最终方法的用例需要额外需求 关心。例如,具有属性的 Kotlin 类将生成相关最后接球手和二传手。挨次 为了能够代理相关方法,一个类型级别@Component注释应优先于方法层面@Bean在 命令通过Kotlin-斯普林插件。一个典型的用例是@Scope而且很受欢迎@RequestScope专业化。

start.spring.io 使 这Kotlin-斯普林默认是插件。所以,实际上你可以写Kotlin豆子 无需任何额外打开关键词,如Java。spring-doc.cadn.net.cn

Spring Framework 文档中的 Kotlin 代码示例并未明确说明打开关于类及其成员函数。这些样本是为项目创作的 使用Kotlin-奥尔开放插件,因为这是最常用的配置。

使用不可变类实例实现持久化

在Kotlin中,声明只读属性既方便又被认为是最佳实践 在主构造子内,如下例所示:spring-doc.cadn.net.cn

class Person(val name: String, val age: Int)

你可以选择添加数据关键词使编译器自动从所有声明的属性中推导出以下成员 在主构造函数中:spring-doc.cadn.net.cn

正如下例所示,这允许对单个属性进行轻易修改,即使属性为只读:spring-doc.cadn.net.cn

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注释的类生成合成无arg构造器。spring-doc.cadn.net.cn

如果你需要利用这种机制来支持其他持久化技术,可以配置 这Kotlin-诺阿尔格插件。spring-doc.cadn.net.cn

截至 Kay 发布列车,Spring Data 支持 Kotlin 不可变类实例, 不需要Kotlin-诺阿尔格插件,如果模块使用 Spring Data 对象映射 (如MongoDB、Redis、Cassandra等)。

添加依赖

Favor构造子注入

我们的建议是尽量优先使用构造体注入瓦尔只读(和 在可能的情况下,不可空)属性, 如下示例所示:spring-doc.cadn.net.cn

@Component
class YourBean(
	private val mongoTemplate: MongoTemplate,
	private val solrClient: SolrClient
)
拥有单一构造器的类参数会自动接线。 这就是为什么不需要明确的@Autowired构造器在所示的例子中 以上。

如果你真的需要用场注入,可以用lateinit var构建 如下示例所示:spring-doc.cadn.net.cn

@Component
class YourBean {

	@Autowired
	lateinit var mongoTemplate: MongoTemplate

	@Autowired
	lateinit var solrClient: SolrClient
}

内部函数名称变形

Kotlin 函数与内部 可见性修饰符具有 当它们编译成JVM字节码时,名称会被扭曲,这在按名称注入依赖时会产生副作用。spring-doc.cadn.net.cn

例如,这个Kotlin类:spring-doc.cadn.net.cn

@Configuration
class SampleConfiguration {

	@Bean
	internal fun sampleBean() = SampleBean()
}

翻译为编译后的JVM字节码的Java表示:spring-doc.cadn.net.cn

@Configuration
@Metadata(/* ... */)
public class SampleConfiguration {

	@Bean
	@NotNull
	public SampleBean sampleBean$demo_kotlin_internal_test() {
		return new SampleBean();
	}
}

因此,以Kotlin字符串表示的相关豆名为“sampleBean\$demo_kotlin_internal_test”, 而不是“样本豆”对于常规公共函数使用场景。注射时一定要用那个被搞错的名字 如同 bean by name,或 add@JvmName(“样本豆”)以禁用名字篡改。spring-doc.cadn.net.cn

注入配置属性

在 Java 中,你可以通过注释(例如)注入配置属性@Value(“${property}”)). 然而,在Kotlin中, 是一个保留字符,用于字符串插值$spring-doc.cadn.net.cn

因此,如果你想使用该系统@Value在Kotlin中,你需要通过写入逃离字符$@Value(“\${property}”).spring-doc.cadn.net.cn

如果你用Spring Boot,应该用@ConfigurationProperties而不是@Value附注。

作为替代方案,你可以通过声明 以下配置豆:spring-doc.cadn.net.cn

@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
	setPlaceholderPrefix("%{")
}

你可以自定义现有代码(比如Spring Boot执行器,或者@LocalServerPort) 该 使用${…​}语法,带有配置豆,如下示例所示:spring-doc.cadn.net.cn

@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
	setPlaceholderPrefix("%{")
	setIgnoreUnresolvablePlaceholders(true)
}

@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()

已检查的例外

Java 和 Kotlin 的例外处理非常接近,主要区别在于 Kotlin 将所有异常视为 未被检查的例外情况。然而,当使用代理对象(例如类或方法)时 注释为@Transactional),投掷的检查异常默认会被 一未声明可抛弃异常.spring-doc.cadn.net.cn

要像 Java 一样投掷原始异常,方法应注释为@Throws明确指定投掷的检查异常(例如)@Throws(IOException::class)).spring-doc.cadn.net.cn

注释数组属性

Kotlin 注释大多类似于 Java 注释,但数组属性(包括 在春季广泛使用)的行为有所不同。正如Kotlin文档中解释的,你可以省略 这与其他属性不同,并指定为瓦拉格参数。spring-doc.cadn.net.cn

要理解这意味着什么,请考虑@RequestMapping(这是最广泛的之一 以 Spring 注释为例。该 Java 注释声明如下:spring-doc.cadn.net.cn

public @interface RequestMapping {

	@AliasFor("path")
	String[] value() default {};

	@AliasFor("value")
	String[] path() default {};

	RequestMethod[] method() default {};

	// ...
}

典型的使用场景@RequestMapping是将处理方法映射到特定路径 以及方法。在 Java 中,你可以为注释数组属性指定一个值, 并且它会自动转换为数组。spring-doc.cadn.net.cn

这就是为什么可以写作@RequestMapping(value = “/toys”, method = RequestMethod.GET)@RequestMapping(path = “/toys”, method = RequestMethod.GET).spring-doc.cadn.net.cn

然而,在Kotlin中,你必须写@RequestMapping(“/toys”, method = [RequestMethod.GET])@RequestMapping(path = [“/toys”], method = [RequestMethod.GET])(方括号需要 需要用命名数组属性来指定)。spring-doc.cadn.net.cn

针对此的替代方案方法属性(最常见的属性)为 使用快捷键注释,例如@GetMapping,@PostMapping,以及其他。spring-doc.cadn.net.cn

如果@RequestMapping 方法属性未指定,所有HTTP方法均未指定 匹配,而不仅仅是获取方法。

声明-地点方差

在用Kotlin编写的Spring应用程序中处理泛型类型,在某些情况下可能需要理解 Kotlin 声明-站点方差,允许在声明类型时定义方差,而这在仅支持 use-site 的 Java 中是不可能的 方差。spring-doc.cadn.net.cn

例如,声明List<Foo>在Kotlin中,概念上等价于java.util.List<?福伸出话道>因为kotlin.collections.列表被宣告为界面 List<out E> : kotlin.collections.Collection<E>.spring-doc.cadn.net.cn

需要通过使用使用 Java 类时,通用类型上的 Kotlin 关键字, 例如,当写一个org.springframework.core.convert.converter.Converter从Kotlin类型到Java类型。spring-doc.cadn.net.cn

class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
    // ...
}

在转换任何类型的物体时,可以使用星体投影 代替*出去 任何.spring-doc.cadn.net.cn

class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
    // ...
}
Spring Framework 尚未利用声明站点方差类型的信息来注入豆子, 订阅 Spring-Framework#22313 以跟踪相关内容 进展。

测试

本节讨论结合 Kotlin 和 Spring Framework 进行测试。 推荐的测试框架是 JUnit 5Mockk 用于模拟。spring-doc.cadn.net.cn

如果你正在使用Spring Boot,请参见相关文档

构造器注入

正如专门部分所述, JUnit Jupiter(JUnit 5)允许构建器注入豆子,这对Kotlin非常实用 以便使用瓦尔而不是lateinit var.你可以使用@TestConstructor(autowireMode = AutowireMode.ALL)以启用所有参数的自动接线。spring-doc.cadn.net.cn

你也可以把默认行为改成junit-platform.properties(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很合适。spring-doc.cadn.net.cn

你也可以把默认行为改成PER_CLASSjunit-platform.properties(junit-platform.properties)文件中包含junit.jupiter.testinstance.lifecycle.default = per_class财产。

以下示例展示了@BeforeAll@AfterAll非静态方法的注释:spring-doc.cadn.net.cn

@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 创建类似规范的测试。 以下示例展示了如何实现:spring-doc.cadn.net.cn

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)
     }
  }
}