コンテンツエンリッチャー

場合によっては、ターゲットシステムによって提供されたよりも多くの情報でリクエストを強化する必要があるかもしれません。データエンリッチャー (英語) パターンでは、さまざまなシナリオと、そのような要件に対処できるコンポーネント(エンリッチャー)について説明します。

Spring Integration Core モジュールには、2 つのエンリッチャーが含まれています。

また、3 つのアダプター固有のヘッダーエンリッチャーが含まれています。

これらのアダプターの詳細については、このリファレンスマニュアルのアダプター固有のセクションを参照してください。

式のサポートに関する詳細については、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 など)について、ヘッダー 'name' と 'value' の両方を提供する必要がある一般的な <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) の便利な機能を導入して、さまざまなコンポーネントの構成を支援しました。ヘッダーエンリッチャーはその 1 つです。前に示した 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 構成を使用したヘッダーエンリッチャーの構成

次の 2 つの例は、ヘッダーエンリッチャーに 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;
}

最初の例は、単一のリテラルヘッダーを追加します。2 番目の例では、リテラルヘッダーと SpEL 式に基づくヘッダーの 2 つのヘッダーを追加します。

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 を作成します。この時間を過ぎると、チャネルはレジストリから削除されます。この動作を変更するには、integrationHeaderChannelRegistry の id で Bean を定義し、コンストラクター引数(ミリ秒単位)を使用して、必要なデフォルト遅延を構成します。

バージョン 4.1 以降、removeOnGet というプロパティを <bean/> 定義の true に設定でき、マッピングエントリは最初の使用時にすぐに削除されます。これは、大規模な環境で、リーパーが削除するのを待つのではなく、チャネルが 1 回だけ使用される場合に役立ちます。

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>

最初の場合、すべてのヘッダーチャネルマッピングの有効期間は 2 分です。2 番目のケースでは、有効期間はメッセージヘッダーで指定され、Elvis 演算子を使用して、ヘッダーがない場合は 2 分間を使用します。

ペイロードエンリッチャー

特定の状況では、前述のヘッダーエンリッチャーでは不十分な場合があり、ペイロード自体に追加情報を追加する必要がある場合があります。例: 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 に送信されるペイロードとして使用されます。request-payload-expression 属性の値として SpEL 式を指定することにより、元のペイロードのサブセット、ヘッダー値、その他の解決可能な SpEL 式を、リクエストチャネルに送信されるペイロードの基礎として使用できます。式の評価では、メッセージ全体を「ルートオブジェクト」として使用できます。たとえば、次の SpEL 式(とりわけ)が可能です: payload.somethingheaders.somethingnew java.util.Date()'thing1' + 'thing2'
7 返信メッセージが期待されるチャネル。これはオプションです。通常、自動生成された一時的な応答チャネルで十分です。オプション。
8Exception が request-channel のダウンストリームに発生した場合、ErrorMessage が送信されるチャネル。これにより、濃縮に使用する代替オブジェクトを返すことができます。設定されていない場合、Exception が呼び出し元にスローされます。オプション。
9 チャネルがブロックする可能性がある場合、チャネルにメッセージを送信するときに待機する最大時間(ミリ秒)。例: キューチャネルは、最大容量に達した場合、スペースが使用可能になるまでブロックできます。内部的には、送信タイムアウトは MessagingTemplate で設定され、MessageChannel で送信操作を呼び出すときに最終的に適用されます。デフォルトでは、送信タイムアウトは "-1" に設定されており、実装によっては、MessageChannel での送信操作が無期限にブロックされる可能性があります。オプション。
10Cloneable を実装するペイロードを、強化データを取得するためにリクエストチャネルにメッセージを送信する前に複製する必要があるかどうかを示すブール値。クローンバージョンは、最終的な応答のターゲットペイロードとして使用されます。デフォルトは false です。オプション。
11 このエンドポイントがポーリングコンシューマーである場合、メッセージポーラーを設定できます。オプション。
12 各 property サブ要素は、プロパティの名前を提供します(必須の name 属性を介して)。そのプロパティは、ターゲットペイロードインスタンスで設定可能である必要があります。value または expression 属性の正確に 1 つも指定する必要があります。前者はリテラル値を設定し、後者は評価する 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 属性のいずれか 1 つも指定する必要があります。前者はリテラル値を設定し、後者は 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 属性を使用する場合、完全なメッセージではなくペイロードの単一のプロパティをリクエストチャネルに渡すことができます。次の例では、ユーザー名プロパティがリクエストチャネルに渡されます。

<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 の完全なセットが含まれることに留意してください。

コレクションデータで構成されるペイロードを強化する方法はありますか?

次の例では、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 式を引き続き使用できます。