在 Spring Framework 5.0 中引入 Kotlin 支持

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

更新:现在提供了一个全面的 Spring Boot + Kotlin 教程

继几个月前我们推出的 start.spring.io 上的 Kotlin 支持 之后,我们一直在努力确保 Spring 和 Kotlin 可以很好地协同工作。Kotlin 的主要优势之一是它提供了与用 Java 编写的库非常好的 互操作性。但是,在开发下一个 Spring 应用程序时,还有办法更进一步,允许编写完全惯用的 Kotlin 代码。除了 Kotlin 应用程序可以利用的 Spring Framework 对 Java 8 的支持(如功能性 Web 或 Bean 注册 API)之外,还有其他专为 Kotlin 设计的功能,可以帮助您提高生产力。

因此,我们在 Spring Framework 5.0 中引入了专用的 Kotlin 支持,并且我想在本博文中总结一下旨在使您在将这些技术结合使用时获得无缝开发体验的功能。您可以使用 此链接 在 Spring Framework 错误跟踪器中查找与 Kotlin 相关的错误。

我们 Kotlin 支持的关键构建块是 Kotlin 扩展。它们允许以非侵入的方式扩展现有 API,为实用程序类或 Kotlin 特定的类层次结构提供更好的替代方案,以便为 Spring 添加专用于 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 的新方法,作为 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 提供方便的 API(感谢 Netflix 的 Jon Schneider 的贡献)以及新的 WebClient Spring WebFlux 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 应用程序时遇到的少数几个痛点之一是需要在每个类及其 Spring Bean 成员函数上添加 open 关键字,这些成员函数使用 CGLIB 代理(如 @Configuration 类)。此要求的根本原因在于,在 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 构建配置

早在今年 5 月,Gradle 宣布 除了 Groovy 之外,他们还将支持使用 Kotlin 编写构建和配置文件。这使得您可以在 IDE 中获得完整的自动完成功能和验证,因为此类文件是常规的静态类型 Kotlin 脚本文件。对于基于 Kotlin 的项目,这很可能会成为自然选择,但对于 Java 项目也同样宝贵。

自 5 月以来,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 社区中所有即将举行的活动。

查看全部