在 Spring Framework 5.0 中引入 Kotlin 支持

工程 | Sébastien Deleuze | 2017 年 1 月 4 日 | ...

更新:一个全面的Spring Boot + Kotlin 教程现已发布。

继几个月前我们在 start.spring.io 上引入 Kotlin 支持之后,我们继续努力确保 Spring 和 Kotlin 良好协作。Kotlin 的一个关键优势在于它与 Java 编写的库具有非常好的互操作性。但我们可以做得更好,在开发下一个 Spring 应用时编写完全地道的 Kotlin 代码。除了 Spring Framework 对 Java 8 的支持(Kotlin 应用可以利用,例如函数式 Web 或 bean 注册 API)之外,还有一些 Kotlin 专属功能,可以帮助您达到新的生产力水平。

这就是为什么我们在 Spring Framework 5.0 中引入了专门的 Kotlin 支持,我想在这篇博客文章中总结一下旨在使您在使用这些技术时获得无缝开发者体验的功能。您可以使用此链接在 Spring Framework 错误跟踪器中查找与 Kotlin 相关的问题。

我们 Kotlin 支持的一个关键组成部分是Kotlin 扩展。它们允许以非侵入性的方式扩展现有 API,为向 Spring 添加 Kotlin 专属功能提供了比工具类或 Kotlin 特定类层次结构更好的替代方案。一些库,例如来自Mario AriasKotlinPrimavera,已经展示了我们可以为 Spring API 带来各种 Kotlin 助手,以便编写更地道的代码。通过 Spring Framework 5,我们将最有用和最流行的扩展集成到 Spring Framework 依赖项中,并且正在添加新的扩展!请注意,Kotlin 扩展是静态解析的,您必须导入它们(类似于 Java 中的静态导入)。

## Spring Framework API 的空安全

Spring Framework 5.0 为所有包引入了正式的非空 API 声明,现在明确可为空的参数和返回值都添加了相应的注解。我们的可空性注解符合 JSR 305,并且一旦 KT-10942 修复后,Kotlin 将会支持它们。这将使得整个 Spring Framework API 从 Kotlin 侧实现空安全,并允许在编译时处理空值,而不是在运行时抛出 NullPointerExceptions

## 在 Spring 注解中利用 Kotlin 可空信息

最初基于社区贡献者 Raman Gupta 的贡献,Spring 现在利用Kotlin 的空安全支持来判断 HTTP 参数是否必需,而无需显式定义 required 属性。这意味着 @RequestParam name: String? 将被视为非必需,而 @RequestParam name: String 将被视为必需。Spring Messaging 的 @Header 注解也支持这一点。

类似地,使用 @Autowired@Inject 进行 Spring bean 注入时,也会利用此信息来判断 bean 是否必需。@Autowired lateinit var foo: Foo 意味着必须在应用上下文中注册一个类型为 Foo 的 bean,而 @Autowired lateinit var foo: Foo? 如果不存在该 bean 则不会引发错误。

## Spring WebFlux 函数式 DSL

Spring Framework 5.0 带来了 Kotlin 路由 DSL,它允许您使用最近宣布的Spring 函数式 Web API 来编写简洁、地道的 Kotlin 代码。

router {
    ("/blog" and accept(TEXT_HTML)).nest {
        GET("/", fooHandler::findAllView)
        GET("/{slug}", fooHandler::findOneView)
    }
    ("/api/blog" and accept(APPLICATION_JSON)).nest {
        GET("/", barHandler::findAll)
        GET("/{id}", barHandler::findOne)
    }
}

感谢 Yevhenii Melnyk 的早期原型和帮助!您可以在 https://github.com/mixitconf/mixit/ 看到使用函数式 Web API 的 Spring Boot 应用的具体示例。

## 函数式 bean 声明 DSL

Spring Framework 5.0 引入了一种使用 lambda 注册 bean 的新方法,作为使用 @Configuration@Bean 的 XML 或 JavaConfig 的替代方案。简而言之,它可以使用充当 FactoryBeanSupplier lambda 来注册 bean。

在 Java 中,例如您可以这样写:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new 
	Bar(context.getBean(Foo.class))
);

而在 Kotlin 中,实化类型参数(reified type parameters)和函数式 bean 声明 DSL 让我们只需这样写:

beans {
    bean<Foo>()
    bean { Bar(ref()) }
}

可用的 ApplicationContext 相关 Kotlin 扩展包括 BeanFactoryExtensionsListableBeanFactoryExtensionsGenericApplicationContextExtensionsAnnotationConfigApplicationContextExtensions

## RestTemplateWebClient API 的扩展

例如,Kotlin 实化类型参数为 JVM 泛型类型擦除提供了一种解决方案,因此我们引入了一些扩展来利用此特性,以便在可能的情况下提供更好的 API。

这使得能够为 RestTemplate 提供便捷的 API(感谢 Netflix 的 Jon Schneider 贡献了此功能)以及为新的 WebClient Spring WebFlux API 提供便捷的 API。例如,在 Java 中获取 Foo 对象列表时,您必须这样写:

Flux<User> users  = client.get().retrieve().bodyToFlux(User.class)

而在 Kotlin 中使用 Spring Framework 5 扩展,您将能够这样写:

val users = client.get().retrieve().bodyToFlux<User>()
// or (both are equivalent)
val users : Flux<User> = client.get().retrieve().bodyToFlux()

和 Java 一样,Kotlin 中的 users 是强类型的,但 Kotlin 智能的类型推断允许更简洁的语法。

Spring Framework 5.0 中可用的 Web API Kotlin 扩展包括 RestOperationsExtensionsServerRequestExtensionsBodyInsertersExtensionsBodyExtractorsExtensionsClientResponseExtensionsModelExtensionsModelMapExtensions

同样值得注意的是,其他 Spring 项目,例如 Spring Data MongoDB,也内置了对 Kotlin 的支持,并提供了此类扩展。

## Reactor 内置 Kotlin 支持

Reactor 是 Spring Framework 5.0 构建所依赖的响应式基础,您很有可能在开发响应式 Web 应用时使用它的 MonoFluxStepVerifier API。

因此,今天我们还在即将发布的 Reactor 3.1 版本中引入了内置 Kotlin 支持!它提供了扩展,能够通过编写 foo.toMono() 从任何类实例创建 Mono 实例,这比 Mono.just(foo) 更受许多人欢迎。它还支持例如使用 stream.toFlux() 从 Java 8 Stream 实例创建 Flux。此外还提供了 IterableCompletableFutureThrowable 的扩展以及基于 KClass 的 Reactor API 变体。

## 不再需要将 bean 类声明为 open

到目前为止,使用 Kotlin 构建 Spring Boot 应用时面临的少数痛点之一是需要在每个类及其通过 CGLIB 代理的 Spring bean 的成员函数上添加 open 关键字,例如 @Configuration 类。此要求的根本原因在于,在 Kotlin 中,类默认是 final 的

幸运的是,Kotlin 1.0.6 现在提供了一个 kotlin-spring 插件,对于使用以下注解之一进行注解或元注解的类,默认会将其类及其成员函数设置为 open:

  • @Component
  • @Async
  • @Transactional
  • @Cacheable

元注解支持意味着使用 @Configuration@Controller@RestController@Service@Repository 注解的类会自动设置为 open,因为这些注解使用 @Component 进行了元注解。

我们已经更新了 start.spring.io,使其默认启用此功能。您可以查看这篇 Kotlin 1.0.6 博客文章了解更多详情,包括与 Spring Data 实体非常有用新的 kotlin-jpakotlin-noarg 插件。

## 基于 Kotlin 的 Gradle 构建配置

回顾五月,Gradle 宣布他们将支持除了 Groovy 之外,使用 Kotlin 编写构建和配置文件。这使得您可以在 IDE 中获得完整的自动补全和验证,因为这些文件是常规的静态类型 Kotlin Script 文件。这很可能成为基于 Kotlin 项目的自然选择,但这对于 Java 项目也很有价值。

自五月以来,kotlin-dsl Gradle 项目持续发展,现在已经可以使用,但需要记住 2 个注意事项:

  • 您需要 Kotlin 1.1 IDEA 插件才能获得自动补全
  • 文档尚不完整,但 Gradle 团队在 Kotlin Slack 的 #gradle 频道上非常乐于助人,并且这将在 1.0 版本中得到改进。

spring-boot-kotlin-demomixit 项目都使用了这种基于 Kotlin 的 Gradle 构建,欢迎查阅。我们正在讨论在 start.spring.io 上添加此类支持。

## 基于 Kotlin Script 的模板

从 4.3 版本开始,Spring Framework 提供了一个 ScriptTemplateView 来使用支持 JSR-223 的脚本引擎渲染模板,而 Spring Framework 5.0 更进一步,支持国际化 (i18n) 和嵌套模板。Kotlin 1.1 提供了此支持,并允许渲染基于 Kotlin 的模板,详情请参阅此提交

这使得一些有趣的用例成为可能,例如使用 kotlinx.html DSL 编写类型安全的模板,或者简单地使用带有插值的 Kotlin 多行 String,如 kotlin-script-templating 项目中所示。这可以让您在 IDE 中编写此类模板,并获得完整的自动补全和重构支持。

import io.spring.demo.*

"""
${include("header")}
<h1>${i18n("title")}</h1>
<ul>
    ${users.joinToLine{ "<li>${i18n("user")} ${it.firstname} ${it.lastname}</li>" }}
</ul>
${include("footer")}
"""

## 结论

我用 Kotlin 编写的 Spring Boot 应用越多,就越觉得这两种技术有着相同的理念,让您能够用富有表现力、简洁且易读的代码更高效地编写应用,而 Spring Framework 5 的 Kotlin 支持是朝着以自然、简单和强大的方式结合这些技术迈出的重要一步。

Kotlin 可用于编写基于注解的 Spring Boot 应用,同时也非常适合 Spring Framework 5.0 将支持的新型函数式和响应式应用

Kotlin 团队出色地修复了我们报告的几乎所有痛点,非常感谢他们。即将发布的 Kotlin 1.2 版本预计也将修复 KT-11235,以便允许在不使用 arrayOf() 的情况下指定数组注解属性的单个值。您可能会遇到的主要剩余问题是 KT-14984,它将要求显式指定 lambda 类型,而本来只指定 { } 就应该足够了。

欢迎前往 start.spring.io 并生成一个 Spring Boot 2.0.0(里程碑或快照)项目来测试 Spring Framework 5.0 的 Kotlin 支持,并通过这里或 Kotlin Slack#spring 频道向我们发送反馈。您也可以贡献您需要的 Kotlin 扩展 ;-)

获取 Spring 邮件通讯

订阅 Spring 邮件通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速前进。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将到来的活动

查看 Spring 社区的所有即将到来的活动。

查看全部