Spring Framework 5.0 中的 Kotlin 支持简介

工程 | Sébastien Deleuze | 2017年01月04日 | ...

更新:现已提供一份全面的 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,从而为实用工具类或特定于 Kotlin 的类层次结构提供更好的替代方案,以向 Spring 添加特定于 Kotlin 的功能。像 Mario Arias 的 KotlinPrimavera 这样的库已经展示了我们可以为 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 的可空信息

Spring 最初基于 Raman Gupta 的社区贡献,现在利用 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/Spring Boot 应用程序的实际示例 中看到使用函数式 Web API 的例子。

## 函数式 Bean 声明 DSL

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

在 Java 中,您可能会这样写

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

而在 Kotlin 中,可重构类型参数和 函数式 Bean 声明 DSL 使我们能够简单地这样写

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

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

## RestTemplateWebClient API 的扩展

例如,Kotlin 的可重构类型参数为 JVM 的 泛型类型擦除提供了变通方法,因此我们引入了一些扩展来利用此功能,从而在可能的情况下提供更好的 API。

这允许为 RestTemplate(例如,感谢 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 Data MongoDB 这样的其他 Spring 项目也通过这些扩展为 Kotlin 提供了内置支持。

## Reactor Kotlin 内置支持

Reactor 是 Spring Framework 5.0 构建在其上的响应式基础,在开发响应式 Web 应用程序时,您很有可能使用其 MonoFluxStepVerifier API。

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

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

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

幸运的是,Kotlin 1.0.6 现在提供了一个 kotlin-spring 插件,该插件默认打开被以下注解或元注解注解的类的及其成员函数

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

元注解支持意味着用 @Configuration@Controller@RestController@Service@Repository 注解的类会被自动打开,因为这些注解都用 @Component 进行了元注解。

我们已更新 start.spring.io 以默认启用它。您可以查看 Kotlin 1.0.6 的这篇博文 以获取更多详细信息,包括与 Spring Data 实体一起使用的 kotlin-jpakotlin-noarg 插件。

## 基于 Kotlin 的 Gradle 构建配置

早在五月份,Gradle 就 宣布 除了 Groovy 之外,还将支持使用 Kotlin 编写构建和配置文件。这使得在 IDE 中可以获得完整的自动完成和验证功能,因为这些文件是常规的静态类型 Kotlin 脚本文件。这很可能成为基于 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 脚本的模板

从 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 社区所有即将举行的活动。

查看所有