內容豐富器

有時,您可能需要使用比目標系統提供的更多資訊來增強請求。 資料豐富器 模式描述了各種情境,以及讓您解決這些需求的元件 (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>

先前的組態顯示,對於眾所周知的標頭 (例如 errorChannelcorrelationIdpriorityreplyChannelrouting-slip 等),您可以直接使用方便的子元素來設定這些值,而無需使用通用的 <header> 子元素,在其中您必須同時提供標頭「名稱」和「值」。

從 4.1 版開始,標頭豐富器提供了 routing-slip 子元素。有關更多資訊,請參閱 路由滑軌

POJO 支援

通常,標頭值無法靜態定義,而必須根據訊息中的某些內容動態決定。這就是標頭豐富器也允許您使用 refmethod 屬性指定 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/>。它沒有任何屬性。這個新的子元素會將現有的 replyChannelerrorChannel 標頭 (當它們是 MessageChannel 時) 轉換為 String,並將通道儲存在登錄中以供稍後解析,以便在需要傳送回覆或處理錯誤時使用。這對於標頭可能會遺失的情況很有用,例如,當將訊息序列化到訊息儲存區中,或透過 JMS 傳輸訊息時。如果標頭尚不存在,或它不是 MessageChannel,則不會進行任何變更。

使用此功能需要存在 HeaderChannelRegistry Bean。依預設,框架會建立具有預設到期時間 (60 秒) 的 DefaultHeaderChannelRegistry。通道會在經過此時間後從登錄中移除。若要變更此行為,請定義一個 idintegrationHeaderChannelRegistry 的 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,它是 EventDrivenConsumerPollingConsumer。選用。
4 指定當此端點作為訂閱者連接到通道時的叫用順序。當該通道使用「容錯移轉」分派策略時,這尤其相關。當此端點本身是具有佇列的通道的輪詢消費者時,它沒有任何作用。選用。
5 識別訊息在此端點處理後傳送到的訊息通道。選用。
6 依預設,原始訊息的酬載會用作傳送到 request-channel 的酬載。透過將 SpEL 運算式指定為 request-payload-expression 屬性的值,您可以使用原始酬載的子集、標頭值或任何其他可解析的 SpEL 運算式作為傳送到請求通道的酬載的基礎。對於運算式評估,完整訊息會作為「根物件」提供。例如,以下 SpEL 運算式 (以及其他運算式) 是可能的:payload.somethingheaders.somethingnew java.util.Date()'thing1' + 'thing2'
7 預期回覆訊息的通道。這是選用的。通常,自動產生的暫時回覆通道就足夠了。選用。
8 Exceptionrequest-channel 的下游發生時,ErrorMessage 傳送到的通道。這可讓您傳回替代物件以用於豐富化。如果未設定,則會將 Exception 擲回給呼叫者。選用。
9 當傳送訊息到通道時要等待的最長時間 (以毫秒為單位),如果通道可能會封鎖。例如,佇列通道可能會封鎖,直到空間可用 (如果已達到其最大容量)。在內部,send() 逾時會在 MessagingTemplate 上設定,並最終在叫用 MessageChannel 上的傳送操作時套用。依預設,send() 逾時設定為 '30'。選用。
10 布林值,表示是否應在將訊息傳送到請求通道以取得豐富化資料之前,複製任何實作 Cloneable 的酬載。複製的版本將用作最終回覆的目標酬載。預設值為 false。選用。
11 如果此端點是輪詢消費者,則可讓您設定訊息輪詢器。選用。
12 每個 property 子元素都提供屬性的名稱 (透過強制性的 name 屬性)。該屬性應可在目標酬載實例上設定。也必須提供 valueexpression 屬性中的正好一個,前者用於要設定的常值,後者用於要評估的 SpEL 運算式。評估內容的根物件是從此豐富器啟動的流程傳回的訊息 (如果沒有請求通道,則為輸入訊息,或使用 @<beanName>.<beanProperty> SpEL 語法的應用程式內容)。從 4.0 版開始,當指定 value 屬性時,您也可以指定選用的 type 屬性。當目的地是類型化的 setter 方法時,只要存在 PropertyEditor 來處理轉換,框架就會適當地強制轉換值。但是,如果目標酬載是 Map,則會使用值填入專案,而無需轉換。例如,type 屬性可讓您將包含數字的 String 轉換為目標酬載中的 Integer 值。從 4.1 版開始,您也可以指定選用的 null-result-expression 屬性。當 enricher 傳回 null 時,會評估它,並改為傳回評估的輸出。
13 每個 header 子元素都提供訊息標頭的名稱 (透過強制性的 name 屬性)。也必須提供 valueexpression 屬性中的正好一個,前者用於要設定的常值,後者用於要評估的 SpEL 運算式。評估內容的根物件是從此豐富器啟動的流程傳回的訊息 (如果沒有請求通道,則為輸入訊息,或使用 '@<beanName>.<beanProperty>' SpEL 語法的應用程式內容)。請注意,與 <header-enricher> 類似,<enricher> 元素的 header 元素具有 typeoverwrite 屬性。但是,一個關鍵差異是,對於 <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 運算式來設定這些值。