Spring Integration 示例

自 Spring Integration 2.0 起,Spring Integration 發行版不再包含示例。相反,我們轉而採用一種更簡單的協作模型,這種模型應能促進更好的社群參與,並且理想情況下能帶來更多貢獻。示例現在有專門的 GitHub 倉庫。示例開發也有自己的生命週期,它不依賴於框架版本的生命週期,儘管出於相容性考慮,該倉庫仍會針對每個主要版本進行標記。

社群的巨大好處是,我們現在可以新增更多示例並立即提供給您,而無需等待下一個版本。擁有一個不依賴於實際框架的獨立 GitHub 倉庫也是一個巨大好處。您現在有了一個專門的地方來建議示例以及報告現有示例的問題。您還可以透過 Pull Request 向我們提交示例。如果我們認為您的示例有價值,我們將非常樂意將其新增到 'samples' 倉庫中,並妥善署名您為作者。

何處獲取示例

Spring Integration Samples 專案託管在 GitHub 上。要簽出 (check out) 或克隆 (clone) 示例,您的系統必須安裝 Git 客戶端。許多平臺都有基於 GUI 的產品可用(例如,Eclipse IDE 的 EGit)。簡單的 Google 搜尋可以幫助您找到它們。您也可以使用 Git 的命令列介面。

如果您需要關於如何安裝或使用 Git 的更多資訊,請訪問:https://git-scm.tw/

要使用 Git 命令列工具克隆 (check out) Spring Integration 示例倉庫,請執行以下命令:

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

前面的命令將整個示例倉庫克隆到您執行該 `git` 命令的工作目錄下的一個名為 `spring-integration-samples` 的目錄中。由於示例倉庫是一個活躍的倉庫,您可能希望定期執行拉取 (pull) 操作(更新)以獲取新示例和現有示例的更新。為此,請執行以下 `git pull` 命令:

$ git pull

提交示例或示例請求

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

我如何貢獻自己的示例?

GitHub 適用於社交編碼:如果您想將自己的程式碼示例提交到 Spring Integration Samples 專案,我們鼓勵透過從此倉庫的分叉建立拉取請求 (pull requests) 來貢獻。如果您想透過這種方式貢獻程式碼,請儘可能引用一個GitHub issue,其中提供有關您示例的一些詳細資訊。

簽署貢獻者許可協議

非常重要:在我們接受您的 Spring Integration 示例之前,您需要簽署 SpringSource 貢獻者許可協議 (CLA)。簽署貢獻者協議並不賦予主倉庫的提交許可權,但這意味著我們可以接受您的貢獻,並且如果接受,您將獲得作者署名。要閱讀並簽署 CLA,請訪問:

Project 下拉選單中,選擇 Spring Integration。專案負責人是 Artem Bilan。

程式碼貢獻流程

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

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

示例請求

之前提到的,Spring Integration Samples 專案使用 GitHub issue 作為錯誤跟蹤系統。要提交新的示例請求,請訪問 github.com/spring-projects/spring-integration-samples/issues

示例結構

從 Spring Integration 2.0 開始,示例的結構發生了變化。隨著計劃中的更多示例,我們意識到並非所有示例都有相同的目標。它們都共同的目標是展示如何應用和使用 Spring Integration 框架。然而,它們的不同之處在於,一些示例專注於技術用例,而另一些則側重於業務用例。此外,一些示例旨在展示可用於解決特定場景(包括技術和業務)的各種技術。新的示例分類使我們能夠更好地根據每個示例解決的問題來組織它們,同時為您提供更簡單的方式來找到適合您需求的示例。

目前,共有四類。在示例倉庫中,每個類別都有自己的目錄,其名稱與類別名稱相同:

基本 (samples/basic)

這是入門的好地方。這裡的示例是技術驅動的,展示了配置和程式碼方面的最低限度。這些應有助於您透過介紹 Spring Integration 的基本概念、API 和配置以及企業整合模式 (EIP) 來快速入門。例如,如果您正在尋找如何實現服務啟用器並將其連線到訊息通道的答案,如何使用訊息閘道器作為訊息交換的門面,或者如何開始使用 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 的書 企業整合模式 中介紹的一個示例。

下圖展示了整個流程:

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

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

  • 管道的型別(點對點通道、釋出-訂閱通道、通道介面卡等)

  • 核心過濾器以及過濾器如何與管道協作的模式(訊息路由器、拆分器和聚合器、各種訊息轉換模式等)

EIP 書籍的第 9 章很好地描述了此用例的詳細資訊和變體,但這裡是簡要總結:在尋找最佳貸款報價時,消費者訂閱貸款經紀人的服務,貸款經紀人處理以下細節:

  • 消費者預篩查(例如,獲取和審查消費者的信用歷史)

  • 確定最合適的銀行(例如,基於消費者的信用歷史或信用評分)

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

  • 收集來自每個銀行的響應

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

  • 將貸款報價返還給消費者。

獲取貸款報價的實際流程通常更為複雜。然而,由於我們的目標是展示如何在 Spring Integration 中實現企業整合模式,用例已簡化,僅關注流程的整合方面。這並不是提供消費者金融方面的建議。

透過與貸款經紀人合作,消費者與貸款經紀人操作的細節隔離開來,並且每個貸款經紀人的操作可能不同,以保持競爭優勢,因此我們組裝和實現的內容必須靈活,以便能夠快速無痛地引入任何更改。

貸款經紀人示例實際上並未與任何“虛構”的銀行或信用局進行實際通訊。這些服務都被模擬了。

我們的目標是組裝、編排和測試整個流程的整合方面。只有這樣,我們才能開始考慮將此類流程連線到實際服務。那時,無論特定貸款經紀人與多少銀行打交道,也無論用於與這些銀行通訊的通訊媒介(或協議)(JMS、WS、TCP 等)是什麼型別,組裝好的流程及其配置都不會改變。

設計

當您分析前面列出的六個要求時,您可以看到它們都是整合關注點。例如,在消費者預篩查步驟中,我們需要收集關於消費者的更多資訊以及消費者的願望,並用額外的元資訊豐富貸款請求。然後,我們必須篩選這些資訊以選擇最合適的銀行列表等。豐富 (enrich)、篩選 (filter) 和選擇 (select) 都是整合關注點,EIP 為此定義了模式形式的解決方案。Spring Integration 提供了這些模式的實現。

下圖展示了訊息閘道器的表現形式:

gateway
圖 2. 訊息閘道器

訊息閘道器模式提供了一種訪問訊息系統的簡單機制,包括我們的貸款經紀人。在 Spring Integration 中,您可以將閘道器定義為一個普通的 Java 介面(無需提供實現),使用 XML 的 `` 元素或 Java 註解進行配置,並像使用任何其他 Spring bean 一樣使用它。Spring Integration 負責將方法呼叫委託並對映到訊息基礎設施,透過生成訊息(載荷對映到方法的輸入引數)並將其傳送到指定通道來完成。以下示例展示瞭如何使用 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>

我們當前的閘道器提供了兩個可以呼叫的方法。一個返回最佳單一報價,另一個返回所有報價。在下游的某個地方,我們需要知道呼叫者需要哪種型別的回覆。在訊息架構中實現此目標的最佳方法是豐富訊息的內容,新增一些描述您意圖的元資料。內容豐富器 (Content Enricher) 就是解決此問題的模式之一。為了方便起見,Spring Integration 確實提供了一個單獨的配置元素來用任意資料豐富訊息頭(稍後描述)。然而,由於 `gateway` 元素負責構造初始訊息,它包含了用任意訊息頭豐富新建立的訊息的能力。在我們的示例中,無論何時呼叫 `getBestQuote()` 方法,我們都會新增一個 `RESPONSE_TYPE` 頭,其值為 `BEST`。對於其他方法,我們不新增任何頭。現在,我們可以在下游檢查此頭的存在性。根據它的存在和它的值,我們可以確定呼叫者需要哪種型別的回覆。

基於用例,我們還知道需要執行一些預篩查步驟,例如獲取和評估消費者的信用評分,因為一些一流銀行只接受滿足最低信用評分要求的消費者的報價請求。因此,如果在訊息被轉發到銀行之前,能夠用此類資訊豐富訊息,那就太好了。如果需要完成幾個流程來提供此類元資訊,並且這些流程可以分組到單個單元中,那就更好了。在我們的用例中,我們需要確定信用評分,並根據信用評分和某些規則,選擇一個訊息通道列表(銀行通道)來發送報價請求。

複合訊息處理器

複合訊息處理器模式描述了關於構建端點的規則,這些端點保持對由多個訊息處理器組成的訊息流的控制。在 Spring Integration 中,複合訊息處理器模式由 `` 元素實現。

下圖展示了鏈模式:

chain
圖 3. 鏈

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

下圖展示了訊息路由器模式:

bank router
圖 4. 訊息路由器

Spring Integration 提供了訊息路由模式的幾種實現。在這種情況下,我們使用一個路由器,它根據評估一個表示式(在 Spring Expression Language 中)來確定通道列表,該表示式檢視信用評分(在前一步中確定)並從 id 為 `banks` 的 `Map` bean 中選擇通道列表,其值根據信用評分的值為 `premier` 或 `secondary`。一旦通道列表被選中,訊息就會被路由到這些通道。

現在,貸款經紀人需要完成的最後一件事是接收來自銀行的貸款報價,按消費者聚合它們(我們不希望向一個消費者展示來自另一個消費者的報價),根據消費者的選擇標準(單一最佳報價或所有報價)組裝響應,並將回覆傳送給啟動流程的訊息閘道器(從而傳送給消費者)。我們的消費者獲得了貸款報價!

下圖顯示了訊息聚合器模式

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>

我們的貸款經紀人使用 <aggregator> 元素定義了一個名為 'quotesAggregator' 的 bean,該 bean 提供預設的聚合和關聯策略。預設的關聯策略基於 correlationId 頭部關聯訊息(參見 EIP 書中的關聯識別符號模式)。請注意,我們從未為此頭部提供值。它是在路由器為每個銀行通道生成單獨訊息時自動設定的。

一旦訊息關聯完成,它們就會被釋放到實際的聚合器實現中。儘管 Spring Integration 提供了一個預設聚合器,但其策略(收集所有訊息的 payload 列表並構建一個以該列表作為 payload 的新訊息)不滿足我們的需求。將所有結果放在一個訊息中是一個問題,因為我們的消費者可能只需要一個最佳報價或所有報價。為了傳達消費者的意圖,我們在流程的早期設定了 RESPONSE_TYPE 頭部。現在我們必須評估此頭部並返回所有報價(預設聚合策略可以工作)或最佳報價(預設聚合策略不工作,因為我們必須確定哪個貸款報價是最好的)。

在一個更真實的應用程式中,選擇最佳報價可能基於複雜的標準,這可能會影響聚合器實現和配置的複雜性。但現在,我們將其簡化。如果消費者想要最佳報價,我們選擇利率最低的報價。為此,LoanQuoteAggregator 類按利率屬性對所有報價進行排序並返回第一個。LoanQuote 類實現了 Comparable 介面,以便根據利率屬性比較報價。響應訊息建立後,它將被髮送到啟動該流程的訊息閘道器的預設回覆通道(從而傳送給消費者)。我們的消費者獲得了貸款報價!

總之,基於 POJO(即現有或遺留)邏輯和輕量級、可嵌入的訊息框架(Spring Integration)組裝了一個相當複雜的流程,它採用鬆散耦合的程式設計模型,旨在簡化異構系統的整合,而無需重量級 ESB 類引擎或專有的開發和部署環境。作為開發者,您不應該僅僅因為存在整合關注點,就不得不將您的 Swing 或控制檯應用移植到 ESB 類伺服器或實現專有介面。

本示例和本節中的其他示例都是基於企業整合模式構建的。您可以將它們視為您解決方案的“構建塊”。它們並非旨在成為完整的解決方案。整合關注點存在於所有型別的應用中(無論是否基於伺服器)。我們的目標是使應用整合不要求在設計、測試和部署策略上進行更改。

咖啡館示例

本節涵蓋了包含在 Spring Integration 示例中的咖啡館示例應用。此示例的靈感來自 Gregor Hohpe 的 Ramblings 中介紹的另一個示例。

領域是一個咖啡館,下圖描繪了基本流程

cafe eip
圖 6. 咖啡館示例

Order 物件可能包含多個 OrderItem。訂單下達後,一個 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,您可以顯著改變結果。例如,您可以為熱飲咖啡師使用一個帶有五個工作執行緒的執行緒池執行器,而保持冷飲咖啡師不變。以下列表配置了這樣的安排

<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"/>

此外,請注意,每次呼叫時都會顯示工作執行緒名稱。您可以看到熱飲是由任務執行器執行緒準備的。如果您提供一個更短的 poller 間隔(例如 100 毫秒),您可以看到它偶爾會透過強制任務排程器(呼叫者)呼叫操作來限制輸入。

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

XML 訊息示例

位於 basic/xml 中的 XML 訊息示例展示瞭如何使用處理 XML payload 的一些提供的元件。該示例使用了處理表示為 XML 的圖書訂單的想法。

此示例表明名稱空間字首可以是您想要的任何內容。雖然我們通常使用 int-xml 來表示整合 XML 元件,但該示例使用了 si-xml。(int 是 “Integration” 的縮寫,si 是 “Spring Integration” 的縮寫。)

首先,訂單被拆分成多個訊息,每個訊息代表一個來自 XPath 分裂器元件的訂單項。以下列表顯示了分裂器的配置

<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"/>