Spring Tips:Spring Integration Kotlin DSL

工程 | Josh Long | 2020年4月7日 | ...

講者:Josh Long (@starbuxman)

哈囉,各位 Spring 的粉絲!在這次的內容中,我們將看看 Spring Integration 的全新 Kotlin DSL。我之前在其他影片中介紹過 Spring Integration 和 Kotlin。我 *非常* 確定我也曾在基於 Kotlin 的 Spring 應用程式中使用 Spring Integration,但這是我第一次能夠專門介紹 Spring Integration 的 Kotlin DSL。

Spring Integration 已經存在很長一段時間了——至少 13 年——它服務於一個永恆的用例:整合不同的系統和服務。它的模式來自 Gregor Hohpe 和 Bobby Woolf 的開創性著作《企業整合模式》。這是一本很棒的書,我再怎麼熱情地推薦它都不為過,因為它以某種方式充當了理解 Spring Integration 所需的文件。Spring Integration 將書中的模式編纂成典;API 元素以書中相關模式命名。

整合必然是高階工作。它涉及到不同管道系統的輸入和輸出。您不希望花費太長時間在最低層級,在物件圖的層級上工作以使其工作。根據這些系統和服務支援的輸入和輸出,將它們解耦要容易得多。Spring Integration 為您提供了一種方法來做到這一點。

多年來,Spring Integration 的 DSL 發生了變化。我們以基於 XML 的 DSL 開始該專案,後來引入了 Java 配置元件模型,然後又引入了 Java DSL。甚至還短暫地嘗試了 Scala DSL。現在我們有了 Kotlin DSL。Kotlin DSL 建立在多年前 Spring Integration Java DSL 中奠定的基礎之上。它擴展了 DSL,使其更具 Kotlin 原生性。

在這個應用程式中,我們將建構一個應用程式來監控檔案系統。我展示這些範例是因為它們不需要您(觀眾)在您或本地機器上安裝任何東西,除了檔案系統,據推測,您已經擁有它。Spring Integration 提供了豐富的整合工具箱。您可以與檔案系統(遠端和本地)、資料庫、訊息佇列以及無數其他協議和整合進行通訊。

讓我們建構一個新的應用程式。前往 Spring Initializr 並確保選擇 Kotlin 作為語言的選擇。另外,請確保使用 Spring Boot 2.3.x 或更高版本。

然後在依賴項組合框中,選擇 Spring Integration。我們還需要新增一個特定於 Spring Integration 的依賴項。雖然這個依賴項在 Spring Initializr 上不可發現,但它由 Spring Boot 管理的,因此我們可以輕鬆地手動將其新增到我們的組建中。

點擊 Generate,然後在您最喜歡的 IDE 中開啟該專案。

前往 pom.xml 檔案並新增以下依賴項

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-file</artifactId>
    <version>${spring-integration.version}</version>
</dependency>

現在,讓我們轉向應用程式本身。實作將相當簡單。讓我們看看偽代碼。

  • 當檔案到達 input 目錄 ($HOME/Desktop/in) 時,Spring Integration inbound adapter 將注意到它的到達,然後將其轉發到...
  • ...篩選器,它將確定該條目是否為檔案(而不是目錄),然後將其傳送到....
  • ...路由器,它將根據檔案的擴展名,確定是將其傳送到 .csv 檔案、.xml 檔案還是其他所有檔案的處理程式。
  • .csv.txt 處理程式最終會將檔案移動到適當的目錄。

我們不會為檔案是其他類型的可能性指定處理程式,但這裡的可能性是無窮無盡的。您可以將錯誤的檔案轉發給一個處理程式,該處理程式會傳送電子郵件,或將某些內容寫入資料庫,或將訊息發布到 Apache Kafka 代理程式等。這裡沒有限制!

我們將有三個流程。現在我知道您剛剛閱讀了那些偽代碼要點,並且可能認為只有一個流程,但我已將 .csv 檔案的處理程式與 .txt 檔案的處理程式分離。解耦很有用,因為這意味著其他流程可能會產生檔案,然後這些檔案作為下游處理程式被路由到 csvtxt 流程。解耦支援良好的整潔架構。它允許我透過保持流程小巧且專注來重新調整流程的用途。這與適用於寫入函數的建議相同。

我們透過明智地使用 MessageChannels 來解耦流程。通道就像名稱管道——管道——訊息透過它流動。

這是我們基本的 Spring Boot 應用程式,只有匯入、我們的 main() 函數,沒有其他太多內容。

package com.example.kotlinspringintegration

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.dsl.MessageChannels
import org.springframework.integration.dsl.integrationFlow
import org.springframework.integration.file.dsl.Files
import java.io.File

@SpringBootApplication
class KotlinSpringIntegrationApplication

fun main(args: Array<String>) {
    runApplication<KotlinSpringIntegrationApplication>(*args)
}

從這裡開始,讓我們定義訊息通道。

@Configuration
class ChannelsConfiguration {

    @Bean
    fun txt() = MessageChannels.direct().get()

    @Bean
    fun csv() = MessageChannels.direct().get()

    @Bean
    fun errors() = MessageChannels.direct().get()
}

我們可以注入配置類,然後調用方法來取消引用個別通道。

讓我們看看主要的整合流程。Spring Integration Java DSL 一直很方便,但從 Kotli 語言中可以更容易地存取它。在這個配置類中,我使用 Kotlin integrationFlow 工廠函數定義一個 IntegrationFlow。它反過來採用一個 lambda,作為我掛起整合流程中各種步驟的上下文。我透過將其指向啟動流程的新訊息將到達的位置來建構 IntegrationFlow。在這種情況下,訊息在消耗來自 inbound 檔案配接器訊息後到達,該配接器每 500 毫秒監控一個目錄 ($HOME/Desktop/in)。已配置的 poller 確定輪詢新訊息的時間表。在這裡,Spring Integration Kotlin DSL 也使事情變得更容易。至少在我看來,這比原始的 Java 配置 DSL 乾淨得多。

檔案一到達,它們就被包裝在 Message<File> 中,並轉發到 filter<File> 擴展函數。請注意,在這裡我不需要指定 File.class——Kotlin 具有偽具體化泛型——參數的類型在函數本身的泛型調用中捕獲。無需類型令牌。filter 函數需要一個 lambda,它檢查目前的訊息(透過隱式參數 it 可用)並確認它是一個檔案(而不是目錄或其他東西)。如果是,則流程繼續到路由器。

然後,路由器會檢查訊息並確定應該將結果訊息轉發到哪個 outbound MessageChannel。這個路由器使用 Kotlin 的漂亮 when 表達式——有點像 Java 中增強的 switch 語句。(注意:Java 中有一個非常有希望的 switch 表達式,但是現在有多少人正在使用它?)。when 表達式產生一個值。在 Kotlin 中,函數的最後一個表達式是返回值(您很少需要指定 return)。在這種情況下,函數的最後一個表達式是 when 表達式的結果:一個 MessageChannel

@Configuration
class FileConfiguration(private val channels: ChannelsConfiguration) {

    private val input = File("${System.getenv("HOME")}/Desktop/in")
    private val output = File("${System.getenv("HOME")}/Desktop/out")
    private val csv = File(output, "csv")
    private val txt = File(output, "txt")

    @Bean
    fun filesFlow() = integrationFlow(
            Files.inboundAdapter(this.input).autoCreateDirectory(true),
            { poller { it.fixedDelay(500).maxMessagesPerPoll(1) } }
    ) {

        filter<File> { it.isFile }
        route<File> {
            when (it.extension.toLowerCase()) {
                "csv" -> channels.csv()
                "txt" -> channels.txt()
                else -> channels.errors()
            }
        }
    }
}

至此,此流程結束。訊息無處可去。我們的列車已經沒有軌道了!我們需要再鋪設兩個流程。一個是支援以 csv 結尾的檔案,另一個是支援以 txt 結尾的檔案。讓我們看看。

在相同的配置類中,新增另外兩個整合流程 bean 定義。


    @Bean
    fun csvFlow() = integrationFlow(channels.csv()) {
        handle(Files.outboundAdapter(csv).autoCreateDirectory(true))
    }

    @Bean
    fun txtFlow() = integrationFlow(channels.txt()) {
        handle(Files.outboundAdapter(txt).autoCreateDirectory(true))
    }

最後這個範例看起來應該與您到目前為止看到的非常相似,只是啟動流程的不是 inbound 配接器,而是來自訊息通道的任何訊息。第一個流程 csvFlow,在訊息從 csv 訊息通道到達時啟動。第二個流程 txtFlow 也是如此。兩個流程都非常突然地終止,只做一件事。它們將訊息轉發到一個 outbound 配接器,該配接器反過來將檔案寫入到其他目錄。outbound 配接器是 inbound 配接器的鏡像;它從 Spring Integration 流程中取得訊息,並將其傳送到現實世界中的某個接收器(檔案系統)。inbound 配接器從現實世界中取得值,並將其轉換為 Spring Integration 訊息。

至此,我們已經有了一個可運作的處理流程。我已經有點敷衍地避開了這樣一個問題:如果訊息不是 txtcsv 檔案,最終進入 errors 通道會發生什麼?正如我之前提到的,這裡沒有限制。

取得 Spring 電子報

隨時關注 Spring 電子報

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看全部