動態路由器
Spring Integration 提供了許多不同的路由器設定,適用於常見的基於內容的路由使用案例,並提供實作自訂路由器作為 POJO 的選項。例如,PayloadTypeRouter
提供了一種簡單的方式來設定路由器,該路由器根據傳入訊息的酬載類型計算通道,而 HeaderValueRouter
在設定路由器方面提供了相同的便利性,該路由器透過評估特定訊息標頭的值來計算通道。還有基於表達式 (SpEL) 的路由器,其中通道是根據評估表達式來確定的。所有這些類型的路由器都表現出一些動態特性。
然而,這些路由器都需要靜態設定。即使在基於表達式的路由器的情況下,表達式本身也被定義為路由器設定的一部分,這意味著對相同值運作的相同表達式始終會導致計算出相同的通道。這在大多數情況下是可以接受的,因為這些路由是明確定義的,因此是可預測的。但是,有時我們需要動態變更路由器設定,以便訊息流可以路由到不同的通道。
例如,您可能想要關閉系統的某些部分進行維護,並暫時將訊息重新路由到不同的訊息流。另一個範例是,您可能想要透過新增另一個路由來處理更具體的 java.lang.Number
類型(在 PayloadTypeRouter
的情況下),從而提高訊息流的精細度。
不幸的是,使用靜態路由器設定來達成這些目標中的任何一個,您都必須關閉整個應用程式、變更路由器的設定(變更路由),然後重新啟動應用程式。這顯然不是任何人想要的解決方案。
動態路由器 模式描述了您可以動態變更或設定路由器,而無需關閉系統或個別路由器的機制。
在我們深入探討 Spring Integration 如何支援動態路由之前,我們需要考慮路由器的典型流程
-
計算通道識別符,這是路由器接收訊息後計算的值。通常,它是一個字串或實際
MessageChannel
的實例。 -
將通道識別符解析為通道名稱。我們稍後在本節中描述此過程的細節。
-
將通道名稱解析為實際的
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>
在酬載類型路由器的上下文中,前面提到的三個步驟將實現如下
-
計算通道識別符,即酬載類型的完整名稱(例如,
java.lang.String
)。 -
將通道識別符解析為通道名稱,其中前一個步驟的結果用於從
mapping
元素中定義的酬載類型映射中選擇適當的值。 -
將通道名稱解析為
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>
現在我們可以考慮標頭值路由器的三個步驟如何運作
-
計算通道識別符,即由
header-name
屬性識別的標頭值。 -
將通道識別符解析為通道名稱,其中前一個步驟的結果用於從
mapping
元素中定義的通用映射中選擇適當的值。 -
將通道名稱解析為
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 支援。 |