Grails 中的 Spring Integration (第一部分)

工程 | Russ Miles | 2008 年 12 月 11 日 | ...

Spring Integration 上週釋出了 1.0 GA 版本,因此,受到 SpringONE Americas 上 Adrian 的主題演講(不,不是 Monty Python 小品,而是 Grails 現場編碼示例)的啟發,我認為展示如何在 Grails 應用這個稍微不同的環境中利用 Spring Integration 會很有趣。

請注意:本文轉載自我的部落格 @ www.russmiles.com

本系列文章將探討如何在 Grails 中以多種配置方式新增 Spring Integration,最終形成一個完整的 Spring Integration Grails 外掛。這更像是一本線上日記,你將有機會看到我們如何透過將 Spring Integration 引導(bootstrapping)到 Grails 應用中來邁出第一步,然後透過使用 Spring Integration 的一些更高階功能來跨不同基礎設施橋接訊息,最後應用這些經驗來建立一個 Grails 外掛,你可以使用該外掛快速輕鬆地將 Spring Integration 新增到你自己的 Grails 專案中。

如果你想在閱讀本文時擁有完成的專案,完整的 Grails 專案原始碼可在此處下載(注意:基於 Grails 1.1 Beta 1 構建)

Grails (簡單介紹)

Grails 是一個基於 Groovy 語言的動態的、約定優於配置的應用框架,它在底層使用了 Spring。這種對 Spring 的使用使得向 Grails 應用新增Spring 組合專案的功能變得非常容易,這正是我們將在本文中從 Spring Integration 專案的角度來看的內容。

Spring Integration (簡單介紹)

對於任何還沒有機會使用 Spring Integration 的人來說,簡單來說,它是一組輕量級的面向訊息的庫集合,你可以在你的基於 Spring 的應用中使用它們。為此,Spring Integration 有一些核心概念,理解它們很有幫助
  • 訊息(Messages) 訊息是任何面向訊息中介軟體的核心抽象,Spring Integration 也不例外。訊息可以由任何 Java(因此也包括 Groovy)型別組成,然後使用通道從端點傳遞到另一個端點...
  • 端點(Endpoints) 端點是想要傳送或接收訊息的元件。有時,從訊息傳送到送達的管道鏈中涉及多個端點。為此,端點本身是一個奇怪的名字,因為實際上並沒有什麼在端點處真正結束。
  • 通道(Channels) 這些是當你的訊息從一個端點傳輸到另一個端點時儲存訊息的管道。通道有多種型別,包括 Direct 通道(訊息在同一執行緒中以阻塞方式從一個端點傳遞到另一個端點),這是未指定其他行為時的預設通道型別,以及 PublishAndSubscribe 通道(訊息以非同步、非阻塞方式傳送給所有訂閱的消費者)。

入門 - 建立示例應用

任何 Grails 開發的第一步是建立一個應用來開展工作。一個 Grails 應用由許多目錄和檔案組成,所有這些在 Grails 約定優於配置的方法下都有隱含的含義。

要建立你的 Grails 應用,你只需確保你的 Grails 安裝已新增到路徑中,然後從命令列輸入以下命令

> grails create-app grails-plus-integration-demo

如果一切正常,你應該會看到以下輸出

... 首先顯示其他輸出,然後 ...

已在 <你的目錄>/grails-plus-integration-demo 建立 Grails 應用

Grails 現在已在你輸入 grails create-app 命令的目錄中建立了一個名為 grails-plus-integration-demo 的應用。切換目錄到你的新 Grails 應用,然後執行它以確保一切正常

> cd grails-plus-integration-demo > grails run-app

你應該會看到如下輸出

... 其他輸出,然後 ...

伺服器正在執行。請訪問 https://:8080/grails-plus-integration-demo

開啟你選擇的瀏覽器,導航到你的正在執行的 Grails 應用地址,應該是 https://:8080/grails-plus-integration-demo,你應該會看到如下內容

圖 1. 你的新 Grails 應用,已啟動並執行

好的,目前為止沒什麼驚天動地的內容,但現在我們已準備好建立一些功能,這些功能最終將使用 Spring Integration 訊息管道。要從命令列關閉你的 Grails 應用,請按 Ctrl-C。

建立領域物件

如果你正在實踐領域驅動設計,那麼典型的下一步是建立一些領域物件。Grails 的設計考慮到了這種方法,並提供了一些預設命令來幫助你入門。

對於本文中我們非常簡單的示例應用,我們只需要一個領域物件,巧妙地命名為 "GreetingsMessage"。為此,請在你的新應用目錄中執行以下 Grails 命令

> grails create-domain-class GreetingsMessage

然後你應該會看到如下內容

正在執行指令碼 /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateDomainClass.groovy 環境設定為 development 已建立 GreetingsMessage 的 DomainClass 已建立 GreetingsMessage 的測試

建立 GreetingsMessage 領域類後,是時候新增一些屬性了。為此,你需要編輯當前位於 grails-app/domain 目錄下的 GreetingsMessage.groovy 檔案。我們的示例只需要新增一行,以便我們可以將字串訊息作為 GreetingsMessage 物件的內容傳送出去

圖 2. 為你的領域類新增一個屬性

儲存你更新後的 GreetingsMessage 領域類,就完成了。

建立控制器和表單檢視

接下來我們需要建立一個控制器和一個檢視,以便建立我們的 GreetingsMessage 例項,然後最終將它們傳送透過 Spring Integration 管道。現在,如果我們只是簡單地將 GreetingsMessage 物件持久化到資料庫中,那麼我們可以使用 grails generate-all 命令來立即獲取我們需要的所有控制器和檢視。然而,在這個示例應用中,我們只想建立一個控制器,允許使用者輸入訊息內容的字串,然後將其路由到特定的服務端點,所以我們將手動建立一個簡單的控制器和檢視來完成這項工作。

使用命令列,建立一個名為 "GreetingsMessageSender" 的 Grails 控制器

> grails create-controller GreetingsMessageSender

此命令應該會輸出如下內容

正在執行指令碼 /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateController.groovy 環境設定為 development 已建立 GreetingsMessageSender 的 Controller [mkdir] 已建立目錄: /Users/russellmiles/project/grails-plus-integration-demo/grails-app/views/greetingsMessageSender 已建立 GreetingsMessageSender 的測試

你現在已經建立了一個空的控制器及其功能的單元測試。Grails 還在你的 grail-app/views 目錄下建立了一個名為 greetingsMessageSender 的空目錄,當你新增一些控制器方法後,該目錄將包含你的檢視模板。

下一步是使用我們希望控制器支援的方法來更新它。開啟並更新 grails-app/controllers/GreetingsMessageSender.groovy,使其與以下內容匹配

圖 3. (部分)完成的 GreetingsMessageSender 控制器

如果你對 Groovy 不太熟悉,那麼下面是該類的程式碼正在做的事情

  • 當訪問控制器且沒有呼叫其他特定操作時,會執行 index 閉包。目前,這個 index 閉包只是簡單地將瀏覽器重定向到 send 操作。
  • 無論何時瀏覽器訪問此控制器上的 send 操作時,都會執行 send 閉包。目前,此程式碼只是簡單地在傳遞到 send 閉包的 HTTP 引數中查詢 'content' 條目,然後,如果非空,則設定 flash 訊息為一些簡單文字,這些文字隨後可以在關聯的檢視中渲染。
那麼關聯的檢視在哪裡呢?現在是時候建立一個了。儲存你的控制器,然後在 grails-app/views/greetingsMessageSender 目錄下建立一個新檔案,命名為 send.gsp

send.gsp 檢視中有很多程式碼,所以我建議你在此處下載程式碼,而不是自己全部輸入。不過如果你很勇敢,你可以將下面顯示的程式碼複製到你的 send.gsp 檔案中

圖 4. 完成的 send.gsp 檢視

檢視中需要關注的關鍵程式碼是 g:form 和 flash.message 部分。g:form 包含一個單行輸入文字框,該文字框將表單中的內容欄位填充到 HTTP 引數中,當表單提交時,GreetingsMessageSenderController 上的 send 閉包將獲取這些引數。

flash.message 程式碼顯示 flash 集合的內容,在本例中是我們由 send 閉包新增的訊息。

你現在可以執行並嘗試這個簡單的介面了,儘管我們目前不期望它做太多事情。再次使用 grails run-app 命令執行你的應用,然後導航到你的應用主頁來試用它

圖 5. 你的 Grails 應用主頁上新的控制器連結

點選 GreetingsMessageController 會執行 'index' 方法,然後你的瀏覽器會被重定向到 send 閉包,接著你應該會看到你的新表單

圖 6. 你的傳送表單

你可以在文字框中輸入訊息,點選“傳送”,然後,嗯,什麼都不會發生... 暫時不會。

加入一份 Spring Integration,充分混合

到目前為止,你建立了一個非常簡單的 Grails 應用,但現在是時候加入一些 Spring Integration 了。Spring Integration 提供了一種將應用內部和外部元件進行松耦合的絕佳方式,這正是我們在這裡要做的。

計劃是建立一個服務,該服務接收一個字串,然後簡單地將其轉換為大寫。這沒什麼太令人興奮的,但精彩之處在於:我們將使我們的前端控制器完全不知道該服務的控制器在哪裡,也不知道它支援什麼介面。當然,我們可以簡單地將服務注入到控制器中,但這會在介面層面將控制器與服務緊密耦合。我們在這裡要做的是將服務與控制器之間的契約體現為僅透過傳遞的訊息型別,在本例中是包含單個字串的訊息。

注意: 當我提到契約和訊息傳遞時,SOA 領域的人士可能會豎起耳朵,意識到這與服務的目標非常接近,在服務中你將契約宣告為將與服務終點交換的訊息型別。這絕對是有意為之的,但值得一提的是,儘管 Spring Integration 是 SOA 實踐的絕佳促進者,但它遠不像擁有一個分散式、單體 ESB 那樣重量級,即使它共享一些相同的架構基礎(如訊息傳遞和端點)。典型的 ESB 實現往往會導致昂貴的 Web 服務呼叫來完成最微不足道的交換,並帶來各種包袱,而 Spring Integration 則允許完全控制,可以從執行緒內訊息傳遞、多執行緒程序內輪詢,一直擴充套件到程序間/機器間交換(如果需要的話)(本系列後續文章中將詳細介紹)。

第一步是將 Spring Integration 庫新增到我們的示例應用中。Grails 為應用直接依賴的庫提供了特定的、每個應用的存放位置。最簡單的方法是將 Spring Integration 分發包的 dist 目錄內容複製到我們的 Grails 應用的 lib 目錄中。

複製 Spring Integration dist 目錄的內容...

... 到你的 Grails 應用根目錄下的 lib 目錄中。

新增這些庫使得 Spring Integration 可用於你的 Grails 應用。在建立了將為該管道提供服務的元件後,你現在已準備好建立你的 Spring Integration 管道了。

建立 Grails Service 作為管道端點

Grails 包含服務的概念,因此我們將建立一個簡單的服務,它將接收一個傳入的字串訊息並將其內容轉換為大寫。要建立該服務,請使用以下 Grails 命令

> grails create-service DemoBean

執行此命令應該會得到類似於以下的輸出

正在執行指令碼 /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateService.groovy 環境設定為 development 已建立 DemoBean 的 Service 已建立 DemoBean 的測試

現在 Grails 已經在 grails-app/services 目錄中建立了一個 DemoBeanService。它不是最有趣的服務,但它將用於說明 Spring Integration 機制。

要完成該服務,請開啟 grails-app/services 目錄下的 DemoBeanService.groovy 檔案,並按如下所示完成它

圖 7. 你完成的 DemoBeanService 類

該服務有一個方法,其目的是在返回任何傳入字串之前將其轉換為大寫。既然你已經有了一個服務,當你使用者將字串提交到控制器時想要呼叫它,那麼是時候將這兩者連線起來了。

現在,我們可以直接使用 DI,但對於這個示例,我們將透過使用 Spring Integration 來處理將傳入的字串傳遞給我們配置的特定服務,從而奠定更靈活架構的基礎。

建立 Spring Integration 管道

為了連線我們的前端 Grails 控制器和後端服務,我們將使用一個簡單的 Spring Integration 管道。Spring Integration 使用 Spring 配置的領域特定方言驅動,該方言有自己的名稱空間,以使配置儘可能簡潔。

Grails 支援使用約定將現有的 Spring XML 配置新增到應用中,該約定將配置儲存在 grails-app/conf/spring 目錄下的 resources.xml 檔案中。為了節省你的打字時間,請在此處下載該檔案,然後將其放入你的 grails-app/conf/spring 目錄中。

接下來,用你喜歡的文字編輯器開啟 resources.xml 檔案,讓我們看看裡面有什麼。

首先,請注意我們正在引入 Spring Integration 名稱空間,以便我們可以使用與 Spring Integration 基於 XML 的領域特定語言相關的元素和屬性。實際上,Spring Integration 名稱空間被設定為文件的預設名稱空間,因此我們必須用名稱空間限定 bean 元素。

然後整個管道都在這個文件中表達。這個管道中的主要元件及其扮演的角色如下

  • 閘道器(Gateway),其 ID 是 messagingGateway 這個元件實際上是一個外觀模式(facade),它將應用的其餘部分與底層訊息架構分離。元件可以像正常一樣消費這個閘道器,並根據指定的服務介面同步呼叫它。在底層,閘道器是一個代理,它將執行必要的 Spring Integration 通道訊息交換。在本例中,閘道器將獲取任何方法呼叫的內容,並將其封裝在 Spring Integration 訊息中,然後將其放置在 default-request-channel 上。
  • 出站通道(Outgoing channel),其 ID 是 demoChannel 這個通道是放置出站訊息的地方。實際上不需要入站訊息通道,因為 Spring Integration 會為回覆訊息建立一個臨時通道,該通道會自動連線到請求訊息來自的閘道器。這是透過使用最初由閘道器設定的關聯 ID 來實現的。
  • 服務啟用器端點(Service activator endpoint),其 ID 是 localService 這個元件負責當訊息出現在入站通道上時,呼叫特定 bean 上的特定方法。在本例中,demoBean 服務上的 doSomething 方法將透過訊息內容被呼叫,根據 doSomething 方法的引數,該內容是一個字串。

注意: Grails 中的所有服務類預設都會生成該類的單例服務物件,該物件在 Spring 應用上下文中以類名的駝峰命名法版本進行索引。因此,當服務啟用器引用 demoBeanService bean 時,Grails 會自動將其解析為我們之前建立的 DemoBeanService 的單例例項。

就這樣,我們有了一個完整的訊息傳遞管道,它將透過內部佇列傳遞訊息,從而將任何訊息提供者與消費者強力解耦。你會注意到,任何建立訊息並呼叫閘道器的消費者都不需要知道訊息最終在哪裡,甚至不需要知道目標端點實現了什麼介面。這裡的契約是正在傳遞的訊息型別,對於這個簡單的示例來說,僅僅是字串。

將閘道器連線到控制器

最後一步是將 Spring Integration 管道連線到 Grails 控制器中,以便它能夠透明地傳遞和接收訊息。就控制器而言,它只是呼叫一個滿足特定介面的本地物件,但在底層,Spring Integration 正在將該訊息路由到我們指定的 bean,即我們的 demoBeanService

為了連線這些鬆散的部分,你需要建立一個你願意向控制器暴露的服務介面。請注意,端點引用的實際 bean 不一定需要實現相同的介面,這裡重要的是訊息引數。

要建立該介面,請在 src/java 目錄下建立一個名為 MessageService.java 的 Java 介面,放在一個合適的包結構下,例如 com.russmiles.demo.grails.integration。完成該介面,使其與以下內容匹配

圖 8. MessageService 介面的定義

現在你已經定義了閘道器介面,最後的工作是將閘道器本身連線到你的控制器中。在 Grails 中,你可以根據物件的名稱自動為其提供依賴項,因此要從控制器內部訪問閘道器,你只需宣告一個屬性,其名稱與閘道器的 ID 匹配

圖 9. 將閘道器依賴項新增到控制器中

現在你已經將閘道器連線到控制器,你可以修改你的 send 閉包來使用它了

圖 10. 你完成的控制器

就這樣,是時候啟動使用 Spring Integration 的 Grails 應用了。

執行你的基於 Spring Integration 的 Grails 應用

一切都連線好後,剩下的就是執行你的應用了。使用常用命令執行 Grails 應用

> grails run-app

現在在你的瀏覽器中訪問你的 GreetingsMessageSenderController,如下所示

圖 11. 你的表單,準備好輸入以觸發你的 Spring Integration 管道

在表單中輸入一些文字,然後點擊發送

圖 12. 表單中輸入了一些資料,準備就緒

下圖顯示了接下來應該發生的事情

圖 13. 你的訊息,透過管道傳送並處理後返回,作為 flash 訊息的一部分顯示

再次強調一下,當你點選“傳送”按鈕時,會發生以下事情。

當你點擊發送時,你的控制器會呼叫閘道器,閘道器將訊息作為 Spring Integration 訊息傳遞給適當的通道,然後服務啟用器會接收該訊息,解包並呼叫 demoBean 服務。該服務接收字串內容並將其轉換為大寫,然後透過為此交換設定的臨時通道返回結果。閘道器上的方法返回,解除控制器的阻塞,然後控制器渲染檢視,並在 flash 集合中顯示結果。

總結

本文只是將 Spring Integration 訊息管道引導到 Grails 應用中的首次嘗試。下一步是做一些比僅僅在程序內從閘道器傳遞訊息到服務啟用物件更復雜的事情。在本系列下一篇文章中,我們將探討如何利用我們靈活的 Spring Integration 管道,使用 JMS 介面卡將訊息傳遞到與我們的 Grails 應用不在同一程序中執行的服務端點。

獲取 Spring 資訊

訂閱 Spring 資訊,保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助你加速前進。

瞭解更多

獲取支援

Tanzu Spring 透過簡單訂閱,提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

近期活動

檢視 Spring 社群的所有近期活動。

檢視全部