內容豐富器
有時,你可能需要用比目標系統提供的資訊更多的資訊來增強請求。 資料豐富器模式描述了各種場景以及讓你滿足此類需求的元件(豐富器)。
Spring Integration Core 模組包含兩個豐富器
它還包括三個介面卡特定的訊息頭豐富器
請參閱本參考手冊中介面卡特定部分以瞭解有關這些介面卡的更多資訊。
有關表示式支援的更多資訊,請參閱 Spring Expression Language (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 Expression Language (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 以來,您可以在 <bean/> 定義上將名為 removeOnGet 的屬性設定為 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 表示式作為傳送到 request-channel 的訊息體的基礎。對於表示式評估,完整訊息作為“根物件”可用。例如,以下 SpEL 表示式(以及其他)是可能的:payload.something、headers.something、new java.util.Date()、'thing1' + 'thing2' |
| 7 | 期望回覆訊息的通道。這是可選的。通常,自動生成的臨時回覆通道就足夠了。可選。 |
| 8 | 如果 request-channel 的下游發生 Exception,則傳送 ErrorMessage 的通道。這使您可以返回一個用於豐富替代物件。如果未設定,則向呼叫方丟擲 Exception。可選。 |
| 9 | 如果通道可能阻塞,則傳送訊息到通道時等待的最大毫秒數。例如,如果佇列通道達到其最大容量,它可能會阻塞直到有空間可用。在內部,send() 超時在 MessagingTemplate 上設定,並最終在呼叫 MessageChannel 上的 send 操作時應用。預設情況下,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 Samples。 |
在以下示例中,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 屬性時,可以將訊息體的單個屬性而不是完整訊息傳遞到請求通道。在以下示例中,使用者名稱屬性被傳遞到請求通道
<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>
請記住,儘管只傳遞了使用者名稱,但傳送到請求通道的結果訊息包含完整的 MessageHeaders。
如何豐富包含集合資料的訊息體?
在以下示例中,傳入的是 Map 而不是 User 物件
<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 物件,該物件最終被新增到 Map 的 user 鍵下。
如何在不使用請求通道的情況下用靜態資訊豐富訊息體?
以下示例根本不使用請求通道,而僅僅用靜態值豐富訊息的訊息體
<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 表示式來設定這些值。