Spring Integration 範例

從 Spring Integration 2.0 開始,Spring Integration 發行版本不再包含範例。相反地,我們已切換到更簡單的協作模型,這應有助於促進更好的社群參與,並理想情況下帶來更多貢獻。範例現在有專用的 GitHub 儲存庫。範例開發也有其自己的生命週期,這不依賴於框架發行版本的生命週期,儘管出於相容性原因,儲存庫仍然標記了每個主要發行版本。

社群的最大好處是,我們現在可以新增更多範例,並立即提供給您,而無需等待下一個發行版本。擁有不與實際框架綁定的專用 GitHub 儲存庫也是一大好處。您現在有一個專門的地方可以建議範例,以及報告現有範例的問題。您也可以將範例作為 Pull Request 提交給我們。如果我們認為您的範例具有價值,我們將非常樂意將其新增到 'samples' 儲存庫中,並適當地註明您是作者。

在哪裡取得範例

Spring Integration 範例專案託管在 GitHub 上。為了檢查或複製範例,您的系統上必須安裝 Git 用戶端。有幾種基於 GUI 的產品可用於許多平台(例如 Eclipse IDE 的 EGit)。簡單的 Google 搜尋可以幫助您找到它們。您也可以使用 Git 的命令列介面。

如果您需要有關如何安裝或使用 Git 的更多資訊,請造訪:https://git.dev.org.tw/

若要使用 Git 命令列工具複製(檢查)Spring Integration 範例儲存庫,請發出以下命令

$ git clone https://github.com/spring-projects/spring-integration-samples.git

前面的命令將整個範例儲存庫複製到名為 spring-integration-samples 的目錄中,該目錄位於您發出 git 命令的工作目錄中。由於範例儲存庫是即時儲存庫,您可能需要執行定期提取(更新)以取得新範例和現有範例的更新。若要這樣做,請發出以下 git pull 命令

$ git pull

提交範例或範例請求

您可以提交新範例和範例請求。我們非常感謝為改進範例所做的任何努力,包括分享好的想法。

我如何貢獻自己的範例?

GitHub 是用於社群編碼的:如果您想將自己的程式碼範例提交給 Spring Integration 範例專案,我們鼓勵透過來自此儲存庫 forkspull requests 來貢獻。如果您想以這種方式貢獻程式碼,請盡可能參考 GitHub issue,其中提供有關您的範例的一些詳細資訊。

簽署貢獻者許可協議

非常重要:在我們可以接受您的 Spring Integration 範例之前,我們需要您簽署 SpringSource 貢獻者許可協議 (CLA)。簽署貢獻者協議不會授予任何人對主要儲存庫的提交權限,但這確實意味著我們可以接受您的貢獻,如果我們這樣做,您將獲得作者署名。為了閱讀和簽署 CLA,請前往

從「專案」下拉式選單中,選取「Spring Integration」。專案負責人是 Artem Bilan。

程式碼貢獻流程

對於實際的程式碼貢獻流程,請閱讀 Spring Integration 的貢獻者指南。它們也適用於範例專案。您可以在 github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc 找到它們

此流程確保每個提交都經過同儕審查。事實上,核心提交者遵循完全相同的規則。我們非常期待您的 Spring Integration 範例!

範例請求

先前所述,Spring Integration 範例專案使用 GitHub issue 作為錯誤追蹤系統。若要提交新的範例請求,請造訪 github.com/spring-projects/spring-integration-samples/issues

範例結構

從 Spring Integration 2.0 開始,範例的結構已變更。隨著更多範例的計畫,我們意識到並非所有範例都具有相同的目標。它們都具有顯示如何應用和使用 Spring Integration 框架的共同目標。但是,它們的不同之處在於,有些範例專注於技術使用案例,而另一些範例則專注於業務使用案例。此外,有些範例是關於展示可用於解決某些情境(技術和業務)的各種技術。新的範例分類讓我們可以根據每個範例解決的問題更好地組織它們,同時為您提供更簡單的方式來找到適合您需求的範例。

目前,共有四個類別。在範例儲存庫中,每個類別都有自己的目錄,該目錄以類別名稱命名

基本 (samples/basic)

這是入門的好地方。此處的範例在技術上具有動機,並展示了關於設定和程式碼的最基本內容。這些應透過向您介紹 Spring Integration 以及企業整合模式 (EIP) 的基本概念、API 和設定,幫助您快速入門。例如,如果您正在尋找有關如何實作和連接服務啟動器到訊息通道、如何使用訊息傳遞閘道器作為訊息交換的 facade,或如何開始使用 MAIL、TCP/UDP 或其他模組的答案,那麼這裡是找到良好範例的正確位置。底線是 samples/basic 是入門的好地方。

中級 (samples/intermediate)

此類別針對已經熟悉 Spring Integration 框架(超出入門)的開發人員,但在切換到訊息傳遞架構後,在解決他們可能遇到的更進階的技術問題時,需要更多指導。例如,如果您正在尋找有關如何在各種訊息交換情境中處理錯誤,或如何在某些訊息永遠不會到達以進行彙集的情況下正確設定彙集器,或任何其他超出特定元件的基本實作和設定並揭示「還有什麼」類型問題的問題的答案,那麼這裡是找到這些類型範例的正確位置。

進階 (samples/advanced)

此類別針對非常熟悉 Spring Integration 框架的開發人員,但他們希望透過使用 Spring Integration 的公共 API 來擴充它以解決特定的自訂需求。例如,如果您正在尋找範例,展示如何實作自訂通道或消費者(基於事件或基於輪詢),或者您正在嘗試找出在 Spring Integration bean 剖析器階層之上實作自訂 bean 剖析器的最合適方法(可能在為自訂元件實作自己的命名空間和架構時),那麼這裡是尋找的正確位置。在這裡,您還可以找到將協助您進行配接器開發的範例。Spring Integration 隨附了廣泛的配接器程式庫,可讓您將遠端系統與 Spring Integration 訊息傳遞框架連接起來。但是,您可能需要與核心框架未提供配接器的系統整合。如果是這樣,您可能會決定實作自己的配接器(請考慮貢獻它)。此類別將包含展示如何操作的範例。

應用程式 (samples/applications)

此類別針對對訊息驅動架構和 EIP 有良好理解,並且對 Spring 和 Spring Integration 有高於平均理解的開發人員和架構師,他們正在尋找解決特定業務問題的範例。換句話說,此類別中範例的重點是業務使用案例,以及如何使用訊息驅動架構和 Spring Integration(尤其是)來解決它們。例如,如果您想了解如何使用 Spring Integration 實作和自動化貸款經紀人或旅行社流程,那麼這裡是找到這些類型範例的正確位置。

Spring Integration 是一個社群驅動的框架。因此,社群參與非常重要。這包括範例。如果您找不到您要尋找的內容,請告訴我們!

範例

目前,Spring Integration 隨附了相當多的範例,您可以期待更多。為了幫助您更好地瀏覽它們,每個範例都附帶自己的 readme.txt 檔案,其中涵蓋了有關範例的幾個詳細資訊(例如,它解決了哪些 EIP 模式、它試圖解決什麼問題、如何執行範例以及其他詳細資訊)。但是,某些範例需要更詳細,有時是圖形化的說明。在本節中,您可以找到我們認為需要特別注意的範例的詳細資訊。

貸款經紀人

本節涵蓋 Spring Integration 範例中包含的貸款經紀人範例應用程式。此範例的靈感來自 Gregor Hohpe 和 Bobby Woolf 的著作 Enterprise Integration Patterns 中介紹的範例之一。

下圖顯示了整個流程

loan broker eip
圖 1. 貸款經紀人範例

EIP 架構的核心是非常簡單但功能強大的管道、篩選器,當然還有:訊息的概念。端點(篩選器)透過通道(管道)相互連接。產生端點將訊息傳送到通道,而消費端點檢索訊息。此架構旨在定義各種機制,描述如何在端點之間交換資訊,而無需了解這些端點是什麼或它們正在交換什麼資訊。因此,它提供了非常鬆散耦合和靈活的協作模型,同時也將整合問題與業務問題分開。EIP 透過進一步定義來擴充此架構

  • 管道類型(點對點通道、發佈-訂閱通道、通道配接器和其他)

  • 核心篩選器和篩選器如何與管道協作的模式(訊息路由器、分割器和彙集器、各種訊息轉換模式和其他)

EIP 書籍的第 9 章很好地描述了此使用案例的詳細資訊和變化,但以下是簡要摘要:在為最佳貸款報價購物時,消費者訂閱貸款經紀人的服務,貸款經紀人處理諸如以下詳細資訊:

  • 消費者預先篩選(例如,取得和審查消費者的信用記錄)

  • 確定最合適的銀行(例如,根據消費者的信用記錄或評分)

  • 向每個選定的銀行發送貸款報價請求

  • 收集每個銀行的回應

  • 根據消費者的要求篩選回應並確定最佳報價。

  • 將貸款報價傳回給消費者。

取得貸款報價的實際流程通常更複雜一些。但是,由於我們的目標是展示如何在 Spring Integration 中實現和實作企業整合模式,因此已簡化使用案例,僅專注於流程的整合方面。這並不是試圖為您提供消費者財務方面的建議。

透過聘請貸款經紀人,消費者與貸款經紀人營運的詳細資訊隔離,並且每個貸款經紀人的營運可能彼此不同,以保持競爭優勢,因此我們組裝和實作的任何內容都必須具有彈性,以便可以快速且輕鬆地引入任何變更。

貸款經紀人範例實際上並未與任何「虛構」銀行或信用局交談。這些服務已被存根化。

我們這裡的目標是組裝、協調和測試整個流程的整合方面。只有這樣,我們才能開始考慮將此類流程連接到真實服務。屆時,組裝的流程及其設定不會改變,無論特定貸款經紀人處理的銀行數量或用於與這些銀行溝通的通訊媒體(或協定)類型(JMS、WS、TCP 等)如何。

設計

當您分析 先前列出的六個需求 時,您可以看到它們都是整合問題。例如,在消費者預先篩選步驟中,我們需要收集有關消費者的其他資訊以及消費者的願望,並使用其他中繼資訊來豐富貸款請求。然後,我們必須篩選此資訊以選擇最合適的銀行清單等等。豐富、篩選和選擇都是整合問題,EIP 為此定義了模式形式的解決方案。Spring Integration 提供了這些模式的實作。

下圖顯示了訊息傳遞閘道器的表示

gateway
圖 2. 訊息傳遞閘道器

訊息傳遞閘道器模式提供了一種簡單的機制來存取訊息傳遞系統,包括我們的貸款經紀人。在 Spring Integration 中,您可以將閘道器定義為普通的舊 Java 介面(您無需提供實作),使用 XML <gateway> 元素或 Java 中的註解進行設定,並像使用任何其他 Spring bean 一樣使用它。Spring Integration 透過產生訊息(payload 對應到方法的輸入參數)並將其發送到指定的通道,來處理委派和將方法調用對應到訊息傳遞基礎結構。以下範例顯示如何使用 XML 定義這樣的閘道器

<int:gateway id="loanBrokerGateway"
  default-request-channel="loanBrokerPreProcessingChannel"
  service-interface="org.springframework.integration.samples.loanbroker.LoanBrokerGateway">
  <int:method name="getBestLoanQuote">
    <int:header name="RESPONSE_TYPE" value="BEST"/>
  </int:method>
</int:gateway>

我們目前的閘道器提供了兩種可以調用的方法。一種傳回最佳單一報價,另一種傳回所有報價。在下游的某個地方,我們需要知道呼叫者需要哪種類型的回覆。在訊息傳遞架構中實現此目的的最佳方法是用描述您的意圖的中繼資料來豐富訊息的內容。內容豐富器是解決此問題的模式之一。作為一種便利,Spring Integration 確實提供了單獨的設定元素,以使用任意資料(稍後描述)來豐富訊息標頭。但是,由於 gateway 元素負責建構初始訊息,因此它包含使用任意訊息標頭來豐富新建立的訊息的能力。在我們的範例中,每當調用 getBestQuote() 方法時,我們都會新增 RESPONSE_TYPE 標頭,其值為 BEST。對於其他方法,我們不會新增任何標頭。現在我們可以檢查下游是否存在此標頭。根據其存在及其值,我們可以確定呼叫者想要哪種類型的回覆。

根據使用案例,我們也知道需要執行一些預先篩選步驟,例如取得和評估消費者的信用評分,因為一些首屈一指的銀行只接受信用評分符合最低要求的消費者的報價請求。因此,如果訊息在轉發到銀行之前能夠使用此類資訊來豐富,那將是很好的。如果需要完成多個流程才能提供此類中繼資訊,則將這些流程分組在單個單元中也將是很好的。在我們的使用案例中,我們需要確定信用評分,並根據信用評分和某些規則,選擇要向其發送報價請求的訊息通道(銀行通道)清單。

組合訊息處理器

組合訊息處理器模式描述了圍繞建構端點的規則,這些端點維護對訊息流程的控制,訊息流程由多個訊息處理器組成。在 Spring Integration 中,組合訊息處理器模式由 <chain> 元素實作。

下圖顯示了鏈模式

chain
圖 3. 鏈

上圖顯示我們有一個鏈,其中包含一個內部標頭豐富器元素,該元素使用 CREDIT_SCORE 標頭和值(由調用信用服務決定 - 由 'creditBureau' 名稱識別的簡單 POJO Spring bean)進一步豐富訊息的內容。然後它委派給訊息路由器。

下圖顯示了訊息路由器模式

bank router
圖 4. 訊息路由器

Spring Integration 提供了多種訊息路由模式的實作。在這個範例中,我們使用一個路由器,它會基於評估一個表達式(使用 Spring Expression Language),來決定一組通道。這個表達式會檢視信用評分(在前一個步驟中決定),並根據信用評分的值,從 idbanksMap bean 中選取值為 premiersecondary 的通道列表。一旦選取了通道列表,訊息就會被路由到這些通道。

現在,貸款仲介還需要做最後一件事,就是接收來自銀行的貸款報價,依消費者彙總這些報價(我們不希望將一位消費者的報價顯示給另一位消費者),根據消費者的選擇標準(單一最佳報價或所有報價)組裝回應,並將回覆發送給消費者。

下圖顯示了訊息彙總器模式

quotes aggregator
圖 5. 訊息彙總器

彙總器模式描述了一個端點,它將相關訊息組合成單一訊息。可以提供標準和規則來決定彙總和關聯策略。Spring Integration 提供了多種彙總器模式的實作,以及一個方便的基於命名空間的配置。

以下範例展示了如何定義一個彙總器

<int:aggregator id="quotesAggregator"
      input-channel="quotesAggregationChannel"
      method="aggregateQuotes">
  <beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
</int:aggregator>

我們的貸款仲介定義了一個名為 'quotesAggregator' 的 bean,使用了 <aggregator> 元素,它提供了預設的彙總和關聯策略。預設的關聯策略基於 correlationId 標頭來關聯訊息(請參閱 EIP 書籍中的關聯識別符模式)。請注意,我們從未提供這個標頭的值。它是由路由器在稍早自動設定的,當時路由器為每個銀行通道產生了個別的訊息。

一旦訊息被關聯,它們就會被釋放到實際的彙總器實作中。雖然 Spring Integration 提供了預設的彙總器,但它的策略(收集所有訊息的 payload 列表,並建構一個以這個列表作為其 payload 的新訊息)並不符合我們的需求。訊息中包含所有結果會產生問題,因為我們的消費者可能需要單一最佳報價或所有報價。為了傳達消費者的意圖,我們在流程的早期設定了 RESPONSE_TYPE 標頭。現在我們必須評估這個標頭,並返回所有報價(預設的彙總策略可以運作)或最佳報價(預設的彙總策略無法運作,因為我們必須決定哪個貸款報價是最佳的)。

在更實際的應用程式中,選擇最佳報價可能基於複雜的標準,這可能會影響彙總器實作和配置的複雜性。不過,現在為了簡化,我們假設如果消費者想要最佳報價,我們就選擇利率最低的報價。為了實現這一點,LoanQuoteAggregator 類別會根據利率對所有報價進行排序,並返回第一個報價。LoanQuote 類別實作了 Comparable 介面,以根據 rate 屬性比較報價。一旦建立回應訊息,它就會被發送到訊息傳輸閘道的預設回覆通道(因此,發送給啟動流程的消費者)。我們的消費者收到了貸款報價!

總之,一個相當複雜的流程是基於 POJO(即現有的或舊有的)邏輯和一個輕量級、可嵌入的訊息傳輸框架(Spring Integration)組裝而成,它具有鬆散耦合的程式設計模型,旨在簡化異質系統的整合,而無需重量級的 ESB 類引擎或專有的開發和部署環境。作為開發人員,您不應該因為有整合方面的考量,就需要將您的 Swing 或基於控制台的應用程式移植到 ESB 類型的伺服器,或實作專有的介面。

本範例和本節中的其他範例都建立在企業整合模式之上。您可以將它們視為您解決方案的「建構模組」。它們並非旨在成為完整的解決方案。整合考量存在於所有類型的應用程式中(無論是否基於伺服器)。我們的目標是使整合應用程式不需要在設計、測試和部署策略上進行變更。

咖啡廳範例

本節介紹了 Spring Integration 範例中包含的咖啡廳範例應用程式。這個範例的靈感來自 Gregor Hohpe 的 Ramblings 中另一個範例。

領域是咖啡廳,下圖描述了基本流程

cafe eip
圖 6. 咖啡廳範例

Order 物件可能包含多個 OrderItems。一旦下訂單,splitter 會將複合訂單訊息分解為每個飲品的單一訊息。然後,這些訊息中的每一個都會由路由器處理,路由器會判斷飲品是熱的還是冷的(透過檢查 OrderItem 物件的 'isIced' 屬性)。Barista 會準備每種飲品,但熱飲和冷飲的準備由兩種不同的方法處理:'prepareHotDrink' 和 'prepareColdDrink'。準備好的飲品然後會被發送到 Waiter,在那裡它們被彙總成一個 Delivery 物件。

以下列表顯示了 XML 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/integration
  https://www.springframework.org/schema/integration/spring-integration.xsd
  http://www.springframework.org/schema/integration/stream
  https://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd">

    <int:gateway id="cafe" service-interface="o.s.i.samples.cafe.Cafe"/>

    <int:channel  id="orders"/>
    <int:splitter input-channel="orders" ref="orderSplitter"
                  method="split" output-channel="drinks"/>

    <int:channel id="drinks"/>
    <int:router  input-channel="drinks"
                 ref="drinkRouter" method="resolveOrderItemChannel"/>

    <int:channel id="coldDrinks"><int:queue capacity="10"/></int:channel>
    <int:service-activator input-channel="coldDrinks" ref="barista"
                           method="prepareColdDrink" output-channel="preparedDrinks"/>

    <int:channel id="hotDrinks"><int:queue capacity="10"/></int:channel>
    <int:service-activator input-channel="hotDrinks" ref="barista"
                           method="prepareHotDrink" output-channel="preparedDrinks"/>

    <int:channel id="preparedDrinks"/>
    <int:aggregator input-channel="preparedDrinks" ref="waiter"
                    method="prepareDelivery" output-channel="deliveries"/>

    <int-stream:stdout-channel-adapter id="deliveries"/>

    <beans:bean id="orderSplitter"
                class="org.springframework.integration.samples.cafe.xml.OrderSplitter"/>

    <beans:bean id="drinkRouter"
                class="org.springframework.integration.samples.cafe.xml.DrinkRouter"/>

    <beans:bean id="barista" class="o.s.i.samples.cafe.xml.Barista"/>
    <beans:bean id="waiter"  class="o.s.i.samples.cafe.xml.Waiter"/>

    <int:poller id="poller" default="true" fixed-rate="1000"/>

</beans:beans>

每個訊息端點都連接到輸入通道、輸出通道或兩者。每個端點都管理自己的生命週期(預設情況下,端點在初始化時自動啟動,為了防止這種情況,請新增 auto-startup 屬性,值為 false)。最重要的是,請注意物件是簡單的 POJO,具有強型別的方法引數。以下範例顯示了 Splitter

public class OrderSplitter {
    public List<OrderItem> split(Order order) {
        return order.getItems();
    }
}

在路由器的情況下,傳回值不必是 MessageChannel 實例(儘管它可以是)。在這個範例中,傳回的是一個 String 值,它保存了通道名稱,如下列表所示。

public class DrinkRouter {
    public String resolveOrderItemChannel(OrderItem orderItem) {
        return (orderItem.isIced()) ? "coldDrinks" : "hotDrinks";
    }
}

現在,回到 XML,您可以看到有兩個 <service-activator> 元素。它們都委派給同一個 Barista 實例,但使用不同的方法(prepareHotDrinkprepareColdDrink),每個方法對應於訂單項目被路由到的兩個通道之一。以下列表顯示了 Barista 類別(其中包含 prepareHotDrinkprepareColdDrink 方法)

public class Barista {

    private long hotDrinkDelay = 5000;
    private long coldDrinkDelay = 1000;

    private AtomicInteger hotDrinkCounter = new AtomicInteger();
    private AtomicInteger coldDrinkCounter = new AtomicInteger();

    public void setHotDrinkDelay(long hotDrinkDelay) {
        this.hotDrinkDelay = hotDrinkDelay;
    }

    public void setColdDrinkDelay(long coldDrinkDelay) {
        this.coldDrinkDelay = coldDrinkDelay;
    }

    public Drink prepareHotDrink(OrderItem orderItem) {
        try {
            Thread.sleep(this.hotDrinkDelay);
            System.out.println(Thread.currentThread().getName()
                    + " prepared hot drink #" + hotDrinkCounter.incrementAndGet()
                    + " for order #" + orderItem.getOrder().getNumber()
                    + ": " + orderItem);
            return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
                    orderItem.isIced(), orderItem.getShots());
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }

    public Drink prepareColdDrink(OrderItem orderItem) {
        try {
            Thread.sleep(this.coldDrinkDelay);
            System.out.println(Thread.currentThread().getName()
                    + " prepared cold drink #" + coldDrinkCounter.incrementAndGet()
                    + " for order #" + orderItem.getOrder().getNumber() + ": "
                    + orderItem);
            return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
                    orderItem.isIced(), orderItem.getShots());
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }
}

從前面的程式碼摘錄中可以看到,Barista 方法具有不同的延遲(熱飲需要五倍的時間來準備)。這模擬了以不同速率完成的工作。當 CafeDemo 'main' 方法執行時,它會循環 100 次,每次發送一個熱飲和一個冷飲。它實際上是透過調用 Cafe 介面上的 'placeOrder' 方法來發送訊息。在先前的 XML 配置中,您可以看到指定了 <gateway> 元素。這會觸發代理的建立,該代理實作給定的服務介面並將其連接到通道。通道名稱在 Cafe 介面的 @Gateway 註解中提供,如下面的介面定義所示

public interface Cafe {

    @Gateway(requestChannel="orders")
    void placeOrder(Order order);

}

最後,看看 CafeDemo 本身的 main() 方法

public static void main(String[] args) {
    AbstractApplicationContext context = null;
    if (args.length > 0) {
        context = new FileSystemXmlApplicationContext(args);
    }
    else {
        context = new ClassPathXmlApplicationContext("cafeDemo.xml", CafeDemo.class);
    }
    Cafe cafe = context.getBean("cafe", Cafe.class);
    for (int i = 1; i <= 100; i++) {
        Order order = new Order(i);
        order.addItem(DrinkType.LATTE, 2, false);
        order.addItem(DrinkType.MOCHA, 3, true);
        cafe.placeOrder(order);
    }
}
要執行此範例以及其他八個範例,請參閱主要發行版本 samples 目錄中的 README.txt(如 本章開頭 所述)。

當您執行 cafeDemo 時,您可以看到冷飲最初比熱飲準備得更快。由於有一個彙總器,冷飲實際上受到熱飲準備速率的限制。這是預期的,基於它們各自 1000 和 5000 毫秒的延遲。但是,透過配置一個帶有並行任務執行器的 poller,您可以顯著改變結果。例如,您可以為熱飲 Barista 使用一個帶有五個 worker 的線程池執行器,同時保持冷飲 Barista 不變。以下列表配置了這樣的安排

<int:service-activator input-channel="hotDrinks"
                     ref="barista"
                     method="prepareHotDrink"
                     output-channel="preparedDrinks"/>

  <int:service-activator input-channel="hotDrinks"
                     ref="barista"
                     method="prepareHotDrink"
                     output-channel="preparedDrinks">
      <int:poller task-executor="pool" fixed-rate="1000"/>
  </int:service-activator>

  <task:executor id="pool" pool-size="5"/>

此外,請注意 worker 線程名稱會與每次調用一起顯示。您可以看到熱飲是由 task-executor 線程準備的。如果您提供更短的 poller 間隔(例如 100 毫秒),您可以看到它偶爾會透過強制任務排程器(調用者)調用操作來限制輸入。

除了實驗 poller 的並行設定外,您還可以新增 'transactional' 子元素,然後在上下文中引用任何 PlatformTransactionManager 實例。

XML 訊息傳輸範例

basic/xml 中的 XML 訊息傳輸範例展示了如何使用一些提供的組件來處理 XML payload。該範例使用了處理以 XML 表示的書籍訂單的想法。

這個範例展示了命名空間前綴可以是您想要的任何名稱。雖然我們通常將 int-xml 用於整合 XML 組件,但該範例使用了 si-xml。(int 是 “Integration” 的縮寫,而 si 是 “Spring Integration” 的縮寫。)

首先,訂單被拆分為多個訊息,每個訊息代表來自 XPath splitter 組件的單個訂單項目。以下列表顯示了 splitter 的配置

<si-xml:xpath-splitter id="orderItemSplitter" input-channel="ordersChannel"
              output-channel="stockCheckerChannel" create-documents="true">
      <si-xml:xpath-expression expression="/orderNs:order/orderNs:orderItem"
                                namespace-map="orderNamespaceMap" />
  </si-xml:xpath-splitter>

然後,服務啟動器將訊息傳遞到庫存檢查器 POJO。訂單項目文件會使用來自庫存檢查器的關於訂單項目庫存水平的資訊進行豐富。然後,這個豐富的訂單項目訊息會被用於路由訊息。在訂單項目有庫存的情況下,訊息會被路由到倉庫。以下列表配置了路由訊息的 xpath-router

<si-xml:xpath-router id="inStockRouter" input-channel="orderRoutingChannel" resolution-required="true">
    <si-xml:xpath-expression expression="/orderNs:orderItem/@in-stock" namespace-map="orderNamespaceMap" />
    <si-xml:mapping value="true" channel="warehouseDispatchChannel"/>
    <si-xml:mapping value="false" channel="outOfStockChannel"/>
</si-xml:xpath-router>

當訂單項目沒有庫存時,訊息會透過 XSLT 轉換為適合發送給供應商的格式。以下列表配置了 XSLT 轉換器

<si-xml:xslt-transformer input-channel="outOfStockChannel"
  output-channel="resupplyOrderChannel"
  xsl-resource="classpath:org/springframework/integration/samples/xml/bigBooksSupplierTransformer.xsl"/>