內容豐富器
有時,您可能需要使用比目標系統提供的更多資訊來增強請求。 資料豐富器 模式描述了各種情境,以及讓您解決這些需求的元件 (Enricher)。
Spring Integration Core
模組包含兩個豐富器
它還包含三個特定於配接器的標頭豐富器
請參閱本參考手冊中特定於配接器的章節,以瞭解有關這些配接器的更多資訊。
有關運算式支援的更多資訊,請參閱 Spring 運算式語言 (SpEL)。
標頭豐富器
如果您只需要將標頭新增至訊息,且標頭並非由訊息內容動態決定,則參考轉換器的自訂實作可能過於繁瑣。因此,Spring Integration 提供了對標頭豐富器模式的支援。它透過 <header-enricher>
元素公開。以下範例示範如何使用它
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="foo" value="123"/>
<int:header name="bar" ref="someBean"/>
</int:header-enricher>
標頭豐富器還提供有用的子元素來設定眾所周知的標頭名稱,如下列範例所示
<int:header-enricher input-channel="in" output-channel="out">
<int:error-channel ref="applicationErrorChannel"/>
<int:reply-channel ref="quoteReplyChannel"/>
<int:correlation-id value="123"/>
<int:priority value="HIGHEST"/>
<routing-slip value="channel1; routingSlipRoutingStrategy; request.headers[myRoutingSlipChannel]"/>
<int:header name="bar" ref="someBean"/>
</int:header-enricher>
先前的組態顯示,對於眾所周知的標頭 (例如 errorChannel
、correlationId
、priority
、replyChannel
、routing-slip
等),您可以直接使用方便的子元素來設定這些值,而無需使用通用的 <header>
子元素,在其中您必須同時提供標頭「名稱」和「值」。
從 4.1 版開始,標頭豐富器提供了 routing-slip
子元素。有關更多資訊,請參閱 路由滑軌。
POJO 支援
通常,標頭值無法靜態定義,而必須根據訊息中的某些內容動態決定。這就是標頭豐富器也允許您使用 ref
和 method
屬性指定 Bean 參考的原因。指定的方法會計算標頭值。請考慮以下組態和具有修改 String
的方法的 Bean
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="something" method="computeValue" ref="myBean"/>
</int:header-enricher>
<bean id="myBean" class="thing1.thing2.MyBean"/>
public class MyBean {
public String computeValue(String payload){
return payload.toUpperCase() + "_US";
}
}
您也可以將 POJO 設定為內部 Bean,如下列範例所示
<int:header-enricher input-channel="inputChannel" output-channel="outputChannel">
<int:header name="some_header">
<bean class="org.MyEnricher"/>
</int:header>
</int:header-enricher>
您可以類似地指向 Groovy 指令碼,如下列範例所示
<int:header-enricher input-channel="inputChannel" output-channel="outputChannel">
<int:header name="some_header">
<int-groovy:script location="org/SampleGroovyHeaderEnricher.groovy"/>
</int:header>
</int:header-enricher>
SpEL 支援
在 Spring Integration 2.0 中,我們引入了 Spring 運算式語言 (SpEL) 的便利性,以協助設定許多不同的元件。標頭豐富器就是其中之一。再次查看先前顯示的 POJO 範例。您可以看到決定標頭值的計算邏輯非常簡單。一個自然的問題是:「是否有更簡單的方法來完成此操作?」。這就是 SpEL 展現其真正力量的地方。請考慮以下範例
<int:header-enricher input-channel="in" output-channel="out">
<int:header name="foo" expression="payload.toUpperCase() + '_US'"/>
</int:header-enricher>
透過為如此簡單的情況使用 SpEL,您不再需要提供單獨的類別並在應用程式內容中設定它。您只需要使用有效的 SpEL 運算式設定 expression
屬性即可。「payload」和「headers」變數會繫結到 SpEL 評估內容,讓您可以完全存取傳入的訊息。
使用 Java 組態設定標頭豐富器
以下兩個範例示範如何將 Java 組態用於標頭豐富器
@Bean
@Transformer(inputChannel = "enrichHeadersChannel", outputChannel = "emailChannel")
public HeaderEnricher enrichHeaders() {
Map<String, ? extends HeaderValueMessageProcessor<?>> headersToAdd =
Collections.singletonMap("emailUrl",
new StaticHeaderValueMessageProcessor<>(this.imapUrl));
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
return enricher;
}
@Bean
@Transformer(inputChannel="enrichHeadersChannel", outputChannel="emailChannel")
public HeaderEnricher enrichHeaders() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<>();
headersToAdd.put("emailUrl", new StaticHeaderValueMessageProcessor<String>(this.imapUrl));
Expression expression = new SpelExpressionParser().parseExpression("payload.from[0].toString()");
headersToAdd.put("from",
new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, String.class));
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
return enricher;
}
第一個範例新增單一常值標頭。第二個範例新增兩個標頭,一個常值標頭和一個基於 SpEL 運算式的標頭。
使用 Java DSL 設定標頭豐富器
以下範例示範標頭豐富器的 Java DSL 組態
@Bean
public IntegrationFlow enrichHeadersInFlow() {
return f -> f
...
.enrichHeaders(h -> h.header("emailUrl", this.emailUrl)
.headerExpression("from", "payload.from[0].toString()"))
.handle(...);
}
標頭通道登錄
從 Spring Integration 3.0 開始,提供了一個新的子元素 <int:header-channels-to-string/>
。它沒有任何屬性。這個新的子元素會將現有的 replyChannel
和 errorChannel
標頭 (當它們是 MessageChannel
時) 轉換為 String
,並將通道儲存在登錄中以供稍後解析,以便在需要傳送回覆或處理錯誤時使用。這對於標頭可能會遺失的情況很有用,例如,當將訊息序列化到訊息儲存區中,或透過 JMS 傳輸訊息時。如果標頭尚不存在,或它不是 MessageChannel
,則不會進行任何變更。
使用此功能需要存在 HeaderChannelRegistry
Bean。依預設,框架會建立具有預設到期時間 (60 秒) 的 DefaultHeaderChannelRegistry
。通道會在經過此時間後從登錄中移除。若要變更此行為,請定義一個 id
為 integrationHeaderChannelRegistry
的 Bean,並透過使用建構子引數 (以毫秒為單位) 設定所需的預設延遲。
從 4.1 版開始,您可以將名為 removeOnGet
的屬性設定為 <bean/>
定義上的 true
,並且對應專案會在第一次使用時立即移除。這在大量環境中可能很有用,並且當通道僅使用一次時,而不是等待清除器移除它。
HeaderChannelRegistry
具有 size()
方法來判斷登錄的目前大小。runReaper()
方法會取消目前排程的工作,並立即執行清除器。然後會根據目前的延遲再次排程工作以執行。這些方法可以直接透過取得登錄的參考來叫用,或者您可以將訊息傳送到控制匯流排,例如具有以下內容
"@integrationHeaderChannelRegistry.runReaper()"
這個子元素很方便,並且相當於指定以下組態
<int:reply-channel
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.replyChannel)"
overwrite="true" />
<int:error-channel
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.errorChannel)"
overwrite="true" />
從 4.1 版開始,您現在可以覆寫登錄設定的清除器延遲,以便通道對應至少保留指定的時間,而與清除器延遲無關。以下範例示範如何執行此操作
<int:header-enricher input-channel="inputTtl" output-channel="next">
<int:header-channels-to-string time-to-live-expression="120000" />
</int:header-enricher>
<int:header-enricher input-channel="inputCustomTtl" output-channel="next">
<int:header-channels-to-string
time-to-live-expression="headers['channelTTL'] ?: 120000" />
</int:header-enricher>
在第一種情況下,每個標頭通道對應的存留時間將為兩分鐘。在第二種情況下,存留時間在訊息標頭中指定,並使用 Elvis 運算子,如果沒有標頭,則使用兩分鐘。
酬載豐富器
在某些情況下,如先前討論的標頭豐富器可能不足,並且酬載本身可能必須使用額外資訊來豐富。例如,進入 Spring Integration 訊息傳遞系統的訂單訊息必須根據提供的客戶編號查閱訂單的客戶,然後使用該資訊豐富原始酬載。
Spring Integration 2.1 引入了酬載豐富器。酬載豐富器定義了一個端點,該端點將 Message
傳遞到公開的請求通道,然後預期回覆訊息。然後,回覆訊息會成為評估運算式的根物件,以豐富目標酬載。
酬載豐富器透過 enricher
元素提供完整的 XML 命名空間支援。為了傳送請求訊息,酬載豐富器具有 request-channel
屬性,可讓您將訊息分派到請求通道。
基本上,透過定義請求通道,酬載豐富器會充當閘道,等待傳送到請求通道的訊息傳回。然後,豐富器會使用回覆訊息提供的資料來擴增訊息的酬載。
當將訊息傳送到請求通道時,您也可以選擇僅使用 request-payload-expression
屬性傳送原始酬載的子集。
酬載的豐富化是透過 SpEL 運算式設定的,從而提供最大程度的彈性。因此,您不僅可以使用回覆通道 Message
中的直接值來豐富酬載,還可以透過 SpEL 運算式從該訊息中擷取子集,或套用其他內嵌轉換,讓您可以進一步操作資料。
如果您只需要使用靜態值來豐富酬載,則無需提供 request-channel
屬性。
豐富器是轉換器的變體。在許多情況下,您可以使用酬載豐富器或通用轉換器實作,將額外資料新增至您的訊息酬載。您應該熟悉 Spring Integration 提供的所有具有轉換能力的元件,並仔細選擇在語意上最適合您的業務案例的實作。 |
組態
以下範例顯示酬載豐富器的所有可用組態選項
<int:enricher request-channel="" (1)
auto-startup="true" (2)
id="" (3)
order="" (4)
output-channel="" (5)
request-payload-expression="" (6)
reply-channel="" (7)
error-channel="" (8)
send-timeout="" (9)
should-clone-payload="false"> (10)
<int:poller></int:poller> (11)
<int:property name="" expression="" null-result-expression="'Could not determine the name'"/> (12)
<int:property name="" value="23" type="java.lang.Integer" null-result-expression="'0'"/>
<int:header name="" expression="" null-result-expression=""/> (13)
<int:header name="" value="" overwrite="" type="" null-result-expression=""/>
</int:enricher>
1 | 訊息傳送到的通道,以取得用於豐富化的資料。選用。 |
2 | 生命週期屬性,表示此元件是否應在應用程式內容啟動期間啟動。預設為 true。選用。 |
3 | 基礎 Bean 定義的 ID,它是 EventDrivenConsumer 或 PollingConsumer 。選用。 |
4 | 指定當此端點作為訂閱者連接到通道時的叫用順序。當該通道使用「容錯移轉」分派策略時,這尤其相關。當此端點本身是具有佇列的通道的輪詢消費者時,它沒有任何作用。選用。 |
5 | 識別訊息在此端點處理後傳送到的訊息通道。選用。 |
6 | 依預設,原始訊息的酬載會用作傳送到 request-channel 的酬載。透過將 SpEL 運算式指定為 request-payload-expression 屬性的值,您可以使用原始酬載的子集、標頭值或任何其他可解析的 SpEL 運算式作為傳送到請求通道的酬載的基礎。對於運算式評估,完整訊息會作為「根物件」提供。例如,以下 SpEL 運算式 (以及其他運算式) 是可能的:payload.something 、headers.something 、new java.util.Date() 、'thing1' + 'thing2' |
7 | 預期回覆訊息的通道。這是選用的。通常,自動產生的暫時回覆通道就足夠了。選用。 |
8 | 當 Exception 在 request-channel 的下游發生時,ErrorMessage 傳送到的通道。這可讓您傳回替代物件以用於豐富化。如果未設定,則會將 Exception 擲回給呼叫者。選用。 |
9 | 當傳送訊息到通道時要等待的最長時間 (以毫秒為單位),如果通道可能會封鎖。例如,佇列通道可能會封鎖,直到空間可用 (如果已達到其最大容量)。在內部,send() 逾時會在 MessagingTemplate 上設定,並最終在叫用 MessageChannel 上的傳送操作時套用。依預設,send() 逾時設定為 '30'。選用。 |
10 | 布林值,表示是否應在將訊息傳送到請求通道以取得豐富化資料之前,複製任何實作 Cloneable 的酬載。複製的版本將用作最終回覆的目標酬載。預設值為 false 。選用。 |
11 | 如果此端點是輪詢消費者,則可讓您設定訊息輪詢器。選用。 |
12 | 每個 property 子元素都提供屬性的名稱 (透過強制性的 name 屬性)。該屬性應可在目標酬載實例上設定。也必須提供 value 或 expression 屬性中的正好一個,前者用於要設定的常值,後者用於要評估的 SpEL 運算式。評估內容的根物件是從此豐富器啟動的流程傳回的訊息 (如果沒有請求通道,則為輸入訊息,或使用 @<beanName>.<beanProperty> SpEL 語法的應用程式內容)。從 4.0 版開始,當指定 value 屬性時,您也可以指定選用的 type 屬性。當目的地是類型化的 setter 方法時,只要存在 PropertyEditor 來處理轉換,框架就會適當地強制轉換值。但是,如果目標酬載是 Map ,則會使用值填入專案,而無需轉換。例如,type 屬性可讓您將包含數字的 String 轉換為目標酬載中的 Integer 值。從 4.1 版開始,您也可以指定選用的 null-result-expression 屬性。當 enricher 傳回 null 時,會評估它,並改為傳回評估的輸出。 |
13 | 每個 header 子元素都提供訊息標頭的名稱 (透過強制性的 name 屬性)。也必須提供 value 或 expression 屬性中的正好一個,前者用於要設定的常值,後者用於要評估的 SpEL 運算式。評估內容的根物件是從此豐富器啟動的流程傳回的訊息 (如果沒有請求通道,則為輸入訊息,或使用 '@<beanName>.<beanProperty>' SpEL 語法的應用程式內容)。請注意,與 <header-enricher> 類似,<enricher> 元素的 header 元素具有 type 和 overwrite 屬性。但是,一個關鍵差異是,對於 <enricher> ,overwrite 屬性依預設為 true ,以便與 <enricher> 元素的 <property> 子元素保持一致。從 4.1 版開始,您也可以指定選用的 null-result-expression 屬性。當 enricher 傳回 null 時,會評估它,並改為傳回評估的輸出。 |
範例
本節包含在各種情況下使用酬載豐富器的幾個範例。
此處顯示的程式碼範例是 Spring Integration Samples 專案的一部分。請參閱 Spring Integration 範例。 |
在以下範例中,User
物件作為 Message
的酬載傳遞
<int:enricher id="findUserEnricher"
input-channel="findUserEnricherChannel"
request-channel="findUserServiceChannel">
<int:property name="email" expression="payload.email"/>
<int:property name="password" expression="payload.password"/>
</int:enricher>
User
具有多個屬性,但最初僅設定 username
。豐富器的 request-channel
屬性已設定為將 User
傳遞到 findUserServiceChannel
。
透過隱含設定的 reply-channel
,會傳回 User
物件,並且透過使用 property
子元素,會從回覆中擷取屬性並用於豐富原始酬載。
如何僅將資料子集傳遞到請求通道?
當使用 request-payload-expression
屬性時,可以將酬載的單一屬性 (而不是完整訊息) 傳遞到請求通道。在以下範例中,username 屬性會傳遞到請求通道
<int:enricher id="findUserByUsernameEnricher"
input-channel="findUserByUsernameEnricherChannel"
request-channel="findUserByUsernameServiceChannel"
request-payload-expression="payload.username">
<int:property name="email" expression="payload.email"/>
<int:property name="password" expression="payload.password"/>
</int:enricher>
請記住,儘管僅傳遞了 username,但傳送到請求通道的結果訊息包含完整的 MessageHeaders
集。
如何豐富由集合資料組成的酬載?
在以下範例中,不是傳遞 User
物件,而是傳遞 Map
<int:enricher id="findUserWithMapEnricher"
input-channel="findUserWithMapEnricherChannel"
request-channel="findUserByUsernameServiceChannel"
request-payload-expression="payload.username">
<int:property name="user" expression="payload"/>
</int:enricher>
Map
包含 username
地圖索引鍵下的使用者名稱。只有 username
會傳遞到請求通道。回覆包含完整的 User
物件,最終會新增至 user
索引鍵下的 Map
。
如何在不使用請求通道的情況下使用靜態資訊豐富酬載?
以下範例完全未使用請求通道,而僅使用靜態值豐富訊息的酬載
<int:enricher id="userEnricher"
input-channel="input">
<int:property name="user.updateDate" expression="new java.util.Date()"/>
<int:property name="user.firstName" value="William"/>
<int:property name="user.lastName" value="Shakespeare"/>
<int:property name="user.age" value="42"/>
</int:enricher>
請注意,此處「靜態」一詞的使用較為寬鬆。您仍然可以使用 SpEL 運算式來設定這些值。