內容豐富器
有時,你可能需要用比目標系統提供的資訊更多的資料來豐富請求。資料豐富器(data enricher)模式描述了各種場景以及讓你能夠滿足此類需求的元件(Enricher)。
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>
子元素來同時提供頭部“name”和“value”。
從 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
,對映條目將在首次使用時立即移除。這在高併發環境以及通道只使用一次的情況下可能非常有用,無需等待 reaper 移除它。
HeaderChannelRegistry
有一個 size()
方法用於確定登錄檔的當前大小。runReaper()
方法會取消當前計劃的任務並立即執行 reaper。然後根據當前延遲重新計劃任務。你可以透過獲取登錄檔的引用直接呼叫這些方法,或者你可以傳送一個訊息,例如包含以下內容,到控制匯流排:
"integrationHeaderChannelRegistry.runReaper"
此子元素是為了方便,相當於指定以下配置:
<int:reply-channel
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.replyChannel)"
overwrite="true" />
<int:error-channel
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.errorChannel)"
overwrite="true" />
從 4.1 版本開始,你現在可以覆蓋登錄檔配置的 reaper 延遲,以便無論 reaper 延遲如何,通道對映都至少保留指定的時間。以下示例展示瞭如何執行此操作:
<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 | 如果 request-channel 下游發生 Exception ,則將 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
。
如何豐富包含集合資料的負載?
在以下示例中,傳遞的是一個 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
物件,該物件最終在 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 表示式來設定這些值。