【譯】Spring Framework 5.0 中引入 Kotlin 支援

jywhltj發表於2017-07-12

本文原發於:https://hltj.me/kotlin/2017/05/23/kotlin-support-in-spring5.html

【譯註】本文是 Spring 官網博文的譯文,英文原版在:https://spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0

我們在幾個月前介紹了 start.spring.io 對 Kotlin 的支援, 我們已繼續努力以確保 SpringKotlin 更好地協作。 Kotlin 的主要優勢之一是它提供了與 Java 編寫的庫非常好的互操作性。 但是當你開發下一個 Spring 應用程式時,有一些方式可以更進一步,讓你能夠編寫完全是 Kotlin 慣用法的程式碼。 Spring 框架除了對 Java 8 的支援外,Kotlin 應用程式也可以利用像函式式 web 或者 bean 註冊 API 這些功能,另外還有 Kotlin 專用的功能,可以讓你達到新的生產力水平。

這就是為什麼我們在 Spring Framework 5.0 M4 中引入 Kotlin 專有支援,我想在這篇博文中總結一下這些功能,旨在使開發人員在使用這些技術時無縫融入。 你可以使用這個連結在 Spring 框架 bug 跟蹤系統中查詢 Kotlin 相關問題。

我們 Kotlin 支援的關鍵組成部分是 Kotlin 擴充套件。 它們允許以非侵入式的方式擴充套件現有 API,提供了比實用程式類或 Kotlin 特有類繼承結構更好的替代方法,來將 Kotlin 專用功能新增到 Spring。 像 Mario Arias 的 KotlinPrimavera 這樣的一些庫已經展示了我們可以為 Spring API 提供 Kotlin 助手的各種方式,以便能夠編寫更加合乎慣用法的程式碼。 使用 Spring Framework 5,我們將最有用與最受歡迎的擴充套件整合在 Spring 框架依賴中,並且我們也在新增新的擴充套件!請注意,Kotlin 擴充套件是靜態解析的,你必須匯入它們(類似 Java 中的靜態匯入)。

用 Kotlin 進行函式式 bean 註冊

Spring Framework 5.0 引入了一種新的方式來註冊 bean:使用 lambda 表示式作為 XML 方式或者用 @Configuration 與 @Bean 的 JavaConfig 方式的替代。 簡而言之,它能夠用 Supplier lambda 表示式充當 FactoryBean 來註冊 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>()) }
}

ApplicationContext 相關的 Kotlin 擴充套件有 BeanFactoryExtensions、 ListableBeanFactoryExtensions、 GenericApplicationContextExtensions 以及 AnnotationConfigApplicationContextExtensions

Spring Web 函式式 API,Kotlin 的方式

Spring Framework 5.0 附帶了一個 Kotlin 路由 DSL,允許你以乾淨、慣用的 Kotlin 程式碼來利用最近宣佈的 Spring 函式式 Web API

{
    ("/blog" and accept(TEXT_HTML)).route {
        GET("/", this@BlogController::findAllView)
        GET("/{slug}", this@BlogController::findOneView)
    }
    ("/api/blog" and accept(APPLICATION_JSON)).route {
        GET("/", this@BlogController::findAll)
        GET("/{id}", this@BlogController::findOne)
    }
}

感謝 Yevhenii Melnyk 的早期原型與幫助!你可以參見一個使用 函式式 web API 的 Spring Boot 應用程式的具體示例,該示例在 https://github.com/mix-it/mixit/

利用 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 不存在時並不會引發錯誤。

用於 RestTemplate 與函式式 Web API 的擴充套件

例如,Kotlin 具體化的型別引數為 JVM 泛型型別擦除提供了一種解決方法,因此我們引入了一些擴充套件來利用這一優勢儘可能提供更好的 API。

這允許為 RestTemplate 提供便利的 API(感謝 Netflix 的 Jon Schneider 對此貢獻)。 例如,要在 Java 中檢索 Foo 物件的列表,你不得不這樣寫:

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 擴充套件有 RestOperationsExtensionsServerRequestExtensionsBodyInsertersExtensionsBodyExtractorsExtensionsClientResponseExtensionsModelExtensions 以及 ModelMapExtensions

這些擴充套件還提供了支援 Kotlin 原生 KClass 的成員函式,允許你指定 Foo::class 引數而不是  Foo::class.java

Reactor Kotlin 擴充套件

Reactor 是 Spring Framework 5.0 所基於的響應式基礎,而在開發響應式 web 應用程式時,你會有很好的機會去使用其 MonoFlux 以及 StepVerifier API。

所以如今我們還通過新的 reactor-kotlin 擴充套件 專案在 Reactor 中引入 Kotlin 支援! 它提供了能夠通過任何類例項這樣寫 foo.toMono() 來建立 Mono 例項的擴充套件,當然很多人傾向於使用 Mono.just(foo)。 它也支援例如通過 stream.toFlux() 從 Java 8 Stream 例項建立 Flux。 還提供了 Iterable、 CompletableFuture 與 Throwable 擴充套件以及 KClass 基於 Reactor API 的變體。

目前該專案還在早期階段,所以如果你發現缺了點什麼,不妨貢獻自己的擴充套件。

不再需要將你的 bean 類宣告為 open

當目前為止,當你使用 Kotlin 構建 Spring Boot 應用程式時遇到的少數痛點之一就是,需要為每個由 CGLIB 如 @Configuration 類代理的 Spring bean 類及其成員函式新增 open 關鍵字。 這一要求的根本原因源於 Kotlin 中類是預設 final 的事實。

幸運的是,Kotlin 1.0.6 現在提供了一個 kotlin-spring 外掛,對於由以下註解之一標註或元標註(meta-annotated)的類,會預設開啟該類及其成員函式:

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

元註解支援意味著用 @Configuration、 @Controller、 @RestController、 @Service 或者 @Repository 標註的類會自動開啟,鑑於這些註解都已被 @Component 註解元標註。

我們已經更新了 start.spring.io 預設啟用了該外掛。 你可以看下這篇 Kotlin 1.0.6 的博文瞭解更多詳情,其中包括對 Spring Data 實體非常有用的新的 kotlin-jpa 與 kotlin-noarg 外掛。

基於 Kotlin 的 Gradle 構建配置

去年 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 不能與該外掛一起可靠運轉)

    【譯註】:目前 1.1 已釋出,該問題已不存在。

  • 其文件不夠完整,但是 Gradle 團隊對 Kotlin Slack 的 #gradle 頻道幫助很大。

spring-boot-kotlin-demo 以及 mixit 專案都使用這種基於 Kotlin 的 Gradle 構建,所以不妨看看。 我們在討論在 start.spring.io 上新增了這項支援。

基於模版的 Kotlin 指令碼

從 4.3 版開始,Spring 框架提供了一個 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.1 釋出版預計也會修復 KT-11235 以允許指定陣列註解的屬性用單個值而無需 arrayOf()。 你會面對的主要剩餘問題可能是 KT-14984:在只需指定 { } 就足夠的地方需要顯式指定 lambda 表示式的型別。

不妨這樣測試下 Spring Framework 5.0 的 Kotlin 支援:開啟 start.spring.io 並生成一個 Spring Boot 2.0.0 (SNAPSHOT) 專案,並在此(譯註:Spring 官網)或者在 Kotlin Slack 的 #spring 頻道向我們傳送你的反饋。 你還可以貢獻你需要的 Kotlin 擴充套件 ;-)

相關文章