動態路由器

Spring Integration 提供了許多不同的路由器設定,適用於常見的基於內容的路由使用案例,並提供實作自訂路由器作為 POJO 的選項。例如,PayloadTypeRouter 提供了一種簡單的方式來設定路由器,該路由器根據傳入訊息的酬載類型計算通道,而 HeaderValueRouter 在設定路由器方面提供了相同的便利性,該路由器透過評估特定訊息標頭的值來計算通道。還有基於表達式 (SpEL) 的路由器,其中通道是根據評估表達式來確定的。所有這些類型的路由器都表現出一些動態特性。

然而,這些路由器都需要靜態設定。即使在基於表達式的路由器的情況下,表達式本身也被定義為路由器設定的一部分,這意味著對相同值運作的相同表達式始終會導致計算出相同的通道。這在大多數情況下是可以接受的,因為這些路由是明確定義的,因此是可預測的。但是,有時我們需要動態變更路由器設定,以便訊息流可以路由到不同的通道。

例如,您可能想要關閉系統的某些部分進行維護,並暫時將訊息重新路由到不同的訊息流。另一個範例是,您可能想要透過新增另一個路由來處理更具體的 java.lang.Number 類型(在 PayloadTypeRouter 的情況下),從而提高訊息流的精細度。

不幸的是,使用靜態路由器設定來達成這些目標中的任何一個,您都必須關閉整個應用程式、變更路由器的設定(變更路由),然後重新啟動應用程式。這顯然不是任何人想要的解決方案。

動態路由器 模式描述了您可以動態變更或設定路由器,而無需關閉系統或個別路由器的機制。

在我們深入探討 Spring Integration 如何支援動態路由之前,我們需要考慮路由器的典型流程

  1. 計算通道識別符,這是路由器接收訊息後計算的值。通常,它是一個字串或實際 MessageChannel 的實例。

  2. 將通道識別符解析為通道名稱。我們稍後在本節中描述此過程的細節。

  3. 將通道名稱解析為實際的 MessageChannel

如果步驟 1 產生 MessageChannel 的實際實例,則在動態路由方面沒有太多可以做的,因為 MessageChannel 是任何路由器工作的最終產品。但是,如果第一步產生不是 MessageChannel 實例的通道識別符,您有幾種可能的方式來影響 MessageChannel 的衍生過程。考慮以下酬載類型路由器的範例

<int:payload-type-router input-channel="routingChannel">
    <int:mapping type="java.lang.String"  channel="channel1" />
    <int:mapping type="java.lang.Integer" channel="channel2" />
</int:payload-type-router>

在酬載類型路由器的上下文中,前面提到的三個步驟將實現如下

  1. 計算通道識別符,即酬載類型的完整名稱(例如,java.lang.String)。

  2. 將通道識別符解析為通道名稱,其中前一個步驟的結果用於從 mapping 元素中定義的酬載類型映射中選擇適當的值。

  3. 將通道名稱解析為 MessageChannel 的實際實例,作為對應用程式內容中 Bean 的參考(希望是 MessageChannel),該 Bean 由前一個步驟的結果識別。

換句話說,每個步驟都會饋送下一步,直到流程完成。

現在考慮標頭值路由器的範例

<int:header-value-router input-channel="inputChannel" header-name="testHeader">
    <int:mapping value="foo" channel="fooChannel" />
    <int:mapping value="bar" channel="barChannel" />
</int:header-value-router>

現在我們可以考慮標頭值路由器的三個步驟如何運作

  1. 計算通道識別符,即由 header-name 屬性識別的標頭值。

  2. 將通道識別符解析為通道名稱,其中前一個步驟的結果用於從 mapping 元素中定義的通用映射中選擇適當的值。

  3. 將通道名稱解析為 MessageChannel 的實際實例,作為對應用程式內容中 Bean 的參考(希望是 MessageChannel),該 Bean 由前一個步驟的結果識別。

前面兩個不同路由器類型的設定看起來幾乎相同。然而,如果您查看 HeaderValueRouter 的替代設定,我們清楚地看到沒有 mapping 子元素,如下列清單所示

<int:header-value-router input-channel="inputChannel" header-name="testHeader"/>

然而,設定仍然完全有效。那麼自然的問題是第二步中的映射呢?

第二步現在是可選的。如果未定義 mapping,則在第一步中計算的通道識別符值會自動被視為 channel name,現在將其解析為實際的 MessageChannel,如第三步所示。這也意味著第二步是為路由器提供動態特性的關鍵步驟之一,因為它引入了一個流程,讓您可以變更通道識別符解析為通道名稱的方式,從而影響從初始通道識別符確定 MessageChannel 最終實例的過程。

例如,在前面的設定中,假設 testHeader 值為 'kermit',現在它是通道識別符(第一步)。由於此路由器中沒有映射,因此將此通道識別符解析為通道名稱(第二步)是不可能的,並且此通道識別符現在被視為通道名稱。然而,如果存在映射但用於不同的值呢?最終結果仍然相同,因為如果無法透過將通道識別符解析為通道名稱的過程來確定新值,則通道識別符將成為通道名稱。

剩下的就是第三步將通道名稱 ('kermit') 解析為由此名稱識別的 MessageChannel 的實際實例。這基本上涉及對提供的名稱進行 Bean 查找。現在,所有包含標頭值對 testHeader=kermit 的訊息都將路由到 Bean 名稱(其 id)為 'kermit' 的 MessageChannel

但是,如果您想將這些訊息路由到 'simpson' 通道呢?顯然,變更靜態設定是可行的,但這樣做也需要關閉您的系統。但是,如果您可以存取通道識別符映射,您可以引入一個新的映射,其中標頭值對現在是 kermit=simpson,從而讓第二步將 'kermit' 視為通道識別符,同時將其解析為 'simpson' 作為通道名稱。

顯然,這同樣適用於 PayloadTypeRouter,您現在可以重新映射或移除特定的酬載類型映射。事實上,它適用於每個其他路由器,包括基於表達式的路由器,因為它們的計算值現在有機會通過第二步解析為實際的 channel name

任何作為 AbstractMappingMessageRouter 子類別的路由器(包括大多數框架定義的路由器)都是動態路由器,因為 channelMapping 是在 AbstractMappingMessageRouter 層級定義的。該映射的 setter 方法作為公共方法公開,以及 'setChannelMapping' 和 'removeChannelMapping' 方法。這些方法讓您可以在運行時變更、新增和移除路由器映射,只要您可以參考路由器本身。這也意味著您可以透過 JMX(請參閱 JMX 支援)或 Spring Integration 控制匯流排(請參閱 控制匯流排)功能公開這些相同的設定選項。

回退到通道鍵作為通道名稱是靈活且方便的。然而,如果您不信任訊息建立者,惡意行為者(了解系統)可能會建立路由到意外通道的訊息。例如,如果將鍵設定為路由器輸入通道的通道名稱,則此類訊息將路由回路由器,最終導致堆疊溢位錯誤。因此,您可能希望停用此功能(將 channelKeyFallback 屬性設定為 false),並在需要時變更映射。

使用控制匯流排管理路由器映射

管理路由器映射的一種方法是透過 控制匯流排 模式,該模式公開了一個控制通道,您可以將控制訊息傳送到該通道,以管理和監控 Spring Integration 元件,包括路由器。

有關控制匯流排的更多資訊,請參閱 控制匯流排

通常,您會傳送一個控制訊息,要求在特定的受管理元件(例如路由器)上調用特定的操作。以下受管理的操作(方法)專用於變更路由器解析流程

  • public void setChannelMapping(String key, String channelName):讓您新增或修改 通道識別符通道名稱 之間的現有映射

  • public void removeChannelMapping(String key):讓您移除特定的通道映射,從而斷開 通道識別符通道名稱 之間的關係

請注意,這些方法可用於簡單的變更(例如更新單一路線或新增或移除路線)。但是,如果您想移除一條路線並新增另一條路線,則更新不是原子性的。這意味著路由表在更新之間可能處於不確定的狀態。從 4.0 版本開始,您現在可以使用控制匯流排以原子方式更新整個路由表。以下方法可讓您執行此操作

  • public Map<String, String>getChannelMappings():傳回目前的映射。

  • public void replaceChannelMappings(Properties channelMappings):更新映射。請注意,channelMappings 參數是一個 Properties 物件。這種安排讓控制匯流排命令可以使用內建的 StringToPropertiesConverter,如下列範例所示

"@'router.handler'.replaceChannelMappings('foo=qux \n baz=bar')"

請注意,每個映射都以換行符號 (\n) 分隔。對於程式化的地圖變更,我們建議您使用 setChannelMappings 方法,因為存在類型安全問題。replaceChannelMappings 會忽略不是 String 物件的鍵或值。

使用 JMX 管理路由器映射

您也可以使用 Spring 的 JMX 支援來公開路由器實例,然後使用您最喜歡的 JMX 用戶端(例如,JConsole)來管理這些操作(方法),以變更路由器的設定。

有關 Spring Integration 的 JMX 支援的更多資訊,請參閱 JMX 支援