继几个月前公布 Kotlin 已在 start.spring.io 中受支持 后,我们继续努力以实现 Spring 和 Kotlin 的更好共存。Kotlin 的一个重要的特性是能与 Java 库很好地 互用 ,但想要在 Spring 中编写原汁原味的 Kotlin 代码,还有一段路要走。除了 Spring 对 Java 8 的支持让 Kotlin 受益(比如说函数式 Web 编程或是 bean 注册 API)之外,还应该有一些专为 Kotlin 准备的特性来让你的生产力提升一个档次。
这就是为什么我们在 Spring Framework 5.0 M4 上专门为 Kotlin 引进支持。我在这篇博客中对这些特性做了一下总结,好让你更顺滑地结合 Spring 和 Kotlin 使用。如果在过程中遇到问题,你能在 这里 找到 Spring bug 跟踪系统里与 Kotlin 相关的 issue。
我们对 Kotlin 支持中的一个关键构造块就是 Kotlin 扩展 。它们能对现有的 API 实现非侵入式的扩展,为利用类或者 Kotlin 的特殊类层级结构,以此提供一个更好的可选方案来向 Spring 加入 Kotlin 的专有功能特性。一些像来自于 Mario Arias 的 KotlinPrimavera 的库,已经展示了各种各样的 Kotlin 辅助程序,我们可以将它们引入到 Spring API , 从而写出更加优雅的代码。运用 Spring Framework 5,我们会在 Spring 框架依赖中集成最实用且最流行的扩展,而如今我们又多了一个新帮手!要注意 Kotlin 的扩展是要进行静态解析的,所以你先将它们导入(就像 Java 中的静态导入一样)。
Spring Framework 5.0 引入了一种注册 Bean 的新方法,作为利用 XML 或者 JavaConfig 的 @Configuration 或者 @Bean 的替代方案。简言之,它能实现用供应器 lambda 扮演工厂 Bean 的角色。
例如用 Java 代码你会这样写:
GenericApplicationContext context = new GenericApplicationContext(); context.registerBean(Foo.class); context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)) );
而使用 Kotlin, 因为被具象化的类型参数,我们可以将代码写成这样:
val context = GenericApplicationContext { registerBean<Foo>() registerBean { Bar(it.getBean<Foo>()) } }
你可以在 https://github.com/mix-it/mixit/ 看到一个同时使用了 web 和 bean 注册 API 的 Spring 应用程序的具体示例。
跟 Kotlin 扩展相关的可用 ApplicationContext 有 BeanFactoryExtensions , ListableBeanFactoryExtensions , GenericApplicationContextExtensions 以及 AnnotationConfigApplicationContextExtensions 。
Spring Framework 5.0 带来的 RouterFunctionDsl 可以让你使用干净且优雅的 Kotlin 代码来运用最近才推出的 Spring 功能性 Web API :
fun route(request: ServerRequest) = RouterFunctionDsl { accept(TEXT_HTML).apply { (GET("/user/") or GET("/users/")) { findAllView() } GET("/user/{login}") { findViewById() } } accept(APPLICATION_JSON).apply { (GET("/api/user/") or GET("/api/users/")) { findAll() } POST("/api/user/") { create() } POST("/api/user/{login}") { findOne() } } } (request)
最初基于一项来自于 Raman Gupta 的社区贡献, Spring 现在将其拿过来加以利用,实现了 Kotlin 对空值安全性的支持, 以此来判断一个 HTTP 参数是否是必需的,但不用去对必需的属性进行明确的定义了。这就意味着 @RequestParam name: String? 会被看成是非必需,而 @RequestParam name: String 是必需的意思。这个也在 Spring Messaging 上的 @Header 注解中得到了支持。
同样以类似的方式,Spring 使用 @Autowired 或者 @Inject 所进行的 Bean 注入也利用了这一信息来获悉一个 Bean 是否是必需的。@Autowired lateinit var foo: Foo 表示一个类型为 Foo 的 Bean 必需在应用程序上下文中被注册,而 @Autowired lateinit var foo: Foo? 则在这样一个 Bean 不存在的时候不会发生错误。
例如, Kotlin 被具象化的类型参数 提供了一种用于 JVM 泛型类型擦除 的方法, 因此我们引入了一些扩展来对该特性加以利用,以尽可能提供一个更好的 API。
这样就能实现提供用于 RestTemplate 的便利 API (感谢 Netflix 的 Jon Schneider 对此的贡献)。例如,要提取一个 Foo 对象的列表,用 Java 代码你要这样写:
List<Foo> result = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<List<Foo>>() { }).getBody();
或者如果你使用了一个中间人数组,就用这种方式写:
List<Foo> result = Arrays.asList(restTemplate.getForObject(url, Foo[].class));
而使用了 Spring Framework 5 扩展,你可以能够用 Kotlin 来这样写:
val result: List<Foo> = restTemplate.getForObject(url)
Spring Framework 5.0 中可用的 Web API Kotlin 扩展有 RestOperationsExtensions , ServerRequestExtensions , BodyInsertersExtensions , BodyExtractorsExtensions , ClientResponseExtensions , ModelExtensions 以及 ModelMapExtensions 。
这些扩展也提供了支持本地 Kotlin KClass 的成员函数,让你可以指定 Foo::class 参数而不是 Foo::class.java。
Reactor 是 Spring Framework 5.0 附带的响应式框架, 而在开发一个响应式 web 应用程序时,有不少可以利用上 Mono , Flux 和 StepVerifier API 的好机会。
因此今天我们也要藉由新的 reactor-kotlin 项目,来对 Reactor 中的 Kotlin 支持进行一下介绍! 它提供了能够通过写上 writingfoo.toMono() 来从任何类实例创建出 Mono 实例的扩展, 许多人会喜欢写成 Mono.just(foo)。它也支持诸如利用 stream.toFlux() 来从一个 Java 8 Stream 实例创建出一个 Flux。Iterable, CompletableFuture 以及 Throwable 扩展,还有基于 KClass 的 Reactor API 变体也得到了提供。
现在这个项目仍处于早期阶段, 因此如果你感觉这个项目还缺了点什么,请随意 贡献 出你自己的扩展吧。
到现在为止,使用 Kotlin 构建 Spring Boot 应用程序时遇到了几个问题,其中之一就是需要在每个类上添加一个 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 博客 来了解更多细节,如新的 kotlin-jpa 和 kotlin-noarg 插件。
回到5月份,Gradle 宣布 他们将支持在 Groovy 和 Kotlin 中编写构建和配置文件。这使得在 IDE 中实现自动完成和验证成为可能,因为这些文件通常是常规静态类型的 Kotlin 脚本文件。这很可能成为基于 Kotlin 的项目的自然选择,这对 Java 项目也有很大价值。
自5月以来, gradle-script-kotlin 项目不断发展,但在使用前需要谨记以下两点:
你需要使用 Kotlin 1.1-EAP IDEA 插件来支持自动完成功能(但如果你正在使用 kotlin-spring 插件,就需等待 Kotlin 1.1-M05,因为 1.1-M04 不能稳定地使用该插件)
文档还不完善,但你可以在 Kotlin Slcak 上的 #gradle 频道中找 Gradle 团队寻求帮助。
Spring-boot-kotlin-demo 和 mixit 项目都是通过基于 Kotlin 的 Gradle 版本构建的,有兴趣可以看看。我们正在 考虑 在 start.spring.io 中添加这种支持。
从 4.3 版本开始,Spring Framework 提供了一个 ScriptTemplateView ,用于利用支持 JSR-223 的脚本引擎来渲染模板。 Kotlin 1.1-M04 提供了这样的支持,并支持渲染基于 Kotlin 的模板,更为详细信息,请参阅 提交记录 。
随之也产生了一些有趣的案例,如使用 kotlinx.html DSL 编写类型安全模板或简单地使用 Kotlin 多 String 字符串插值,正如 kotlin-script-templating 项目中所示。你可以在 IDE 中编写具有自动完成功能和重构支持的一类模板:
import io.spring.demo.User import io.spring.demo.joinToLine """ ${include("header", bindings)} <h1>Title : $title</h1> <ul> ${(users as List<User>).joinToLine{ "<li>User ${it.firstname} ${it.lastname}</li>" }} </ul> ${include("footer")} """
这个功能仍在开发中,但我正在与 Kotlin 团队合作,以确保在 MVC 和 Spring Framework 5.0 GA 的 Reactive 中暂时能够与嵌套模板和支持 i18n 的功能一起工作。
用 Kotlin 写 Spring Boot 应用越多,我越是感到这两种技术具有相同的技术理念,它们能够让你写出更具有表现力、更加简洁、易读性更强的代码,从而更加有效地去进行开发工作,Spring Framework 5 对 Kotlin 的支持,更是自然、简洁、有效地整合这些技术的重要一步。
Kotlin 可以用来写 基于注解的 Spring Boot 应用 ,同时也能够很好地和 Spring Framework 5.0 中支持的新型 功能性和反应性应用 兼容。
Kotlin 工作组基本上都解决了我们反馈的问题,感谢他们的努力。马上就要发布的 Kotlin1.1 版本也将修改 KT-11235 ,以便允许指定阵列不使用 arrayof()就能赋予单值属性。主要遗留问题可能是 KT-4984 ,它要求指定特定的 lambda 类型,但其实你只需要指定一个 {} 就够了。
欢迎到 start.spring.io 建立一个 Spring Boot 2.0.0 项目来体验 Spring Framework 5.0 对 Kotlin 的支持,您可以将您的反馈发送到这里,或者是发送到 Kotlin Slack 的 #spring 频道。您也可以将您需要的 Kotlin 扩展 投稿 。