メッセージルーティング

この章では、Spring Integration を使用したメッセージのルーティングの詳細について説明します。

ルーター

このセクションでは、ルーターの動作について説明します。次のトピックが含まれます。

概要

ルーターは、多くのメッセージングアーキテクチャで重要な要素です。メッセージチャネルからメッセージを消費し、一連の条件に応じて、消費した各メッセージを 1 つ以上の異なるメッセージチャネルに転送します。

Spring Integration は、次のルーターを提供します。

ルーターの実装は多くの構成パラメーターを共有します。ただし、ルーター間には一定の違いがあります。さらに、構成パラメーターの可用性は、ルーターがチェーンの内部で使用されるか外部で使用されるかによって異なります。簡単な概要を提供するために、使用可能なすべての属性を次の 2 つの表にリストします。

次の表は、チェーンの外部のルーターで使用可能な構成パラメーターを示しています。

表 1: チェーンの外側のルーター
属性 ルーター ヘッダー値ルーター xpath ルーター ペイロード型ルーター 受信者リストルート 例外型ルーター

apply-sequence

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

default-output-channel

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

resolution-required

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

ignore-send-failures

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

timeout

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

id

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

auto-startup

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

input-channel

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

order

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

method

tickmark

ref

tickmark

expression

tickmark

header-name

tickmark

evaluate-as-string

tickmark

xpath-expression-ref

tickmark

converter

tickmark

次の表は、チェーン内部のルーターで使用可能な構成パラメーターを示しています。

表 2: チェーン内部のルーター
属性 ルーター ヘッダー値ルーター xpath ルーター ペイロード型ルーター 受信者リストルーター 例外型ルーター

apply-sequence

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

default-output-channel

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

resolution-required

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

ignore-send-failures

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

timeout

tickmark
tickmark
tickmark
tickmark
tickmark
tickmark

id

auto-startup

input-channel

order

method

tickmark

ref

tickmark

expression

tickmark

header-name

tickmark

evaluate-as-string

tickmark

xpath-expression-ref

tickmark

converter

tickmark

Spring Integration 2.1 の時点で、すべてのルーターの実装でルーターパラメーターがより標準化されました。その結果、いくつかの小さな変更により、古い Spring Integration ベースのアプリケーションが壊れる可能性があります。

Spring Integration 2.1 以降、動作を resolution-required 属性と統合するために、ignore-channel-name-resolution-failures 属性が削除されました。また、resolution-required 属性のデフォルトは true になりました。

これらの変更の前は、resolution-required 属性はデフォルトで false に設定されていたため、チャネルが解決されず、default-output-channel が設定されていない場合、メッセージが静かにドロップされました。新しい動作には少なくとも 1 つの解決されたチャネルが必要であり、デフォルトでは、チャネルが決定されなかった場合(または送信の試行が成功しなかった場合) MessageDeliveryException をスローします。

メッセージを静かにドロップしたい場合は、default-output-channel="nullChannel" を設定できます。

共通のルーターパラメーター

このセクションでは、すべてのルーターパラメーターに共通のパラメーターについて説明します(この章で前述した 2 つの表で、すべてのボックスがチェックされているパラメーター)。

チェーンの内側と外側

以下のパラメーターは、チェーンの内部および外部のすべてのルーターに有効です。

apply-sequence

この属性は、シーケンス番号とサイズのヘッダーを各メッセージに追加するかどうかを指定します。このオプション属性のデフォルトは false です。

default-output-channel

設定されている場合、この属性は、チャネルの解決に失敗した場合にメッセージが送信されるチャネルへの参照を提供します。デフォルトの出力チャネルが提供されていない場合、ルーターは例外をスローします。代わりにこれらのメッセージをサイレントドロップする場合は、デフォルトの出力チャネル属性値を nullChannel に設定します。

resolution-required が false であり、チャネルが解決されない場合、メッセージは default-output-channel にのみ送信されます。
resolution-required

この属性は、チャネル名を常に存在するチャネルインスタンスに正常に解決する必要があるかどうかを指定します。true に設定すると、チャネルを解決できない場合に MessagingException が発生します。この属性を false に設定すると、回復不能なチャネルは無視されます。このオプション属性のデフォルトは true です。

resolution-required が false であり、チャネルが解決されない場合、指定されている場合、メッセージは default-output-channel のみに送信されます。
ignore-send-failures

true に設定されている場合、メッセージチャネルへの送信の失敗は無視されます。false に設定すると、代わりに MessageDeliveryException がスローされ、ルーターが複数のチャネルを解決した場合、後続のチャネルはメッセージを受信しません。

この属性の正確な動作は、メッセージが送信される Channel の型によって異なります。例: 直接チャネル(シングルスレッド)を使用している場合、送信エラーは、さらに下流のコンポーネントによってスローされた例外によって発生する可能性があります。ただし、メッセージを単純なキューチャネル(非同期)に送信する場合、例外がスローされる可能性はむしろリモートです。

ほとんどのルーターは単一のチャネルにルーティングしますが、複数のチャネル名を返すことができます。たとえば、recipient-list-router はまさにそれを行います。単一のチャネルにのみルーティングするルーターでこの属性を true に設定すると、発生した例外はすべて飲み込まれますが、これは通常ほとんど意味がありません。その場合は、フローエントリポイントでエラーフローの例外をキャッチすることをお勧めします。ignore-send-failures 属性を true に設定すると、通常、ルーター実装が複数のチャネル名を返す場合に意味があります。失敗したチャネルに続く他のチャネルがメッセージを受信するためです。

この属性のデフォルトは false です。

timeout

timeout 属性は、ターゲットメッセージチャネルにメッセージを送信するときに待機する最大時間をミリ秒単位で指定します。デフォルトでは、送信操作は無期限にブロックされます。

トップレベル (チェーンの外側)

以下のパラメーターは、チェーンの外部にあるすべての最上位ルーターでのみ有効です。

id

基礎となる Spring Bean 定義を識別します。これは、ルーターの場合、それぞれ EventDrivenConsumer または PollingConsumer のインスタンスであり、ルーターの input-channel がそれぞれ SubscribableChannel か PollableChannel かによって異なります。これはオプションの属性です。

auto-startup

この「ライフサイクル」属性は、アプリケーションコンテキストの起動中にこのコンポーネントを起動する必要があるかどうかを示します。このオプション属性のデフォルトは true です。

input-channel

このエンドポイントの受信メッセージチャネル。

order

この属性は、このエンドポイントがチャンネルのサブスクライバーとして接続されている場合の呼び出しの順序を定義します。これは、そのチャネルがフェイルオーバーディスパッチ戦略を使用する場合に特に関連します。このエンドポイント自体がキューのあるチャネルのポーリングコンシューマーである場合、効果はありません。

ルーターの実装

コンテンツベースのルーティングにはドメイン固有のロジックが必要になることが多いため、ほとんどのユースケースでは、XML 名前空間のサポートまたはアノテーションを使用して POJO に委譲するための Spring Integration のオプションが必要です。これらの両方については、後で説明します。ただし、最初に一般的な要件を満たす実装をいくつか紹介します。

PayloadTypeRouter

次の例に示すように、PayloadTypeRouter はペイロード型マッピングで定義されたチャネルにメッセージを送信します。

<bean id="payloadTypeRouter"
      class="org.springframework.integration.router.PayloadTypeRouter">
    <property name="channelMapping">
        <map>
            <entry key="java.lang.String" value-ref="stringChannel"/>
            <entry key="java.lang.Integer" value-ref="integerChannel"/>
        </map>
    </property>
</bean>

PayloadTypeRouter の構成は、Spring Integration(Namespace Support を参照)によって提供される名前空間によってもサポートされます。PayloadTypeRouter の構成は、<router/> 構成とそれに対応する実装(<bean/> 要素を使用して定義)を単一のより簡潔な構成要素に組み合わせることにより、構成を本質的に簡素化します。次の例は、上記のものと同等ですが、名前空間サポートを使用する PayloadTypeRouter 構成を示しています。

<int:payload-type-router input-channel="routingChannel">
    <int:mapping type="java.lang.String" channel="stringChannel" />
    <int:mapping type="java.lang.Integer" channel="integerChannel" />
</int:payload-type-router>

次の例は、Java で構成された同等のルーターを示しています。

@ServiceActivator(inputChannel = "routingChannel")
@Bean
public PayloadTypeRouter router() {
    PayloadTypeRouter router = new PayloadTypeRouter();
    router.setChannelMapping(String.class.getName(), "stringChannel");
    router.setChannelMapping(Integer.class.getName(), "integerChannel");
    return router;
}

Java DSL を使用する場合、2 つのオプションがあります。

最初に、前の例に示すようにルーターオブジェクトを定義できます。

@Bean
public IntegrationFlow routerFlow1() {
    return IntegrationFlows.from("routingChannel")
            .route(router())
            .get();
}

public PayloadTypeRouter router() {
    PayloadTypeRouter router = new PayloadTypeRouter();
    router.setChannelMapping(String.class.getName(), "stringChannel");
    router.setChannelMapping(Integer.class.getName(), "integerChannel");
    return router;
}

ルーターは @Bean にできますが、そうする必要はありません。@Bean でない場合、フローはそれを登録します。

次に、次の例に示すように、DSL フロー自体内でルーティング機能を定義できます。

@Bean
public IntegrationFlow routerFlow2() {
    return IntegrationFlows.from("routingChannel")
            .<Object, Class<?>>route(Object::getClass, m -> m
                    .channelMapping(String.class, "stringChannel")
                    .channelMapping(Integer.class, "integerChannel"))
            .get();
}
HeaderValueRouter

HeaderValueRouter は、個々のヘッダー値マッピングに基づいてメッセージをチャネルに送信します。HeaderValueRouter が作成されると、評価されるヘッダーの名前で初期化されます。ヘッダーの値は、次の 2 つのいずれかです。

  • 任意の値

  • チャンネル名

任意の値である場合、これらのヘッダー値からチャネル名への追加マッピングが必要です。それ以外の場合、追加の構成は必要ありません。

Spring Integration は、HeaderValueRouter を構成するための単純な名前空間ベースの XML 構成を提供します。次の例は、チャネルへのヘッダー値のマッピングが必要な場合の HeaderValueRouter の構成を示しています。

<int:header-value-router input-channel="routingChannel" header-name="testHeader">
    <int:mapping value="someHeaderValue" channel="channelA" />
    <int:mapping value="someOtherHeaderValue" channel="channelB" />
</int:header-value-router>

解決プロセス中に、前の例で定義されたルーターでチャネル解決エラーが発生し、例外が発生する場合があります。このような例外を抑制し、未解決のメッセージをデフォルトの出力チャネル(default-output-channel 属性で識別)に送信する場合は、resolution-required を false に設定します。

通常、ヘッダー値が明示的にチャネルにマッピングされていないメッセージは default-output-channel に送信されます。ただし、ヘッダー値がチャネル名にマップされているが、チャネルを解決できない場合、resolution-required 属性を false に設定すると、そのようなメッセージが default-output-channel にルーティングされます。

Spring Integration 2.1 以降、属性は ignore-channel-name-resolution-failures から resolution-required に変更されました。属性 resolution-required のデフォルトは true です。

次の例は、Java で構成された同等のルーターを示しています。

@ServiceActivator(inputChannel = "routingChannel")
@Bean
public HeaderValueRouter router() {
    HeaderValueRouter router = new HeaderValueRouter("testHeader");
    router.setChannelMapping("someHeaderValue", "channelA");
    router.setChannelMapping("someOtherHeaderValue", "channelB");
    return router;
}

Java DSL を使用する場合、2 つのオプションがあります。最初に、前の例に示すようにルーターオブジェクトを定義できます。

@Bean
public IntegrationFlow routerFlow1() {
    return IntegrationFlows.from("routingChannel")
            .route(router())
            .get();
}

public HeaderValueRouter router() {
    HeaderValueRouter router = new HeaderValueRouter("testHeader");
    router.setChannelMapping("someHeaderValue", "channelA");
    router.setChannelMapping("someOtherHeaderValue", "channelB");
    return router;
}

ルーターは @Bean にできますが、そうする必要はありません。@Bean でない場合、フローはそれを登録します。

次に、次の例に示すように、DSL フロー自体内でルーティング機能を定義できます。

@Bean
public IntegrationFlow routerFlow2() {
    return IntegrationFlows.from("routingChannel")
            .<Message<?>, String>route(m -> m.getHeaders().get("testHeader", String.class), m -> m
                    .channelMapping("someHeaderValue", "channelA")
                    .channelMapping("someOtherHeaderValue", "channelB"),
                e -> e.id("headerValueRouter"))
            .get();
}

ヘッダー値自体がチャネル名を表すため、ヘッダー値のチャネル名へのマッピングが不要な構成。次の例は、ヘッダー値をチャネル名にマッピングする必要のないルーターを示しています。

<int:header-value-router input-channel="routingChannel" header-name="testHeader"/>

Spring Integration 2.1 以降、チャネル解決の動作はより明確になりました。例: default-output-channel 属性を省略した場合、ルーターは少なくとも 1 つの有効なチャネルを解決できず、resolution-required を false に設定することでチャネル名解決の失敗は無視され、MessageDeliveryException がスローされます。

基本的に、デフォルトでは、ルーターはメッセージを少なくとも 1 つのチャネルに正常にルーティングできる必要があります。メッセージを本当にドロップしたい場合は、default-output-channel を nullChannel に設定する必要もあります。

RecipientListRouter

RecipientListRouter は、受信した各メッセージを静的に定義されたメッセージチャネルのリストに送信します。次の例では、RecipientListRouter を作成します。

<bean id="recipientListRouter"
      class="org.springframework.integration.router.RecipientListRouter">
    <property name="channels">
        <list>
            <ref bean="channel1"/>
            <ref bean="channel2"/>
            <ref bean="channel3"/>
        </list>
    </property>
</bean>

Spring Integration は、次の例に示すように、RecipientListRouter 構成(名前空間サポートを参照)の名前空間サポートも提供します。

<int:recipient-list-router id="customRouter" input-channel="routingChannel"
        timeout="1234"
        ignore-send-failures="true"
        apply-sequence="true">
  <int:recipient channel="channel1"/>
  <int:recipient channel="channel2"/>
</int:recipient-list-router>

次の例は、Java で構成された同等のルーターを示しています。

@ServiceActivator(inputChannel = "routingChannel")
@Bean
public RecipientListRouter router() {
    RecipientListRouter router = new RecipientListRouter();
    router.setSendTimeout(1_234L);
    router.setIgnoreSendFailures(true);
    router.setApplySequence(true);
    router.addRecipient("channel1");
    router.addRecipient("channel2");
    router.addRecipient("channel3");
    return router;
}

次の例は、Java DSL を使用して構成された同等のルーターを示しています。

@Bean
public IntegrationFlow routerFlow() {
    return IntegrationFlows.from("routingChannel")
            .routeToRecipients(r -> r
                    .applySequence(true)
                    .ignoreSendFailures(true)
                    .recipient("channel1")
                    .recipient("channel2")
                    .recipient("channel3")
                    .sendTimeout(1_234L))
            .get();
}
ここでの 'apply-sequence' フラグは、publish-subscribe-channel の場合と同じ効果があり、publish-subscribe-channel の場合と同様に、recipient-list-router ではデフォルトで無効になっています。詳細については、PublishSubscribeChannel 設定を参照してください。

RecipientListRouter を構成する際のもう 1 つの便利なオプションは、Spring Expression Language(SpEL)サポートを個々の受信者チャネルのセレクターとして使用することです。これは、「チェーン」の先頭で「選択的コンシューマー」として機能するフィルターを使用することに似ています。ただし、この場合、次の例に示すように、すべてルーターの構成にかなり簡潔に結合されます。

<int:recipient-list-router id="customRouter" input-channel="routingChannel">
    <int:recipient channel="channel1" selector-expression="payload.equals('foo')"/>
    <int:recipient channel="channel2" selector-expression="headers.containsKey('bar')"/>
</int:recipient-list-router>

上記の構成では、selector-expression 属性で識別される SpEL 式が評価され、この受信者を特定の入力メッセージの受信者リストに含めるかどうかが決定されます。式の評価結果はブール値でなければなりません。この属性が定義されていない場合、チャネルは常に受信者のリストに含まれます。

RecipientListRouterManagement

バージョン 4.1 以降、RecipientListRouter は、実行時に動的に受信者を操作するためのいくつかの操作を提供します。これらの管理操作は、RecipientListRouterManagement によって @ManagedResource アノテーションを介して提示されます。次の例に示すように、制御バスと JMX を使用して使用できます。

<control-bus input-channel="controlBus"/>

<recipient-list-router id="simpleRouter" input-channel="routingChannelA">
   <recipient channel="channel1"/>
</recipient-list-router>

<channel id="channel2"/>
messagingTemplate.convertAndSend(controlBus, "@'simpleRouter.handler'.addRecipient('channel2')");

アプリケーションから simpleRouter を起動すると、channel1 受信者が 1 人だけになります。ただし、addRecipient コマンドの後に、channel2 受信者が追加されます。ある時点でルーターからのメッセージに関心がある場合、「メッセージの一部である何かに関心を登録する」ユースケースであるため、recipient-list-router にサブスクライブし、ある時点で、登録解除。

<recipient-list-router> のランタイム管理操作のため、最初から <recipient> なしで構成できます。この場合、メッセージに一致する受信者がいない場合の RecipientListRouter の動作は同じです。defaultOutputChannel が構成されている場合、メッセージはそこに送信されます。そうでない場合、MessageDeliveryException がスローされます。

XPath ルーター

XPath ルーターは XML モジュールの一部です。XPath を使用した XML メッセージのルーティングを参照してください。

ルーティングとエラー処理

Spring Integration は、ルーティングエラーメッセージ(payload が Throwable インスタンスであるメッセージとして定義される)のために、ErrorMessageExceptionTypeRouter と呼ばれる特別な型ベースのルーターも提供します。ErrorMessageExceptionTypeRouter は PayloadTypeRouter に似ています。実際、それらはほとんど同一です。唯一の違いは、PayloadTypeRouter がペイロードインスタンスのインスタンス階層(たとえば payload.getClass().getSuperclass())をナビゲートして最も具象型とチャネルマッピングを見つけるのに対し、ErrorMessageExceptionTypeRouter は「例外原因」(たとえば payload.getCause())の階層をナビゲートして見つけることです最も具体的な Throwable 型またはチャネルマッピング。mappingClass.isInstance(cause) を使用して、cause をクラスまたはスーパークラスに一致させます。

この場合のチャネルマッピングの順序は重要です。そのため、IllegalArgumentException のマッピングを取得する必要があり、RuntimeException のマッピングを取得する必要がない場合、最初にルーターで最後の設定を行う必要があります。
バージョン 4.3 以降、ErrorMessageExceptionTypeRouter は初期化フェーズ中にすべてのマッピングクラスをロードして、ClassNotFoundException のフェイルファーストを行います。

次の例は、ErrorMessageExceptionTypeRouter のサンプル構成を示しています。

<int:exception-type-router input-channel="inputChannel"
                           default-output-channel="defaultChannel">
    <int:mapping exception-type="java.lang.IllegalArgumentException"
                 channel="illegalChannel"/>
    <int:mapping exception-type="java.lang.NullPointerException"
                 channel="npeChannel"/>
</int:exception-type-router>

<int:channel id="illegalChannel" />
<int:channel id="npeChannel" />

汎用ルーターの構成

Spring Integration は汎用ルーターを提供します。これを汎用ルーティングに使用できます(Spring Integration が提供する他のルーターとは異なり、各ルーターには何らかの特殊化があります)。

XML を使用したコンテンツベースのルーターの構成

router 要素は、ルーターを入力チャネルに接続する方法を提供し、オプションの default-output-channel 属性も受け入れます。ref 属性は、カスタムルーター実装(AbstractMessageRouter を継承する必要があります)の Bean 名を参照します。次の例は、3 つの汎用ルーターを示しています。

<int:router ref="payloadTypeRouter" input-channel="input1"
            default-output-channel="defaultOutput1"/>

<int:router ref="recipientListRouter" input-channel="input2"
            default-output-channel="defaultOutput2"/>

<int:router ref="customRouter" input-channel="input3"
            default-output-channel="defaultOutput3"/>

<beans:bean id="customRouterBean" class="org.foo.MyCustomRouter"/>

あるいは、ref は @Router アノテーションを含む POJO を指す場合があります(後述)か、ref を明示的なメソッド名と組み合わせることができます。メソッドを指定すると、このドキュメントで後述する @Router アノテーションセクションで説明されているのと同じ動作が適用されます。次の例では、ref 属性で POJO を指すルーターを定義しています。

<int:router input-channel="input" ref="somePojo" method="someMethod"/>

カスタムルーターの実装が他の <router> 定義で参照されている場合は、通常 ref 属性を使用することをお勧めします。ただし、カスタムルーターの実装を <router> の単一の定義にスコープする必要がある場合は、次の例に示すように、内部 Bean 定義を提供できます。

<int:router method="someMethod" input-channel="input3"
            default-output-channel="defaultOutput3">
    <beans:bean class="org.foo.MyCustomRouter"/>
</int:router>
同じ <router> 構成で ref 属性と内部ハンドラー定義の両方を使用することは許可されていません。そうすると、あいまいな状態が作成され、例外がスローされます。
ref 属性が AbstractMessageProducingHandler を継承する Bean を参照する場合(フレームワーク自体が提供するルーターなど)、構成はルーターを直接参照するように最適化されます。この場合、各 ref 属性は個別の Bean インスタンス(または prototype -scoped Bean)を参照するか、内部 <bean/> 構成型を使用する必要があります。ただし、この最適化は、ルーター XML 定義でルーター固有の属性を指定しない場合にのみ適用されます。誤って複数の Bean から同じメッセージハンドラーを参照すると、構成例外が発生します。

次の例は、Java で構成された同等のルーターを示しています。

@Bean
@Router(inputChannel = "routingChannel")
public AbstractMessageRouter myCustomRouter() {
    return new AbstractMessageRouter() {

        @Override
        protected Collection<MessageChannel> determineTargetChannels(Message<?> message) {
            return // determine channel(s) for message
        }

    };
}

次の例は、Java DSL を使用して構成された同等のルーターを示しています。

@Bean
public IntegrationFlow routerFlow() {
    return IntegrationFlows.from("routingChannel")
            .route(myCustomRouter())
            .get();
}

public AbstractMessageRouter myCustomRouter() {
    return new AbstractMessageRouter() {

        @Override
        protected Collection<MessageChannel> determineTargetChannels(Message<?> message) {
            return // determine channel(s) for message
        }

    };
}

または、次の例に示すように、メッセージペイロードからデータをルーティングできます。

@Bean
public IntegrationFlow routerFlow() {
    return IntegrationFlows.from("routingChannel")
            .route(String.class, p -> p.contains("foo") ? "fooChannel" : "barChannel")
            .get();
}

ルーターと Spring 式言語 (SpEL)

ルーティングロジックは単純な場合があり、そのための個別のクラスを作成して Bean として構成することは、やり過ぎに見えるかもしれません。Spring Integration 2.0 の時点で、SpEL を使用して、以前はカスタム POJO ルーターを必要とした単純な計算を実装できる代替手段を提供しています。

Spring Expression Language の詳細については、Spring Framework リファレンスガイドの関連する章を参照してください。

一般に、次の例に示すように、SpEL 式が評価され、その結果がチャネルにマッピングされます。

<int:router input-channel="inChannel" expression="payload.paymentType">
    <int:mapping value="CASH" channel="cashPaymentChannel"/>
    <int:mapping value="CREDIT" channel="authorizePaymentChannel"/>
    <int:mapping value="DEBIT" channel="authorizePaymentChannel"/>
</int:router>

次の例は、Java で構成された同等のルーターを示しています。

@Router(inputChannel = "routingChannel")
@Bean
public ExpressionEvaluatingRouter router() {
    ExpressionEvaluatingRouter router = new ExpressionEvaluatingRouter("payload.paymentType");
    router.setChannelMapping("CASH", "cashPaymentChannel");
    router.setChannelMapping("CREDIT", "authorizePaymentChannel");
    router.setChannelMapping("DEBIT", "authorizePaymentChannel");
    return router;
}

次の例は、Java DSL で構成された同等のルーターを示しています。

@Bean
public IntegrationFlow routerFlow() {
    return IntegrationFlows.from("routingChannel")
        .route("payload.paymentType", r -> r
            .channelMapping("CASH", "cashPaymentChannel")
            .channelMapping("CREDIT", "authorizePaymentChannel")
            .channelMapping("DEBIT", "authorizePaymentChannel"))
        .get();
}

さらに簡素化するために、次の式が示すように、SpEL 式はチャネル名に評価される場合があります。

<int:router input-channel="inChannel" expression="payload + 'Channel'"/>

上記の構成では、結果チャネルは SpEL 式によって計算され、payload の値をリテラル String の "Channel" と連結します。

ルーターを構成するための SpEL のもう 1 つの長所は、式が Collection を返すことができ、事実上すべての <router> を受信者リストルーターにすることです。式が複数のチャネル値を返すたびに、メッセージは各チャネルに転送されます。次の例は、そのような式を示しています。

<int:router input-channel="inChannel" expression="headers.channels"/>

上記の構成で、メッセージに "channels" という名前のヘッダーが含まれ、そのヘッダーの値がチャネル名の List である場合、メッセージはリスト内の各チャネルに送信されます。また、複数のチャネルを選択する必要がある場合に、コレクションの射影式とコレクションの選択式が役立つ場合があります。詳細については、以下を参照してください。

アノテーション付きのルーターの構成

@Router を使用してメソッドにアノテーションを付ける場合、メソッドは MessageChannel または String 型のいずれかを返す場合があります。後者の場合、エンドポイントはデフォルトの出力チャネルの場合と同様にチャネル名を解決します。さらに、メソッドは単一の値またはコレクションを返す場合があります。コレクションが返される場合、応答メッセージは複数のチャネルに送信されます。要約すると、次のメソッドシグネチャーはすべて有効です。

@Router
public MessageChannel route(Message message) {...}

@Router
public List<MessageChannel> route(Message message) {...}

@Router
public String route(Foo payload) {...}

@Router
public List<String> route(Foo payload) {...}

ペイロードベースのルーティングに加えて、メッセージヘッダー内でプロパティまたは属性として使用可能なメタデータに基づいてメッセージをルーティングできます。この場合、@Router アノテーションが付けられたメソッドには、@Header アノテーションが付けられたパラメーターが含まれる場合があります。これは、次の例に示すようにヘッダー値にマッピングされ、アノテーションサポートにドキュメント化されます。

@Router
public List<String> route(@Header("orderStatus") OrderStatus status)
XPath サポートを含む XML ベースのメッセージのルーティングについては、XML サポート - XML ペイロードの処理を参照してください。

ルーター構成の詳細については、Java DSL の章のメッセージルーターも参照してください。

動的ルーター

Spring Integration は、一般的なコンテンツベースのルーティングのユースケースに対応する非常に少数の異なるルーター構成と、カスタムルーターを POJO として実装するオプションを提供します。例: PayloadTypeRouter は、受信メッセージのペイロード型に基づいてチャネルを計算するルーターを構成する簡単な方法を提供しますが、HeaderValueRouter は、特定のメッセージヘッダーの値を評価してチャネルを計算するルーターを構成するのと同じ便利な機能を提供します。式ベース(SpEL)のルーターもあり、式の評価に基づいてチャネルが決定されます。これらの型のルーターはすべて、いくつかの動的特性を示します。

ただし、これらのルーターはすべて静的構成が必要です。式ベースのルーターの場合でも、式自体はルーター構成の一部として定義されます。つまり、同じ値で動作する同じ式は常に同じチャネルの計算になります。このようなルートは明確に定義されており、予測可能であるため、ほとんどの場合、これは受け入れられます。ただし、メッセージフローが別のチャネルにルーティングされるように、ルーターの構成を動的に変更する必要がある場合があります。

例: メンテナンスのためにシステムの一部を停止し、一時的にメッセージを別のメッセージフローに再ルーティングしたい場合があります。別の例として、より具象型の java.lang.Number (PayloadTypeRouter の場合)を処理する別のルートを追加することにより、メッセージフローをより細かくしたい場合があります。

残念ながら、これらのゴールのいずれかを達成するための静的ルーター構成では、アプリケーション全体を停止し、ルーターの構成を変更(ルートを変更)し、アプリケーションを再起動する必要があります。これは明らかに誰もが望む解決策ではありません。

動的ルーター (英語) パターンは、システムまたは個々のルーターを停止することなく、ルーターを動的に変更または構成できるメカニズムを記述します。

Spring Integration が動的ルーティングをサポートする方法の詳細に入る前に、ルーターの典型的なフローを考慮する必要があります。

  1. チャネル識別子を計算します。これは、ルーターがメッセージを受信すると計算される値です。通常、これは String または実際の MessageChannel のインスタンスです。

  2. チャネル識別子をチャネル名に解決します。このセクションの後半で、このプロセスの詳細を説明します。

  3. チャネル名を実際の MessageChannel に解決します

MessageChannel はルーターのジョブの最終製品であるため、ステップ 1 の結果が MessageChannel の実際のインスタンスになる場合、動的ルーティングに関してできることはあまりありません。ただし、最初のステップの結果が MessageChannel のインスタンスではないチャネル ID になった場合、MessageChannel を導出するプロセスに影響を与える方法はかなりあります。ペイロード型のルーターの次の例を考えてみましょう。

<int:payload-type-router input-channel="routingChannel">
    <int:mapping type="java.lang.String"  channel="channel1" />
    <int:mapping type="java.lang.Integer" channel="channel2" />
</int:payload-type-router>

ペイロード型のルーターのコンテキスト内で、前述の 3 つの手順は次のように実現されます。

  1. ペイロード型の完全修飾名(たとえば、java.lang.String)であるチャネル識別子を計算します。

  2. チャネル識別子をチャネル名に解決します。この場合、前の手順の結果を使用して、mapping 要素で定義されたペイロード型マッピングから適切な値を選択します。

  3. 前のステップの結果で識別されたアプリケーションコンテキスト(できれば MessageChannel)内の Bean への参照として、チャネル名を MessageChannel の実際のインスタンスに解決します。

つまり、プロセスが完了するまで、各ステップは次のステップにフィードします。

次に、ヘッダー値ルーターの例を考えます。

<int:header-value-router input-channel="inputChannel" header-name="testHeader">
    <int:mapping value="foo" channel="fooChannel" />
    <int:mapping value="bar" channel="barChannel" />
</int:header-value-router>

これで、ヘッダー値ルーターの 3 つのステップがどのように機能するかを検討できます。

  1. header-name 属性によって識別されるヘッダーの値であるチャネル識別子を計算します。

  2. チャネル識別子 a をチャネル名に解決します。ここで、前の手順の結果を使用して、mapping 要素で定義された一般的なマッピングから適切な値を選択します。

  3. 前のステップの結果で識別されたアプリケーションコンテキスト(できれば MessageChannel)内の Bean への参照として、チャネル名を MessageChannel の実際のインスタンスに解決します。

2 つの異なるルーター型の前述の 2 つの構成は、ほとんど同じように見えます。ただし、HeaderValueRouter の代替構成を見ると、次のように、mapping サブ要素がないことが明確にわかります。

<int:header-value-router input-channel="inputChannel" header-name="testHeader">

ただし、構成はまだ完全に有効です。自然な問題は、第 2 ステップのマッピングについてはどうでしょうか?

2 番目のステップはオプションになりました。mapping が定義されていない場合、最初のステップで計算されたチャネル識別子の値は自動的に channel name として扱われ、現在は 3 番目のステップのように実際の MessageChannel に解決されます。また、2 番目のステップは、チャネル識別子がチャネル名に解決する方法を変更できるプロセスを導入し、最終的な決定プロセスに影響を与えるため、ルーターに動的特性を提供するための重要なステップの 1 つであることも意味します。初期チャネル識別子からの MessageChannel のインスタンス。

例: 上記の構成では、testHeader 値が "kermit" であり、現在はチャネル識別子であると想定しています(最初のステップ)。このルーターにはマッピングがないため、このチャネル識別子をチャネル名に解決することはできず(2 番目のステップ)、このチャネル識別子はチャネル名として扱われます。ただし、マッピングがあり、値が異なる場合はどうなるでしょうか? チャネル識別子をチャネル名に解決するプロセスで新しい値を決定できない場合、チャネル識別子がチャネル名になるため、最終結果は同じままです。

残っているのは、3 番目のステップでチャネル名(「カーミット」)を、この名前で識別される MessageChannel の実際のインスタンスに解決することだけです。基本的には、提供された名前の Bean ルックアップが含まれます。これで、testHeader=kermit としてヘッダーと値のペアを含むすべてのメッセージは、Bean 名(その id)が "kermit" である MessageChannel にルーティングされます。

しかし、これらのメッセージを「シンプソン」チャンネルにルーティングしたい場合はどうでしょうか? 明らかに静的構成の変更は機能しますが、変更するにはシステムを停止する必要があります。ただし、チャネル識別子マップにアクセスできる場合、ヘッダーと値のペアが kermit=simpson になった新しいマッピングを導入できます。2 番目のステップで "kermit" をチャネル識別子として扱い、"simpson" をチャンネル名。

PayloadTypeRouter にも同じことが当てはまります。特定のペイロード型のマッピングを再マップまたは削除できるようになりました。実際、計算された値は 2 番目のステップを経て実際の channel name に解決される可能性があるため、式ベースのルーターを含む他のすべてのルーターに適用されます。

AbstractMappingMessageRouter のサブクラスであるルーター (フレームワーク定義のルーターのほとんどを含む) は動的ルーターです。これは、channelMapping が AbstractMappingMessageRouter レベルで定義されているためです。そのマップの setter メソッドは、'setChannelMapping' および 'removeChannelMapping' メソッドとともにパブリックメソッドとして公開されています。これらを使用すると、ルーター自体への参照がある限り、実行時にルーターマッピングを変更、追加、削除できます。また、JMX (JMX サポートを参照) または Spring Integration コントロールバス ( 制御バスを参照) 機能を通じて、同じ構成オプションを公開することもできます。

チャンネル名は柔軟で便利なので、チャンネルキーに戻ります。ただし、メッセージ作成者を信頼していない場合、悪意のあるアクター(システムの知識がある)が予期しないチャネルにルーティングされるメッセージを作成する可能性があります。例: キーがルーターの入力チャンネルのチャンネル名に設定されている場合、そのようなメッセージはルーターにルーティングされ、最終的にスタックオーバーフローエラーが発生します。この機能を無効にし(channelKeyFallback プロパティを false に設定)、必要に応じてマッピングを変更することをお勧めします。
コントロールバスを使用してルーターマッピングを管理する

ルーターマッピングを管理する 1 つの方法は、コントロールバス (英語) パターンを使用することです。これは、ルーターを含む Spring Integration コンポーネントを管理および監視するためのコントロールメッセージを送信できるコントロールチャネルを公開します。

制御バスの詳細については、制御バスを参照してください。

通常、特定の管理対象コンポーネント(ルーターなど)で特定の操作を呼び出すように要求する制御メッセージを送信します。次の管理操作(メソッド)は、ルーター解決プロセスの変更に固有のものです。

  • public void setChannelMapping(String key, String channelName)channel identifier と channel name の間に新しいマッピングを追加したり、既存のマッピングを変更したりできます。

  • public void removeChannelMapping(String key): 特定のチャネルマッピングを削除して、channel identifier と channel name の関連を切断できます。

これらのメソッドは、単純な変更(単一のルートの更新、ルートの追加または削除など)に使用できることに注意してください。ただし、あるルートを削除して別のルートを追加する場合、更新はアトミックではありません。これは、ルーティングテーブルが更新間で不確定な状態になる可能性があることを意味します。バージョン 4.0 以降、コントロールバスを使用して、ルーティングテーブル全体をアトミックに更新できるようになりました。次のメソッドを使用して、これを行うことができます。

  • public Map<String, String>getChannelMappings(): 現在のマッピングを返します。

  • public void replaceChannelMappings(Properties channelMappings): マッピングを更新します。channelMappings パラメーターは Properties オブジェクトであることに注意してください。この配置により、次の例に示すように、コントロールバスコマンドで組み込みの StringToPropertiesConverter を使用できます。

"@'router.handler'.replaceChannelMappings('foo=qux \n baz=bar')"

各マッピングは改行文字(\n)で区切られていることに注意してください。マップをプログラムで変更する場合は、型の安全性が懸念されるため、setChannelMappings メソッドを使用することをお勧めします。replaceChannelMappings は、String オブジェクトではないキーまたは値を無視します。

JMX を使用してルーターマッピングを管理する

Spring の JMX サポートを使用してルーターインスタンスを公開し、お気に入りの JMX クライアント(たとえば、JConsole)を使用して、ルーターの構成を変更するための操作(メソッド)を管理することもできます。

Spring Integration の JMX サポートの詳細については、JMX サポートを参照してください。
ルーティングスリップ

バージョン 4.1 から、Spring Integration はルーティングスリップ (英語) エンタープライズ統合パターンの実装を提供します。outputChannel がエンドポイントに指定されていない場合、routingSlip メッセージヘッダーとして実装され、AbstractMessageProducingHandler インスタンスの次のチャネルを決定するために使用されます。このパターンは、メッセージフローを決定するために複数のルーターを構成することが困難になる複雑で動的な場合に役立ちます。output-channel を持たないエンドポイントにメッセージが到着すると、routingSlip が調べられて、メッセージが送信される次のチャネルが決定されます。ルーティングスリップがなくなると、通常の replyChannel 処理が再開されます。

ルーティングスリップの構成は、HeaderEnricher オプションとして提示されます。これは、次の例に示すように、path エントリを含むセミコロンで区切られたルーティングスリップです。

<util:properties id="properties">
    <beans:prop key="myRoutePath1">channel1</beans:prop>
    <beans:prop key="myRoutePath2">request.headers[myRoutingSlipChannel]</beans:prop>
</util:properties>

<context:property-placeholder properties-ref="properties"/>

<header-enricher input-channel="input" output-channel="process">
    <routing-slip
        value="${myRoutePath1}; @routingSlipRoutingPojo.get(request, reply);
               routingSlipRoutingStrategy; ${myRoutePath2}; finishChannel"/>
</header-enricher>

上記の例には次のものがあります。

  • ルーティングスリップ path のエントリを解決可能なキーとして指定できることを示すための <context:property-placeholder> 構成。

  • <header-enricher> <routing-slip> サブエレメントは、RoutingSlipHeaderValueMessageProcessor を HeaderEnricher ハンドラーに移入するために使用されます。

  • RoutingSlipHeaderValueMessageProcessor は、解決されたルーティングスリップ path エントリの String 配列を受け入れ、key として path を、routingSlipIndex として 0 を含む singletonMap を(processMessage() から)返します。

ルーティングスリップ path エントリには、MessageChannel Bean 名、RoutingSlipRouteStrategy Bean 名、および Spring 式(SpEL)を含めることができます。RoutingSlipHeaderValueMessageProcessor は、最初の processMessage 呼び出しで、各ルーティングスリップ path エントリを BeanFactory に対してチェックします。エントリ(アプリケーションコンテキストの Bean 名ではない)を ExpressionEvaluatingRoutingSlipRouteStrategy インスタンスに変換します。RoutingSlipRouteStrategy エントリは、null または空の String を返すまで複数回呼び出されます。

ルーティングスリップは getOutputChannel プロセスに関係しているため、リクエスト / 応答コンテキストがあります。RoutingSlipRouteStrategy は、requestMessage および reply オブジェクトを使用する次の outputChannel を決定するために導入されました。この戦略の実装は、アプリケーションコンテキストで Bean として登録する必要があり、その Bean 名はルーティングスリップ path で使用されます。ExpressionEvaluatingRoutingSlipRouteStrategy 実装が提供されます。SpEL 式を受け入れ、内部 ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply オブジェクトが評価コンテキストのルートオブジェクトとして使用されます。これは、各 ExpressionEvaluatingRoutingSlipRouteStrategy.getNextPath() 呼び出しの EvaluationContext 作成のオーバーヘッドを回避するためです。これは、Message<?> request と Object reply の 2 つのプロパティを持つ単純な Java Bean です。この式の実装では、SpEL(たとえば @routingSlipRoutingPojo.get(request, reply) および request.headers[myRoutingSlipChannel])を使用してルーティングスリップ path エントリを指定し、RoutingSlipRouteStrategy に Bean を定義しないようにすることができます。

requestMessage 引数は常に Message<?> です。コンテキストに応じて、応答オブジェクトは Message<?>AbstractIntegrationMessageBuilder、任意のアプリケーションドメインオブジェクト(たとえば、サービスアクティベータによって呼び出された POJO メソッドによって返される場合)です。最初の 2 つのケースでは、SpEL(または Java 実装)を使用すると、通常の Message プロパティ(payload および headers)が利用可能です。任意のドメインオブジェクトの場合、これらのプロパティは使用できません。このため、結果が次のパスの決定に使用される場合、POJO メソッドと一緒に回覧先を使用する場合は注意してください。
ルーティングスリップが分散環境に関係している場合、ルーティングスリップ path にインライン式を使用しないことをお勧めします。この推奨事項は、クロス JVM アプリケーションなどの分散環境、メッセージブローカー(AMQP サポートまたは JMS サポートなど)を介した request-reply の使用、または統合フローでの永続的な MessageStore (メッセージストア)の使用に適用されます。フレームワークは RoutingSlipHeaderValueMessageProcessor を使用して ExpressionEvaluatingRoutingSlipRouteStrategy オブジェクトに変換し、routingSlip メッセージヘッダーで使用されます。このクラスは Serializable ではないため(BeanFactory に依存するため、そうではありません)、Message 全体がシリアライズ不可能になり、分散操作では NotSerializableException になります。この制限を克服するには、ExpressionEvaluatingRoutingSlipRouteStrategy Bean を希望の SpEL に登録し、ルーティングスリップ path 構成でその Bean 名を使用します。

Java 構成の場合、次の例に示すように、RoutingSlipHeaderValueMessageProcessor インスタンスを HeaderEnricher Bean 定義に追加できます。

@Bean
@Transformer(inputChannel = "routingSlipHeaderChannel")
public HeaderEnricher headerEnricher() {
    return new HeaderEnricher(Collections.singletonMap(IntegrationMessageHeaderAccessor.ROUTING_SLIP,
            new RoutingSlipHeaderValueMessageProcessor("myRoutePath1",
                                                       "@routingSlipRoutingPojo.get(request, reply)",
                                                       "routingSlipRoutingStrategy",
                                                       "request.headers[myRoutingSlipChannel]",
                                                       "finishChannel")));
}

エンドポイントが応答を生成し、outputChannel が定義されていない場合、ルーティングスリップアルゴリズムは次のように機能します。

  • routingSlipIndex は、ルーティングスリップ path リストから値を取得するために使用されます。

  • routingSlipIndex の値が String の場合、BeanFactory から Bean を取得するために使用されます。

  • 返された Bean が MessageChannel のインスタンスである場合、次の outputChannel として使用され、routingSlipIndex は応答メッセージヘッダーでインクリメントされます(ルーティングスリップ path エントリは変更されません)。

  • 返された Bean が RoutingSlipRouteStrategy のインスタンスであり、その getNextPath が空の String を返さない場合、その結果は次の outputChannel の Bean 名として使用されます。routingSlipIndex は変更されません。

  • RoutingSlipRouteStrategy.getNextPath が空の String または null を返す場合、routingSlipIndex はインクリメントされ、getOutputChannelFromRoutingSlip は次のルーティングスリップ path アイテムに対して再帰的に呼び出されます。

  • 次のルーティングスリップ path エントリが String でない場合、RoutingSlipRouteStrategy のインスタンスでなければなりません。

  • routingSlipIndex がルーティングスリップ path リストのサイズを超えると、アルゴリズムは標準 replyChannel ヘッダーのデフォルトの動作に移行します。

Process Manager エンタープライズ統合パターン

エンタープライズ統合パターンには、プロセスマネージャー (英語) パターンが含まれます。ルーティングスリップ内の RoutingSlipRouteStrategy にカプセル化されたカスタムプロセスマネージャーロジックを使用して、このパターンを簡単に実装できるようになりました。Bean 名に加えて、RoutingSlipRouteStrategy は任意の MessageChannel オブジェクトを返すことができ、この MessageChannel インスタンスがアプリケーションコンテキストの Bean である必要はありません。この方法により、使用するチャネルを予測する方法がない場合に、強力な動的ルーティングロジックを提供できます。RoutingSlipRouteStrategy 内で MessageChannel を作成して返すことができます。MessageHandler 実装が関連付けられた FixedSubscriberChannel は、このような場合に適した組み合わせです。例: 次の例に示すように、Reactive Streams (英語) にルーティングできます。

@Bean
public PollableChannel resultsChannel() {
    return new QueueChannel();
}
@Bean
public RoutingSlipRouteStrategy routeStrategy() {
    return (requestMessage, reply) -> requestMessage.getPayload() instanceof String
            ? new FixedSubscriberChannel(m ->
            Mono.just((String) m.getPayload())
                    .map(String::toUpperCase)
                    .subscribe(v -> messagingTemplate().convertAndSend(resultsChannel(), v)))
            : new FixedSubscriberChannel(m ->
            Mono.just((Integer) m.getPayload())
                    .map(v -> v * 2)
                    .subscribe(v -> messagingTemplate().convertAndSend(resultsChannel(), v)));
}

フィルター

メッセージフィルターは、メッセージヘッダー値やメッセージコンテンツ自体などの条件に基づいて、Message を渡すかドロップするかを決定するために使用されます。メッセージフィルターはルーターに似ていますが、フィルターの入力チャネルから受信した各メッセージについて、同じメッセージがフィルターの出力チャネルに送信される場合と送信されない場合があります。ルーターとは異なり、メッセージを送信するメッセージチャネルに関する決定は行いませんが、メッセージを送信するかどうかのみを決定します。

このセクションの後半で説明するように、フィルターは破棄チャネルもサポートします。場合によっては、ブール条件に基づいて、非常に単純なルーター(または「スイッチ」)のロールを果たすことができます。

Spring Integration では、MessageSelector インターフェースの実装に委譲するメッセージエンドポイントとしてメッセージフィルターを構成できます。次のように、このインターフェース自体は非常にシンプルです。

public interface MessageSelector {

    boolean accept(Message<?> message);

}

MessageFilter コンストラクターは、次の例に示すように、セレクターインスタンスを受け入れます。

MessageFilter filter = new MessageFilter(someSelector);

名前空間と SpEL を組み合わせて、ごくわずかな Java コードで強力なフィルターを構成できます。

XML を使用したフィルターの構成

<filter> 要素を使用して、メッセージ選択エンドポイントを作成できます。input-channel および output-channel 属性に加えて、ref 属性が必要です。次の例に示すように、ref は MessageSelector 実装を指すことができます。

<int:filter input-channel="input" ref="selector" output-channel="output"/>

<bean id="selector" class="example.MessageSelectorImpl"/>

または、method 属性を追加できます。その場合、ref 属性は任意のオブジェクトを参照できます。参照されるメソッドは、受信メッセージの Message 型またはペイロード型のいずれかを予期する場合があります。メソッドはブール値を返す必要があります。メソッドが "true" を返す場合、メッセージは出力チャネルに送信されます。次の例は、method 属性を使用するフィルターを構成する方法を示しています。

<int:filter input-channel="input" output-channel="output"
    ref="exampleObject" method="someBooleanReturningMethod"/>

<bean id="exampleObject" class="example.SomeObject"/>

セレクターまたは適合された POJO メソッドが false を返す場合、拒否されたメッセージの処理を制御する設定がいくつかあります。デフォルトでは(前の例のように構成されている場合)、拒否されたメッセージは静かにドロップされます。拒否によりエラー状態が発生する場合は、次の例に示すように、throw-exception-on-rejection 属性を true に設定します。

<int:filter input-channel="input" ref="selector"
    output-channel="output" throw-exception-on-rejection="true"/>

拒否されたメッセージを特定のチャネルにルーティングする場合は、次の例に示すように、その参照を discard-channel として提供します。

<int:filter input-channel="input" ref="selector"
    output-channel="output" discard-channel="rejectedMessages"/>

アドバイスフィルターも参照してください。

メッセージフィルターは、一般にパブリッシュ / サブスクライブチャネルと組み合わせて使用されます。多くのフィルターエンドポイントは同じチャネルにサブスクライブでき、サポートされている型(サービスアクティベーターなど)のいずれかである可能性がある次のエンドポイントにメッセージを渡すかどうかを決定します。これは、単一のポイントツーポイント入力チャネルと複数の出力チャネルを備えたメッセージルーターを使用する、より積極的なアプローチのリアクティブな代替手段を提供します。

カスタムフィルターの実装が他の <filter> 定義で参照されている場合は、ref 属性を使用することをお勧めします。ただし、カスタムフィルターの実装が単一の <filter> 要素にスコープされている場合は、次の例に示すように、内部 Bean 定義を提供する必要があります。

<int:filter method="someMethod" input-channel="inChannel" output-channel="outChannel">
  <beans:bean class="org.foo.MyCustomFilter"/>
</filter>
同じ <filter> 構成で ref 属性と内部ハンドラー定義の両方を使用することは、あいまいな条件を作成して例外をスローするため、許可されません。
ref 属性が MessageFilter を継承する Bean を参照する場合(フレームワーク自体によって提供されるフィルターなど)、構成は、出力チャネルをフィルター Bean に直接注入することにより最適化されます。この場合、各 ref は個別の Bean インスタンス(または prototype -scoped Bean)に属しているか、内部 <bean/> 構成型を使用する必要があります。ただし、この最適化は、フィルター XML 定義にフィルター固有の属性を指定しない場合にのみ適用されます。誤って複数の Bean から同じメッセージハンドラーを参照すると、構成例外が発生します。

SpEL サポートの導入により、Spring Integration は expression 属性をフィルター要素に追加しました。次の例に示すように、単純なフィルターで Java を完全に回避するために使用できます。

<int:filter input-channel="input" expression="payload.equals('nonsense')"/>

expression 属性の値として渡された文字列は、評価コンテキストで利用可能なメッセージとともに SpEL 式として評価されます。アプリケーションコンテキストのスコープに式の結果を含める必要がある場合は、次の例に示すように、SpEL リファレンスドキュメントで定義されている #{} 表記を使用できます。

<int:filter input-channel="input"
            expression="payload.matches(#{filterPatterns.nonsensePattern})"/>

式自体を動的にする必要がある場合は、「式」サブ要素を使用できます。それは、ExpressionSource からのキーによって式を解決するための間接性のレベルを提供します。これは、直接実装できる戦略インターフェースです。または、「リソースバンドル」から式をロードし、指定された秒数後に変更を確認できる Spring Integration で利用可能なバージョンに依存できます。このすべては、基になるファイルが変更された場合に 1 分以内に式をリロードできる次の構成例で示されています。

<int:filter input-channel="input" output-channel="output">
    <int:expression key="filterPatterns.example" source="myExpressions"/>
</int:filter>

<beans:bean id="myExpressions" id="myExpressions"
    class="o.s.i.expression.ReloadableResourceBundleExpressionSource">
    <beans:property name="basename" value="config/integration/expressions"/>
    <beans:property name="cacheSeconds" value="60"/>
</beans:bean>

ExpressionSource Bean の名前が expressionSource の場合、<expression> 要素に `source` 属性を指定する必要はありません。ただし、前の例では、完全を期すために示しています。

次の例に示すように、"config/integration/expressions.properties" ファイル(またはリソースバンドルがロードされる一般的な方法で解決されるロケール拡張機能を備えたより具体的なバージョン)には、キー / 値のペアを含めることができます。

filterPatterns.example=payload > 100
属性またはサブ要素として expression を使用するこれらの例はすべて、トランスフォーマー、ルーター、スプリッター、サービスアクティベーター、ヘッダーエンリッチャー要素内でも適用できます。メソッド呼び出しの戻り値が解釈されるのと同じように、与えられたコンポーネント型のセマンティクスとロールは評価結果の解釈に影響を与えます。例: 式は、ルーターコンポーネントによってメッセージチャネル名として扱われる文字列を返すことができます。ただし、メッセージに対してルートオブジェクトとして式を評価し、接頭辞 "@" が付いている場合に Bean 名を解決する基本機能は、Spring Integration 内のすべてのコア EIP コンポーネントで一貫しています。

アノテーション付きのフィルターの構成

次の例は、アノテーションを使用してフィルターを構成する方法を示しています。

public class PetFilter {
    ...
    @Filter  (1)
    public boolean dogsOnly(String input) {
        ...
    }
}
1 このメソッドがフィルターとして使用されることを示すアノテーション。このクラスをフィルターとして使用する場合は、指定する必要があります。

XML 要素によって提供されるすべての構成オプションは、@Filter アノテーションでも使用できます。

フィルターは、XML から明示的に参照するか、クラスで @MessageEndpoint アノテーションが定義されている場合、クラスパススキャンにより自動的に検出されます。

スプリッター

スプリッターは、メッセージをいくつかの部分に分割し、結果のメッセージを送信して個別に処理するロールを持つコンポーネントです。非常に多くの場合、アグリゲーターを含むパイプラインの上流のプロデューサーです。

プログラミングモデル

分割を実行するための API は、1 つの基本クラス AbstractMessageSplitter で構成されています。これは、生成されるメッセージに適切なメッセージヘッダー(CORRELATION_IDSEQUENCE_SIZESEQUENCE_NUMBER)を入力するなど、スプリッターに共通の機能をカプセル化する MessageHandler 実装です。この入力により、メッセージとその処理結果を追跡できます(通常のシナリオでは、これらのヘッダーは、さまざまな変換エンドポイントによって生成されるメッセージにコピーされます)。値は、たとえば、構成されたメッセージプロセッサー (英語) によって使用できます。

次の例は、AbstractMessageSplitter からの抜粋を示しています。

public abstract class AbstractMessageSplitter
    extends AbstractReplyProducingMessageConsumer {
    ...
    protected abstract Object splitMessage(Message<?> message);

}

アプリケーションに特定のスプリッターを実装するには、AbstractMessageSplitter を継承し、メッセージを分割するためのロジックを含む splitMessage メソッドを実装します。戻り値は次のいずれかです。

  • Collection またはメッセージの配列、あるいはメッセージを反復処理する Iterable (または Iterator)。この場合、メッセージはメッセージとして送信されます(CORRELATION_IDSEQUENCE_SIZESEQUENCE_NUMBER が入力された後)。このアプローチを使用すると、より詳細な制御が可能になります。たとえば、分割プロセスの一部としてカスタムメッセージヘッダーにデータを入力できます。

  • Collection または非メッセージオブジェクトの配列、または非メッセージオブジェクトを反復処理する Iterable (または Iterator)。各コレクション要素がメッセージペイロードとして使用されることを除いて、前のケースと同様に機能します。このアプローチを使用すると、メッセージングシステムを考慮することなくドメインオブジェクトに集中でき、テストしやすいコードを生成できます。

  • Message または非メッセージオブジェクト(ただし、コレクションまたは配列ではありません)。単一のメッセージが送信されることを除いて、前のケースと同様に機能します。

Spring Integration では、単一の引数を受け入れ、戻り値を持つメソッドを定義していれば、どの POJO も分割アルゴリズムを実装できます。この場合、メソッドの戻り値は前述のように解釈されます。入力引数は、Message または単純な POJO のいずれかです。後者の場合、スプリッターは受信メッセージのペイロードを受信します。この方法は、Spring Integration API からコードを切り離し、通常はテストが簡単なので、この方法をお勧めします。

イテレータ

バージョン 4.1 から、AbstractMessageSplitter は value が分割する Iterator 型をサポートします。Iterator (または Iterable)の場合、基になるアイテムの数にアクセスできず、SEQUENCE_SIZE ヘッダーが 0 に設定されていることに注意してください。つまり、<aggregator> のデフォルト SequenceSizeReleaseStrategy は機能せず、splitter から CORRELATION_ID のグループは解放されません。incomplete のままになります。この場合、適切なカスタム ReleaseStrategy を使用するか、group-timeout または MessageGroupStoreReaper とともに send-partial-result-on-expiry に依存する必要があります。

バージョン 5.0 から、AbstractMessageSplitter は protected obtainSizeIfPossible() メソッドを提供し、可能であれば Iterable および Iterator オブジェクトのサイズを決定できます。たとえば、XPathMessageSplitter は、基になる NodeList オブジェクトのサイズを判別できます。また、バージョン 5.0.9 以降、このメソッドは com.fasterxml.jackson.core.TreeNode のサイズも適切に返します。

Iterator オブジェクトは、分割する前にメモリ内にコレクション全体を構築する必要を回避できます。例: 基礎となるアイテムが、反復またはストリームを使用して外部システム(例: DataBase または FTP MGET)から取り込まれた場合。

ストリームと Flux

バージョン 5.0 以降、AbstractMessageSplitter は、value が分割する Java Stream および Reactive Streams Publisher 型をサポートします。この場合、ターゲット Iterator は反復機能に基づいて構築されます。

さらに、スプリッターの出力チャンネルが ReactiveStreamsSubscribableChannel のインスタンスである場合、AbstractMessageSplitter は Iterator の代わりに Flux の結果を生成し、出力チャンネルは、ダウンストリームフローデマンドでのバックプレッシャーベースの分割のためにこの Flux にサブスクライブされます。

バージョン 5.2 以降、スプリッターは、スプリット関数が空のコンテナー(コレクション、配列、ストリーム、Flux など)を返したリクエストメッセージを送信するための discardChannel オプションをサポートします。この場合、outputChannel に送信するために反復する項目はありません。null の分割結果は、フロー終了インジケータとして残ります。

XML を使用したスプリッターの構成

スプリッターは、次のように XML で構成できます。

<int:channel id="inputChannel"/>

<int:splitter id="splitter"           (1)
  ref="splitterBean"                  (2)
  method="split"                      (3)
  input-channel="inputChannel"        (4)
  output-channel="outputChannel"      (5)
  discard-channel="discardChannel" /> (6)

<int:channel id="outputChannel"/>

<beans:bean id="splitterBean" class="sample.PojoSplitter"/>
1 スプリッターの ID はオプションです。
2 アプリケーションコンテキストで定義された Bean への参照。Bean は、前のセクションで説明したように、分割ロジックを実装する必要があります。オプション。Bean への参照が提供されない場合、input-channel に到着したメッセージのペイロードは java.util.Collection の実装であり、デフォルトの分割ロジックがコレクションに適用され、個々の要素がメッセージに組み込まれて送信されると想定されます。output-channel へ。
3 分割ロジックを実装するメソッド(Bean で定義)。オプション。
4 スプリッターの入力チャンネル。必須。
5 スプリッターが受信メッセージを分割した結果を送信するチャネル。オプション(受信メッセージ自体が応答チャネルを指定できるため)。
6 分割結果が空の場合にリクエストメッセージが送信されるチャネル。オプション(null 結果の場合のように停止します)。

カスタムスプリッター実装を他の <splitter> 定義で参照できる場合は、ref 属性を使用することをお勧めします。ただし、カスタムスプリッターハンドラーの実装を <splitter> の単一の定義にスコープする必要がある場合、次の例のように、内部 Bean 定義を構成できます。

<int:splitter id="testSplitter" input-channel="inChannel" method="split"
                output-channel="outChannel">
  <beans:bean class="org.foo.TestSplitter"/>
</int:splitter>
同じ <int:splitter> 構成で ref 属性と内部ハンドラー定義の両方を使用することは許可されません。曖昧な状態を作成し、例外がスローされるためです。
ref 属性が AbstractMessageProducingHandler を継承する Bean を参照する場合(フレームワーク自体が提供するスプリッターなど)、構成は、出力チャネルをハンドラーに直接注入することにより最適化されます。この場合、各 ref は個別の Bean インスタンス(または prototype -scoped Bean)であるか、内部 <bean/> 構成型を使用する必要があります。ただし、この最適化は、スプリッター XML 定義でスプリッター固有の属性を指定しない場合にのみ適用されます。誤って複数の Bean から同じメッセージハンドラーを参照すると、構成例外が発生します。

アノテーション付きのスプリッターの構成

@Splitter アノテーションは、Message 型またはメッセージペイロード型のいずれかを期待するメソッドに適用可能であり、メソッドの戻り値は任意の型の Collection でなければなりません。返される値が実際の Message オブジェクトではない場合、各アイテムは Message のペイロードとして Message にラップされます。結果の各 Message は、@Splitter が定義されているエンドポイントの指定された出力チャネルに送信されます。

次の例は、@Splitter アノテーションを使用してスプリッターを構成する方法を示しています。

@Splitter
List<LineItem> extractItems(Order order) {
    return order.getItems()
}

Java DSL の章のスプリッターも参照してください。

アグリゲーター

基本的にスプリッターの鏡像であるアグリゲーターは、複数のメッセージを受け取り、単一のメッセージに結合するメッセージハンドラーの一種です。実際、アグリゲーターは多くの場合、スプリッターを含むパイプラインのダウンストリームコンシューマーです。

技術的には、アグリゲーターはステートフルであるため、スプリッターよりも複雑です。集約するメッセージを保持し、メッセージの完全なグループをいつ集約する準備ができたかを判断する必要があります。そのためには、MessageStore が必要です。

機能性

アグリゲーターは、グループが完了したと見なされるまで、関連するメッセージのグループを関連付けて保管することにより、グループを結合します。その時点で、アグリゲーターはグループ全体を処理して単一のメッセージを作成し、集約されたメッセージを出力として送信します。

アグリゲーターを実装するには、集約を実行するロジックを提供する必要があります(つまり、多数からの単一メッセージの作成)。関連する 2 つの概念は、相関とリリースです。

相関により、メッセージを集約用にグループ化する方法が決まります。Spring Integration では、IntegrationMessageHeaderAccessor.CORRELATION_ID メッセージヘッダーに基づいて、デフォルトで相関が行われます。同じ IntegrationMessageHeaderAccessor.CORRELATION_ID を持つメッセージはグループ化されます。ただし、相関ストラテジをカスタマイズして、メッセージをグループ化する方法を指定する他の方法を許可できます。そのためには、CorrelationStrategy (この章で後述)を実装できます。

メッセージのグループを処理する準備が整った時点を判断するために、ReleaseStrategy が参照されます。アグリゲーターのデフォルトのリリース戦略は、IntegrationMessageHeaderAccessor.SEQUENCE_SIZE ヘッダーに基づいて、シーケンスに含まれるすべてのメッセージが存在するときにグループをリリースします。カスタム ReleaseStrategy 実装への参照を提供することにより、このデフォルト戦略をオーバーライドできます。

プログラミングモデル

Aggregation API は、いくつかのクラスで構成されています。

  • インターフェース MessageGroupProcessor およびそのサブクラス: MethodInvokingAggregatingMessageGroupProcessor および ExpressionEvaluatingMessageGroupProcessor

  • ReleaseStrategy インターフェースとそのデフォルト実装: SimpleSequenceSizeReleaseStrategy

  • CorrelationStrategy インターフェースとそのデフォルト実装: HeaderAttributeCorrelationStrategy

AggregatingMessageHandler

AggregatingMessageHandler (AbstractCorrelatingMessageHandler のサブクラス)は MessageHandler 実装であり、アグリゲーター(および他の相関するユースケース)の共通機能をカプセル化します。

  • 集約するグループにメッセージを関連付ける

  • グループが解放されるまで MessageStore でこれらのメッセージを維持する

  • グループをいつリリースできるかを決定する

  • リリースされたグループを単一のメッセージに集約する

  • 期限切れのグループの認識と対応

メッセージをグループ化する方法を決定する責任は、CorrelationStrategy インスタンスに委譲されます。メッセージグループを解放できるかどうかを決定する責任は、ReleaseStrategy インスタンスに委譲されます。

次のリストは、ベース AbstractAggregatingMessageGroupProcessor の簡単なハイライトを示しています(aggregatePayloads メソッドを実装する責任は開発者に任されています)。

public abstract class AbstractAggregatingMessageGroupProcessor
              implements MessageGroupProcessor {

    protected Map<String, Object> aggregateHeaders(MessageGroup group) {
        // default implementation exists
    }

    protected abstract Object aggregatePayloads(MessageGroup group, Map<String, Object> defaultHeaders);

}

AbstractAggregatingMessageGroupProcessor のすぐに使用可能な実装として DefaultAggregatingMessageGroupProcessorExpressionEvaluatingMessageGroupProcessorMethodInvokingMessageGroupProcessor を参照してください。

バージョン 5.2 以降、Function<MessageGroup, Map<String, Object>> 戦略は、AbstractAggregatingMessageGroupProcessor が出力メッセージのヘッダーをマージおよび計算(集約)するために使用可能です。DefaultAggregateHeadersFunction 実装は、グループ間で競合のないすべてのヘッダーを返すロジックで使用できます。グループ内の 1 つ以上のメッセージにヘッダーがない場合、競合とは見なされません。競合するヘッダーは省略されます。新しく導入された DelegatingMessageGroupProcessor とともに、この関数は任意の(AbstractAggregatingMessageGroupProcessor 以外の) MessageGroupProcessor 実装に使用されます。基本的に、フレームワークは提供された関数を AbstractAggregatingMessageGroupProcessor インスタンスに注入し、他のすべての実装を DelegatingMessageGroupProcessor にラップします。AbstractAggregatingMessageGroupProcessor と DelegatingMessageGroupProcessor のロジックの違い。後者は、デリゲート戦略を呼び出す前にヘッダーを事前に計算せず、デリゲートが Message または AbstractIntegrationMessageBuilder を返す場合、関数を呼び出しません。その場合、フレームワークは、ターゲット実装が返された結果に含まれる適切なヘッダーセットを生成することを考慮したと想定します。Function<MessageGroup, Map<String, Object>> ストラテジーは、XML 構成の headers-function 参照属性、Java DSL の AggregatorSpec.headersFunction() オプション、プレーン Java 構成の AggregatorFactoryBean.setHeadersFunction() として利用可能です。

CorrelationStrategy は AbstractCorrelatingMessageHandler が所有し、次の例に示すように、IntegrationMessageHeaderAccessor.CORRELATION_ID メッセージヘッダーに基づいたデフォルト値を持っています。

public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
        CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) {
    ...
    this.correlationStrategy = correlationStrategy == null ?
        new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID) : correlationStrategy;
    this.releaseStrategy = releaseStrategy == null ? new SimpleSequenceSizeReleaseStrategy() : releaseStrategy;
    ...
}

メッセージグループの実際の処理に関しては、デフォルトの実装は DefaultAggregatingMessageGroupProcessor です。ペイロードが特定のグループに対して受信されたペイロードの List である単一の Message を作成します。これは、スプリッター、パブリッシュ / サブスクライブチャネル、アップストリームの受信者リストルーターを使用した単純なスキャッターギャザーの実装に適しています。

この型のシナリオでパブリッシュ / サブスクライブチャネルまたは受信者リストルーターを使用する場合は、必ず apply-sequence フラグを有効にしてください。そうすることで、必要なヘッダー CORRELATION_IDSEQUENCE_NUMBERSEQUENCE_SIZE が追加されます。この動作は、Spring Integration のスプリッターではデフォルトで有効になっていますが、パブリッシュ / サブスクライブチャネルや受信者リストルーターでは有効になっていません。これらのコンポーネントは、これらのヘッダーが不要なさまざまなコンテキストで使用される可能性があるためです。

アプリケーションに特定のアグリゲーター戦略を実装する場合、AbstractAggregatingMessageGroupProcessor を継承して aggregatePayloads メソッドを実装できます。ただし、XML またはアノテーションを使用して構成できる集約ロジックを実装するための、API とはあまり関係のない、より優れたソリューションがあります。

一般に、POJO は、単一の java.util.List を引数として受け入れるメソッドを提供する場合、集約アルゴリズムを実装できます(パラメーター化されたリストもサポートされます)。このメソッドは、次のようにメッセージを集約するために呼び出されます。

  • 引数が java.util.Collection<T> であり、パラメーター型 T が Message に割り当て可能な場合、集約のために蓄積されたメッセージのリスト全体が集約器に送信されます。

  • 引数がパラメーター化されていない java.util.Collection であるか、パラメーター型が Message に割り当て可能でない場合、メソッドは蓄積されたメッセージのペイロードを受け取ります。

  • 戻り値の型が Message に割り当て可能でない場合、フレームワークによって自動的に作成される Message のペイロードとして扱われます。

コードをシンプルにし、低カップリング、テスト容易性などのベストプラクティスを促進するために、集約ロジックを実装する推奨する方法は、POJO を使用し、XML またはアノテーションサポートを使用してアプリケーションで構成することです。

バージョン 5.3 以降、メッセージグループの処理後、AbstractCorrelatingMessageHandler は、いくつかのネストされたレベルを持つ適切なスプリッターアグリゲーターシナリオの MessageBuilder.popSequenceDetails() メッセージヘッダー変更を実行します。メッセージグループの解放結果がメッセージのコレクションでない場合にのみ行われます。その場合、ターゲット MessageGroupProcessor は、それらのメッセージの作成中に MessageBuilder.popSequenceDetails() 呼び出しを担当します。

MessageGroupProcessor が Message を返す場合、sequenceDetails がグループ内の最初のメッセージと一致する場合にのみ、出力メッセージに対して MessageBuilder.popSequenceDetails() が実行されます。(以前は、プレーンペイロードまたは AbstractIntegrationMessageBuilder が MessageGroupProcessor から返された場合にのみ、これが行われていました。)

この機能は、新しい popSequence boolean プロパティによって制御できるため、相関の詳細が標準スプリッターによって入力されていないシナリオでは、MessageBuilder.popSequenceDetails() を無効にすることができます。このプロパティは、基本的に、AbstractMessageSplitter 内の最も近いアップストリーム applySequence = true によって実行されたことを元に戻します。詳細については、スプリッターを参照してください。

SimpleMessageGroup.getMessages() メソッドは unmodifiableCollection を返します。集約 POJO メソッドに Collection<Message> パラメーターがある場合、渡される引数は正確にその Collection インスタンスであり、アグリゲーターに SimpleMessageStore を使用すると、元の Collection<Message> はグループの解放後にクリアされます。アグリゲーターから渡された場合、POJO の Collection<Message> 変数もクリアされます。さらなる処理のためにそのコレクションをそのままリリースしたい場合は、新しい Collection (たとえば new ArrayList<Message>(messages))を構築する必要があります。バージョン 4.3 以降、フレームワークは、不要な余分なオブジェクトの作成を避けるために、メッセージを新しいコレクションにコピーしなくなりました。

MessageGroupProcessor の processMessageGroup メソッドがコレクションを返す場合、Message<?> オブジェクトのコレクションでなければなりません。この場合、メッセージは個別にリリースされます。バージョン 4.2 より前は、XML 構成を使用して MessageGroupProcessor を提供することはできませんでした。集約には POJO メソッドのみを使用できます。ここで、参照された(または内部の)Bean が MessageProcessor を実装していることをフレームワークが検出すると、アグリゲーターの出力プロセッサーとして使用されます。

カスタム MessageGroupProcessor からオブジェクトのコレクションをメッセージのペイロードとしてリリースする場合、クラスは AbstractAggregatingMessageGroupProcessor を継承し、aggregatePayloads() を実装する必要があります。

また、バージョン 4.2 以降、SimpleMessageGroupProcessor が提供されています。グループからメッセージのコレクションを返します。これにより、前述のように、リリースされたメッセージが個別に送信されます。

これにより、アグリゲーターはメッセージバリアとして機能し、リリース戦略が実行され、グループが個々のメッセージのシーケンスとしてリリースされるまで、到着したメッセージが保持されます。

ReleaseStrategy

ReleaseStrategy インターフェースは次のように定義されます。

public interface ReleaseStrategy {

  boolean canRelease(MessageGroup group);

}

一般に、POJO は、単一の java.util.List を引数として受け入れ(パラメーター化されたリストもサポートされている)、ブール値を返すメソッドを提供する場合、完了決定ロジックを実装できます。このメソッドは、新しいメッセージが到着するたびに呼び出され、次のようにグループが完了したかどうかを判断します。

  • 引数が java.util.List<T> であり、パラメーター型 T が Message に割り当て可能な場合、グループに蓄積されたメッセージのリスト全体がメソッドに送信されます。

  • 引数がパラメーター化されていない java.util.List であるか、パラメーター型が Message に割り当て可能でない場合、メソッドは蓄積されたメッセージのペイロードを受け取ります。

  • このメソッドは、メッセージグループが集約の準備ができている場合は true を返し、それ以外の場合は false を返す必要があります。

次の例は、型 Message の List に @ReleaseStrategy アノテーションを使用する方法を示しています。

public class MyReleaseStrategy {

    @ReleaseStrategy
    public boolean canMessagesBeReleased(List<Message<?>>) {...}
}

次の例は、型 String の List に @ReleaseStrategy アノテーションを使用する方法を示しています。

public class MyReleaseStrategy {

    @ReleaseStrategy
    public boolean canMessagesBeReleased(List<String>) {...}
}

前述の 2 つの例の署名に基づいて、POJO ベースのリリース戦略には、まだリリースされていないメッセージの Collection (Message 全体へのアクセスが必要な場合)またはペイロードオブジェクトの Collection が渡されます(type パラメーターが何かの場合 Message 以外)。これにより、ほとんどのユースケースが満たされます。ただし、何らかの理由で MessageGroup 全体にアクセスする必要がある場合は、ReleaseStrategy インターフェースの実装を提供する必要があります。

グループがリリースされる前にリリース戦略が複数回呼び出される可能性があるため、潜在的に大きなグループを処理する場合、これらのメソッドがどのように呼び出されるかを理解する必要があります。最も効率的なのは、アグリゲーターが直接呼び出すことができるため、ReleaseStrategy の実装です。2 番目に効率的なのは、Collection<Message<?>> パラメーター型の POJO メソッドです。最も効率が悪いのは、Collection<Something> 型の POJO メソッドです。フレームワークは、リリース戦略が呼び出されるたびに、グループ内のメッセージからペイロードを新しいコレクションにコピーする必要があります(ペイロードを Something に変換しようとする可能性があります)。Collection<?> を使用すると、変換は回避されますが、新しい Collection を作成する必要があります。

これらの理由から、大規模なグループでは、ReleaseStrategy を実装することをお勧めします。

グループが集約のためにリリースされると、まだリリースされていないすべてのメッセージが処理され、グループから削除されます。グループも完了している場合(つまり、シーケンスからのすべてのメッセージが到着した場合、またはシーケンスが定義されていない場合)、グループは完了としてマークされます。このグループの新しいメッセージは、破棄チャネル(定義されている場合)に送信されます。expire-groups-upon-completion を true (デフォルトは false)に設定すると、グループ全体が削除され、新しいメッセージ(削除されたグループと同じ相関 ID を持つ)は新しいグループを形成します。MessageGroupStoreReaper と true に設定されている send-partial-result-on-expiry を使用して、部分シーケンスを解放できます。

遅延到着メッセージの廃棄を容易にするために、アグリゲーターはグループが解放された後、グループに関する状態を維持する必要があります。これにより、最終的にメモリ不足状態が発生する可能性があります。このような状況を回避するには、MessageGroupStoreReaper を構成してグループメタデータを削除することを検討する必要があります。有効期限パラメーターは、ポイントに到達した後にグループが期限切れになるように設定する必要があります。それ以降は、遅延メッセージが到着することはありません。リーパーの構成については、アグリゲーターでの状態の管理: MessageGroupStore を参照してください。

Spring Integration は、ReleaseStrategy の実装を提供します: SimpleSequenceSizeReleaseStrategy。この実装は、到着する各メッセージの SEQUENCE_NUMBER および SEQUENCE_SIZE ヘッダーを調べて、メッセージグループが完了し、集約の準備ができたときを判断します。前に示したように、これはデフォルトの戦略でもあります。

バージョン 5.0 より前のデフォルトのリリース戦略は SequenceSizeReleaseStrategy でしたが、これは大規模なグループではうまく機能しません。この戦略では、重複したシーケンス番号が検出され、拒否されます。この操作は高負荷になる可能性があります。

大きなグループを集約する場合、部分的なグループを解放する必要はなく、重複したシーケンスを検出 / 拒否する必要はありません。代わりに SimpleSequenceSizeReleaseStrategy を使用することを検討してください。これらのユースケースでははるかに効率的で、デフォルトです部分的なグループリリースが指定されていない場合、バージョン 5.0 以降。

大規模グループの集約

4.3 リリースは、SimpleMessageGroup のメッセージのデフォルト Collection を HashSet に変更しました(以前は BlockingQueue でした)。これは、大きなグループから個々のメッセージを削除するときにコストがかかりました(O(n)線形スキャンが必要でした)。ハッシュセットは通常、削除がはるかに高速ですが、挿入と削除の両方でハッシュを計算する必要があるため、大きなメッセージの場合はコストが高くなる可能性があります。ハッシュが高負荷なメッセージがある場合は、他のコレクション型の使用を検討してください。MessageGroupFactory を使用するで説明したように、SimpleMessageGroupFactory が提供されているため、ニーズに最適な Collection を選択できます。独自のファクトリ実装を提供して、他の Collection<Message<?>> を作成することもできます。

次の例は、以前の実装と SimpleSequenceSizeReleaseStrategy でアグリゲーターを構成する方法を示しています。

<int:aggregator input-channel="aggregate"
    output-channel="out" message-store="store" release-strategy="releaser" />

<bean id="store" class="org.springframework.integration.store.SimpleMessageStore">
    <property name="messageGroupFactory">
        <bean class="org.springframework.integration.store.SimpleMessageGroupFactory">
            <constructor-arg value="BLOCKING_QUEUE"/>
        </bean>
    </property>
</bean>

<bean id="releaser" class="SimpleSequenceSizeReleaseStrategy" />
相関戦略

CorrelationStrategy インターフェースは次のように定義されます。

public interface CorrelationStrategy {

  Object getCorrelationKey(Message<?> message);

}

このメソッドは、メッセージをメッセージグループに関連付けるために使用される相関キーを表す Object を返します。キーは、equals() および hashCode() の実装に関して Map のキーに使用される条件を満たさなければなりません。

一般に、どの POJO も相関ロジックを実装でき、メッセージをメソッドの引数にマッピングするルールは ServiceActivator と同じです(@Header アノテーションのサポートを含む)。メソッドは値を返す必要があり、値は null であってはなりません。

Spring Integration は、CorrelationStrategy の実装を提供します: HeaderAttributeCorrelationStrategy。この実装は、メッセージヘッダーの 1 つ(コンストラクター引数で名前が指定されている)の値を相関キーとして返します。デフォルトでは、相関戦略は CORRELATION_ID ヘッダー属性の値を返す HeaderAttributeCorrelationStrategy です。相関に使用するカスタムヘッダー名がある場合は、HeaderAttributeCorrelationStrategy のインスタンスで構成して、アグリゲーターの相関戦略の参照として提供できます。

レジストリをロック

グループへの変更はスレッドセーフです。同じ相関 ID のメッセージを同時に送信すると、そのうちの 1 つのみがアグリゲーターで処理され、メッセージグループごとにシングルスレッドとして効果的に処理されます。LockRegistry は、解決された相関 ID のロックを取得するために使用されます。DefaultLockRegistry はデフォルトで使用されます(メモリ内)。共有 MessageGroupStore が使用されているサーバー間で更新を同期するには、共有ロックレジストリを構成する必要があります。

デッドロックの回避

上記で説明したように、メッセージグループが変更されると(メッセージが追加または解放される)、ロックが保持されます。

次のフローを検討してください。

...->aggregator1-> ... ->aggregator2-> ...

複数のスレッドがあり、アグリゲーターが共通のロックレジストリを共有している場合、デッドロックが発生する可能性があります。これにより、スレッドがハングし、jstack <pid> は次のような結果を表示する場合があります。

Found one Java-level deadlock:
=============================
"t2":
  waiting for ownable synchronizer 0x000000076c1cbfa0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "t1"
"t1":
  waiting for ownable synchronizer 0x000000076c1ccc00, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "t2"

この問題を回避するには、いくつかの方法があります。

  • 各アグリゲーターに独自のロックレジストリがあることを確認します (これは、アプリケーションインスタンス間で共有レジストリにすることができますが、フロー内の 2 つ以上のアグリゲーターにはそれぞれ個別のレジストリが必要です)

  • アグリゲータの出力チャネルとして ExecutorChannel または QueueChannel を使用して、ダウンストリームフローが新しいスレッドで実行されるようにします

  • バージョン 5.1.1 以降、releaseLockBeforeSend アグリゲータープロパティを true に設定します。

この問題は、何らかの理由で単一のアグリゲーターの出力が最終的に同じアグリゲーターにルーティングされる場合にも発生する可能性があります。もちろん、この場合、上記の最初の解決策は適用されません。

Java DSL でのアグリゲーターの構成

Java DSL でアグリゲーターを構成する方法については、アグリゲーターとリシーケンサーを参照してください。

XML を使用したアグリゲーターの構成

Spring Integration は、<aggregator/> 要素を介した XML を使用したアグリゲーターの構成をサポートします。次の例は、アグリゲーターの例を示しています。

<channel id="inputChannel"/>

<int:aggregator id="myAggregator"                          (1)
        auto-startup="true"                                (2)
        input-channel="inputChannel"                       (3)
        output-channel="outputChannel"                     (4)
        discard-channel="throwAwayChannel"                 (5)
        message-store="persistentMessageStore"             (6)
        order="1"                                          (7)
        send-partial-result-on-expiry="false"              (8)
        send-timeout="1000"                                (9)

        correlation-strategy="correlationStrategyBean"     (10)
        correlation-strategy-method="correlate"            (11)
        correlation-strategy-expression="headers['foo']"   (12)

        ref="aggregatorBean"                               (13)
        method="aggregate"                                 (14)

        release-strategy="releaseStrategyBean"             (15)
        release-strategy-method="release"                  (16)
        release-strategy-expression="size() == 5"          (17)

        expire-groups-upon-completion="false"              (18)
        empty-group-min-timeout="60000"                    (19)

        lock-registry="lockRegistry"                       (20)

        group-timeout="60000"                              (21)
        group-timeout-expression="size() ge 2 ? 100 : -1"  (22)
        expire-groups-upon-timeout="true"                  (23)

        scheduler="taskScheduler" >                        (24)
            <expire-transactional/>                        (25)
            <expire-advice-chain/>                         (26)
</aggregator>

<int:channel id="outputChannel"/>

<int:channel id="throwAwayChannel"/>

<bean id="persistentMessageStore" class="org.springframework.integration.jdbc.store.JdbcMessageStore">
    <constructor-arg ref="dataSource"/>
</bean>

<bean id="aggregatorBean" class="sample.PojoAggregator"/>

<bean id="releaseStrategyBean" class="sample.PojoReleaseStrategy"/>

<bean id="correlationStrategyBean" class="sample.PojoCorrelationStrategy"/>
1 アグリゲーターの ID はオプションです。
2 アプリケーションコンテキストの起動中にアグリゲーターを起動する必要があるかどうかを示すライフサイクル属性。オプション(デフォルトは "true" )。
3 アグリゲーターがメッセージを受信するチャンネル。必須。
4 アグリゲータが集約結果を送信するチャネル。オプション (受信メッセージ自体が 'replyChannel' メッセージヘッダーで応答チャネルを指定できるため)。
5 アグリゲーターがタイムアウトしたメッセージを送信するチャネル(send-partial-result-on-expiry が false の場合)。オプション。
6 完了するまで相関キーにメッセージのグループを保管するために使用される MessageGroupStore への参照。オプション。デフォルトでは、揮発性のメモリ内ストアです。詳細については、メッセージストアを参照してください。
7 複数のハンドルが同じ DirectChannel にサブスクライブされるときのこのアグリゲーターの順序(負荷分散の目的で使用)。オプション。
8 期限切れのメッセージは、それを含む MessageGroup の有効期限が切れたら ( MessageGroupStore.expireMessageGroups(long) (Javadoc) を参照)、集約されて「出力チャネル」または "replyChannel" に送信されることを示します。MessageGroup を期限切れにする 1 つの方法は、MessageGroupStoreReaper を構成することです。ただし、代わりに MessageGroupStore.expireMessageGroups(timeout) を呼び出して MessageGroup を期限切れにすることもできます。これは、コントロールバス操作を通じて、または MessageGroupStore インスタンスへの参照がある場合は expireMessageGroups(timeout) を呼び出すことによって実行できます。それ以外の場合、この属性自体は何も行いません。期限切れになりそうな MessageGroup に残っているメッセージを破棄するか、出力チャネルまたは応答チャネルに送信するかを示すインジケータとしてのみ機能します。オプション (デフォルトは false)。注: expire-groups-upon-timeout が false に設定されている場合、グループは実際には期限切れにならない可能性があるため、この属性は send-partial-result-on-timeout と呼ぶ方が適切です。
9 応答 Message を output-channel または discard-channel に送信するときに待機するタイムアウト間隔。デフォルトは -1 で、これにより無期限にブロックされます。固定の「容量」を持つ QueueChannel など、出力チャネルに「送信」制限がある場合にのみ適用されます。この場合、MessageDeliveryException がスローされます。AbstractSubscribableChannel 実装の場合、send-timeout は無視されます。group-timeout(-expression) の場合、スケジュールされた期限切れタスクの MessageDeliveryException により、このタスクが再スケジュールされます。オプション。
10 メッセージ相関(グループ化)アルゴリズムを実装する Bean への参照。Bean は、CorrelationStrategy インターフェースまたは POJO の実装にすることができます。後者の場合、correlation-strategy-method 属性も定義する必要があります。オプション(デフォルトでは、アグリゲーターは IntegrationMessageHeaderAccessor.CORRELATION_ID ヘッダーを使用します)。
11correlation-strategy によって参照される Bean で定義されたメソッド。相関決定アルゴリズムを実装します。オプション、制限付き(correlation-strategy が存在する必要があります)。
12 相関戦略を表す SpEL 式。例: "headers['something']" correlation-strategy または correlation-strategy-expression のいずれかのみが許可されます。
13 アプリケーションコンテキストで定義された Bean への参照。Bean は、前述のように集約ロジックを実装する必要があります。オプション(デフォルトでは、集約されたメッセージのリストは出力メッセージのペイロードになります)。
14ref 属性によって参照される Bean で定義されたメソッド。メッセージ集約アルゴリズムを実装します。オプション(定義される ref 属性に依存)。
15 リリース戦略を実装する Bean への参照。Bean は、ReleaseStrategy インターフェースまたは POJO の実装にすることができます。後者の場合、release-strategy-method 属性も定義する必要があります。オプション(デフォルトでは、アグリゲーターは IntegrationMessageHeaderAccessor.SEQUENCE_SIZE ヘッダー属性を使用します)。
16release-strategy 属性によって参照される Bean で定義されたメソッド。完了決定アルゴリズムを実装します。オプション、制限付き(release-strategy が存在する必要があります)。
17 リリース戦略を表す SpEL 式。式のルートオブジェクトは MessageGroup です。例: "size() == 5" release-strategy または release-strategy-expression のいずれかのみが許可されます。
18true (デフォルトは false)に設定すると、完了したグループがメッセージストアから削除され、同じ相関を持つ後続のメッセージが新しいグループを形成します。デフォルトの動作では、完了したグループと同じ相関関係を持つメッセージを discard-channel に送信します。
19MessageGroupStoreReaper が <aggregator> の MessageStore 用に構成されている場合にのみ適用されます。デフォルトでは、MessageGroupStoreReaper が部分グループを期限切れにするように構成されている場合、空のグループも削除されます。グループが通常解放された後、空のグループが存在します。空のグループにより、後着メッセージの検出と破棄が可能になります。部分的なグループの期限切れよりも長いスケジュールで空のグループを期限切れにする場合は、このプロパティを設定します。空のグループは、少なくともこのミリ秒数の間変更されない限り、MessageStore から削除されません。空のグループの有効期限が切れる実際の時間は、リーパーの timeout プロパティの影響も受けます。この値にタイムアウトを加えた値になる可能性があることに注意してください。
20org.springframework.integration.util.LockRegistry Bean への参照。MessageGroup での同時操作のために、groupId に基づいて Lock を取得していました。デフォルトでは、内部 DefaultLockRegistry が使用されます。ZookeeperLockRegistry などの分散 LockRegistry を使用すると、アグリゲーターの 1 つのインスタンスのみがグループで同時に操作できるようになります。詳細については、Redis ロックレジストリGemfire Lock レジストリZookeeper ロックレジストリを参照してください。
21ReleaseStrategy が現在のメッセージが到着したときにグループを解放しない場合に、MessageGroup を強制的に完了するタイムアウト(ミリ秒単位)。この属性は、時間からカウントされるタイムアウト内で MessageGroup に新しいメッセージが到着しない場合、部分的な結果を発行する(またはグループを破棄する)必要がある場合に、アグリゲーターに組み込みの時間ベースのリリース戦略を提供します。最後のメッセージが到着しました。MessageGroup が作成された時間からカウントするタイムアウトを設定するには、group-timeout-expression 情報を参照してください。新しいメッセージがアグリゲーターに到着すると、その MessageGroup の既存の ScheduledFuture<?> は取り消されます。ReleaseStrategy が false (解放しないことを意味する)および groupTimeout > 0 を返す場合、新しいタスクがグループを期限切れにするようにスケジュールされます。この属性をゼロ(または負の値)に設定することはお勧めしません。これを行うと、すべてのメッセージグループがすぐに完了するため、アグリゲーターが効果的に無効になります。ただし、式を使用して条件付きでゼロ(または負の値)に設定できます。詳細については、group-timeout-expression を参照してください。完了時に実行されるアクションは、ReleaseStrategy および send-partial-group-on-expiry 属性によって異なります。詳細については、アグリゲーターとグループのタイムアウトを参照してください。'group-timeout-expression' 属性と相互に排他的です。
22MessageGroup を #root 評価コンテキストオブジェクトとする groupTimeout に評価される SpEL 式。MessageGroup の強制完了のスケジューリングに使用します。式が null と評価される場合、補完はスケジュールされません。評価が 0 の場合、グループは現在のスレッドでただちに完了します。これにより、動的な group-timeout プロパティが提供されます。例として、グループが作成されてから 10 秒が経過した後に MessageGroup を強制的に完了させたい場合は、以下の SpEL 式を使用することを検討してください。timestamp + 10000 - T(System).currentTimeMillis() ここで、timestamp は MessageGroup として MessageGroup.getTimestamp() から提供されます。ここでは #root 評価コンテキストオブジェクトです。ただし、他のグループ有効期限プロパティの構成によっては、グループ作成時刻が最初に到着したメッセージの時刻と異なる場合があることに注意してください。詳細については、group-timeout を参照してください。'group-timeout' 属性とは相互に排他的です。
23 タイムアウトにより(または MessageGroupStoreReaper により)グループが完了すると、グループはデフォルトで期限切れ(完全に削除)になります。遅れて到着するメッセージは新しいグループを開始します。これを false に設定してグループを完成させることができますが、そのメタデータを残しておくと、遅れて到着するメッセージが破棄されます。空のグループは、empty-group-min-timeout 属性と一緒に MessageGroupStoreReaper を使用して、後で期限切れにすることができます。デフォルトは "true" です。
24groupTimeout 内の MessageGroup に新しいメッセージが到着しない場合、MessageGroup を強制的に完了するようにスケジュールする TaskScheduler Bean 参照。指定しない場合、ApplicationContext (ThreadPoolTaskScheduler)に登録されているデフォルトのスケジューラー(taskScheduler)が使用されます。group-timeout または group-timeout-expression が指定されていない場合、この属性は適用されません。
25 バージョン 4.1 以降。これにより、forceComplete 操作のトランザクションを開始できます。これは、group-timeout(-expression) または MessageGroupStoreReaper によって開始され、通常の addreleasediscard 操作には適用されません。このサブエレメントまたは <expire-advice-chain/> のみが許可されます。
26 バージョン 4.1 以降。これにより、forceComplete 操作用に任意の Advice を構成できます。これは、group-timeout(-expression) または MessageGroupStoreReaper によって開始され、通常の addreleasediscard 操作には適用されません。このサブエレメントまたは <expire-transactional/> のみが許可されます。トランザクション Advice は、Spring tx 名前空間を使用してここで構成することもできます。
期限切れのグループ

期限切れ(完全に削除)のグループに関連する 2 つの属性があります。グループの有効期限が切れると、そのレコードはありません。また、同じ相関で新しいメッセージが到着すると、新しいグループが開始されます。グループが(有効期限なしで)完了すると、空のグループが残り、遅れて到着したメッセージは破棄されます。空のグループは、MessageGroupStoreReaper を empty-group-min-timeout 属性と組み合わせて使用することにより、後で削除できます。

expire-groups-upon-completion は、ReleaseStrategy がグループを解放するときの「正常な」完了に関連しています。デフォルトは false です。

グループが正常に完了せず、タイムアウトのために解放または破棄された場合、グループは通常有効期限が切れます。バージョン 4.1 以降、expire-groups-upon-timeout を使用してこの動作を制御できます。下位互換性のため、デフォルトでは true になっています。

グループがタイムアウトすると、ReleaseStrategy にはグループを解放するもう 1 つの機会が与えられます。expire-groups-upon-timeout が false である場合、有効期限は expire-groups-upon-completion によって制御されます。タイムアウト時にグループがリリース戦略によってリリースされない場合、有効期限は expire-groups-upon-timeout によって制御されます。タイムアウトしたグループは破棄されるか、部分的なリリースが発生します(send-partial-result-on-expiry に基づく)。

バージョン 5.0 以降、空のグループも empty-group-min-timeout の後に削除される予定です。expireGroupsUponCompletion == false および minimumTimeoutForEmptyGroups > 0 の場合、グループを削除するタスクは、通常または部分シーケンスのリリースが発生したときにスケジュールされます。

通常、カスタムアグリゲーターハンドラー実装が他の <aggregator> 定義で参照される可能性がある場合は、ref 属性を使用することをお勧めします。ただし、カスタムアグリゲーターの実装が <aggregator> の単一の定義でのみ使用されている場合、次の例に示すように、<aggregator> エレメント内の集約 POJO を構成するために、内部 Bean 定義(バージョン 1.0.3 以降)を使用できます。

<aggregator input-channel="input" method="sum" output-channel="output">
    <beans:bean class="org.foo.PojoAggregator"/>
</aggregator>
同じ <aggregator> 構成で ref 属性と内部 Bean 定義の両方を使用することは、あいまいな条件を作成するため許可されません。そのような場合、例外がスローされます。

次の例は、アグリゲーター Bean の実装を示しています。

public class PojoAggregator {

  public Long add(List<Long> results) {
    long total = 0l;
    for (long partialResult: results) {
      total += partialResult;
    }
    return total;
  }
}

前述の例の完了戦略 Bean の実装は、次のとおりです。

public class PojoReleaseStrategy {
...
  public boolean canRelease(List<Long> numbers) {
    int sum = 0;
    for (long number: numbers) {
      sum += number;
    }
    return sum >= maxValue;
  }
}
意味がある場合はいつでも、リリース戦略メソッドとアグリゲーターメソッドを単一の Bean に組み合わせることができます。

上記の例の相関戦略 Bean の実装は、次のようになります。

public class PojoCorrelationStrategy {
...
  public Long groupNumbersByLastDigit(Long number) {
    return number % 10;
  }
}

前の例のアグリゲーターは、ある基準(この場合は 10 で割った後の残り)によって番号をグループ化し、ペイロードによって提供される番号の合計が特定の値を超えるまでグループを保持します。

そうすることが理にかなっている場合はいつでも、リリース戦略メソッド、相関戦略メソッド、アグリゲーターメソッドを単一の Bean に組み合わせることができます。(実際、それらのすべてまたは任意の 2 つを組み合わせることができます。)
アグリゲーターと Spring 式言語 (SpEL)

Spring Integration 2.0 以降、さまざまな戦略(相関、リリース、集約)を SpEL で処理できるようになりました。これは、そのようなリリース戦略の背後にあるロジックが比較的単純な場合に推奨されます。オブジェクトの配列を受け取るように設計されたレガシーコンポーネントがあるとします。デフォルトのリリース戦略では、List 内のすべての集約メッセージが組み立てられることがわかっています。ここで 2 つの問題があります。まず、リストから個々のメッセージを抽出する必要があります。次に、各メッセージのペイロードを抽出し、オブジェクトの配列を組み立てる必要があります。次の例は、両方の問題を解決します。

public String[] processRelease(List<Message<String>> messages){
    List<String> stringList = new ArrayList<String>();
    for (Message<String> message : messages) {
        stringList.add(message.getPayload());
    }
    return stringList.toArray(new String[]{});
}

ただし、SpEL では、このような要件は実際には 1 行の式で比較的簡単に処理できるため、カスタムクラスを記述して Bean として構成する必要はありません。次の例は、その方法を示しています。

<int:aggregator input-channel="aggChannel"
    output-channel="replyChannel"
    expression="#this.![payload].toArray()"/>

前述の構成では、コレクション射影式を使用して、リスト内のすべてのメッセージのペイロードから新しいコレクションを組み立て、それを配列に変換します。これにより、以前の Java コードと同じ結果が得られます。

カスタムリリースおよび相関戦略を扱うときに、同じ式ベースのアプローチを適用できます。

次の例に示すように、correlation-strategy 属性でカスタム CorrelationStrategy の Bean を定義する代わりに、単純な相関ロジックを SpEL 式として実装し、correlation-strategy-expression 属性で構成できます。

correlation-strategy-expression="payload.person.id"

前述の例では、ペイロードに person 属性と id があり、メッセージの関連付けに使用されると想定しています。

同様に、ReleaseStrategy の場合、リリースロジックを SpEL 式として実装し、release-strategy-expression 属性で構成できます。評価コンテキストのルートオブジェクトは MessageGroup 自体です。メッセージの List は、式内のグループの message プロパティを使用して参照できます。

前の例が示すように、バージョン 5.0 より前のリリースでは、ルートオブジェクトは Message<?> のコレクションでした。
release-strategy-expression="!messages.?[payload==5].empty"

上記の例では、SpEL 評価コンテキストのルートオブジェクトは MessageGroup 自体であり、このグループに 5 のペイロードを持つメッセージがあるとすぐに、グループを解放する必要があると述べています。

アグリゲーターとグループのタイムアウト

バージョン 4.0 から、2 つの新しい相互に排他的な属性が導入されました: group-timeout および group-timeout-expression (前述の説明を参照)。XML を使用したアグリゲーターの構成を参照してください。場合によっては、現在のメッセージが到着したときに ReleaseStrategy が解放されない場合、タイムアウト後にアグリゲーターの結果を発行する(またはグループを破棄する)必要があります。このため、groupTimeout オプションを使用すると、次の例に示すように、MessageGroup のスケジューリングを強制的に完了させることができます。

<aggregator input-channel="input" output-channel="output"
        send-partial-result-on-expiry="true"
        group-timeout-expression="size() ge 2 ? 10000 : -1"
        release-strategy-expression="messages[0].headers.sequenceNumber == messages[0].headers.sequenceSize"/>

この例では、アグリゲーターが release-strategy-expression で定義された最後のメッセージを順番に受信した場合、通常のリリースが可能です。その特定のメッセージが到着しない場合、groupTimeout は、グループに少なくとも 2 つのメッセージが含まれている限り、10 秒後にグループを強制的に完了させます。

グループを強制的に完了する結果は、ReleaseStrategy と send-partial-result-on-expiry に依存します。まず、通常のリリースが行われるかどうかを確認するために、リリース戦略が再度参照されます。グループは変更されていませんが、ReleaseStrategy はこの時点でグループをリリースすることを決定できます。それでもリリース戦略がグループをリリースしない場合、期限切れになります。send-partial-result-on-expiry が true の場合、(部分的な) MessageGroup 内の既存のメッセージは、output-channel への通常のアグリゲーター応答メッセージとしてリリースされます。それ以外の場合は、破棄されます。

groupTimeout の動作と MessageGroupStoreReaper には違いがあります(XML を使用したアグリゲーターの構成を参照)。リーパーは、MessageGroupStore 内のすべての MessageGroup に対して定期的に強制完了を開始します。groupTimeout は、groupTimeout の間に新しいメッセージが到着しない場合、各 MessageGroup に対して個別にそれを行います。また、リーパーを使用して空のグループを削除できます(expire-groups-upon-completion が false の場合、遅延メッセージを破棄するために空のグループが保持されます)。

アノテーション付きのアグリゲーターの構成

次の例は、アノテーションを使用して構成されたアグリゲーターを示しています。

public class Waiter {
  ...

  @Aggregator  (1)
  public Delivery aggregatingMethod(List<OrderItem> items) {
    ...
  }

  @ReleaseStrategy  (2)
  public boolean releaseChecker(List<Message<?>> messages) {
    ...
  }

  @CorrelationStrategy  (3)
  public String correlateBy(OrderItem item) {
    ...
  }
}
1 このメソッドをアグリゲーターとして使用する必要があることを示すアノテーション。このクラスをアグリゲーターとして使用する場合は指定する必要があります。
2 このメソッドがアグリゲーターのリリース戦略として使用されることを示すアノテーション。どのメソッドにも存在しない場合、アグリゲーターは SimpleSequenceSizeReleaseStrategy を使用します。
3 このメソッドをアグリゲーターの相関戦略として使用する必要があることを示すアノテーション。相関戦略が示されていない場合、アグリゲーターは CORRELATION_ID に基づいて HeaderAttributeCorrelationStrategy を使用します。

XML 要素によって提供されるすべての構成オプションは、@Aggregator アノテーションでも使用できます。

アグリゲーターは、XML から明示的に参照するか、クラスで @MessageEndpoint が定義されている場合、クラスパススキャンによって自動的に検出されます。

Aggregator コンポーネントのアノテーション構成(@Aggregator など)は、ほとんどのデフォルトオプションで十分な単純なユースケースのみを対象としています。アノテーション構成を使用するときにこれらのオプションをさらに制御する必要がある場合は、次の例に示すように、AggregatingMessageHandler の @Bean 定義の使用を検討し、@Bean メソッドを @ServiceActivator でマークします。

@ServiceActivator(inputChannel = "aggregatorChannel")
@Bean
public MessageHandler aggregator(MessageGroupStore jdbcMessageGroupStore) {
     AggregatingMessageHandler aggregator =
                       new AggregatingMessageHandler(new DefaultAggregatingMessageGroupProcessor(),
                                                 jdbcMessageGroupStore);
     aggregator.setOutputChannel(resultsChannel());
     aggregator.setGroupTimeoutExpression(new ValueExpression<>(500L));
     aggregator.setTaskScheduler(this.taskScheduler);
     return aggregator;
}

詳細については、プログラミングモデルおよび @Bean メソッドのアノテーションを参照してください。

バージョン 4.2 以降、AggregatorFactoryBean は、AggregatingMessageHandler の Java 構成を簡素化するために使用可能です。

アグリゲーターでの状態の管理: MessageGroupStore

アグリゲーター(および Spring Integration のその他のパターン)は、すべて同じ相関キーを持つ一定期間に到着したメッセージのグループに基づいて決定を下す必要があるステートフルパターンです。ステートフルパターン(ReleaseStrategy など)のインターフェースの設計は、コンポーネント(フレームワークまたはユーザーによって定義されているかどうか)がステートレスのままである必要があるという原則に基づいています。すべての状態は MessageGroup によって運ばれ、その管理は MessageGroupStore に委譲されます。MessageGroupStore インターフェースは次のように定義されます。

public interface MessageGroupStore {

    int getMessageCountForAllMessageGroups();

    int getMarkedMessageCountForAllMessageGroups();

    int getMessageGroupCount();

    MessageGroup getMessageGroup(Object groupId);

    MessageGroup addMessageToGroup(Object groupId, Message<?> message);

    MessageGroup markMessageGroup(MessageGroup group);

    MessageGroup removeMessageFromGroup(Object key, Message<?> messageToRemove);

    MessageGroup markMessageFromGroup(Object key, Message<?> messageToMark);

    void removeMessageGroup(Object groupId);

    void registerMessageGroupExpiryCallback(MessageGroupCallback callback);

    int expireMessageGroups(long timeout);
}

詳しくは、Javadoc を参照してください。

MessageGroupStore は、リリース戦略がトリガーされるのを待っている間、MessageGroups に状態情報を蓄積しますが、そのイベントは発生しません。そのため、古いメッセージが残るのを防ぎ、揮発性ストアがアプリケーションのシャットダウン時にクリーンアップのフックを提供するために、MessageGroupStore では、MessageGroups が期限切れになったときに適用するコールバックを登録できます。次のように、インターフェースは非常に簡単です。

public interface MessageGroupCallback {

    void execute(MessageGroupStore messageGroupStore, MessageGroup group);

}

コールバックはストアとメッセージグループに直接アクセスできるため、永続的な状態を管理できます(たとえば、グループをストアから完全に削除するなど)。

MessageGroupStore はこれらのコールバックのリストを保持し、要求に応じて、タイムスタンプがパラメーターとして指定された時間よりも早いすべてのメッセージに適用します(前述の registerMessageGroupExpiryCallback(..) および expireMessageGroups(..) メソッドを参照)。詳細については、アグリゲーターでの状態の管理: MessageGroupStore を参照してください。

expireMessageGroups 機能に依存する場合、異なるアグリゲーターコンポーネントで同じ MessageGroupStore インスタンスを使用しないことが重要です。すべての AbstractCorrelatingMessageHandler は、forceComplete() コールバックに基づいて独自の MessageGroupCallback を登録します。この方法では、有効期限の各グループが間違ったアグリゲーターによって完了または破棄される可能性があります。バージョン 5.0.10 から、MessageGroupStore の登録コールバックに UniqueExpiryCallback が AbstractCorrelatingMessageHandler から使用されます。MessageGroupStore は、このクラスのインスタンスが存在するかどうかを確認し、コールバックセットにすでに存在する場合は適切なメッセージでエラーを記録します。このように、フレームワークは、異なるアグリゲーター / リシーケンサーでの MessageGroupStore インスタンスの使用を禁止して、特定の相関ハンドラーによって作成されていないグループの期限切れの前述の副作用を回避します。

タイムアウト値を指定して expireMessageGroups メソッドを呼び出すことができます。現在の時刻からこの値を引いたものより古いメッセージは期限切れになり、コールバックが適用されます。メッセージグループの「有効期限」の意味を定義するのはストアのユーザーです。

次の例に示すように、ユーザーの利便性のために、Spring Integration は MessageGroupStoreReaper の形式でメッセージの有効期限のラッパーを提供します。

<bean id="reaper" class="org...MessageGroupStoreReaper">
    <property name="messageGroupStore" ref="messageStore"/>
    <property name="timeout" value="30000"/>
</bean>

<task:scheduled-tasks scheduler="scheduler">
    <task:scheduled ref="reaper" method="run" fixed-rate="10000"/>
</task:scheduled-tasks>

リーパーは Runnable です。上記の例では、メッセージグループストアの expire メソッドは 10 秒ごとに呼び出されます。タイムアウト自体は 30 秒です。

MessageGroupStoreReaper の 'timeout' プロパティはおおよその値であり、タスクスケジューラのレートの影響を受けることを理解することが重要です。このプロパティは、MessageGroupStoreReaper タスクの次のスケジュールされた実行でのみチェックされるためです。例: タイムアウトが 10 分間に設定されているが、MessageGroupStoreReaper タスクが 1 時間ごとに実行されるようにスケジュールされ、MessageGroupStoreReaper タスクの最後の実行がタイムアウトの 1 分前に発生した場合、MessageGroup は次の 59 分間有効期限が切れません。そのため、レートを少なくともタイムアウトの値以下に設定することをお勧めします。

リーパーに加えて、アプリケーションが AbstractCorrelatingMessageHandler のライフサイクルコールバックを介してシャットダウンすると、有効期限コールバックが呼び出されます。

AbstractCorrelatingMessageHandler は独自の有効期限コールバックを登録します。これは、アグリゲーターの XML 構成内のブールフラグ send-partial-result-on-expiry とのリンクです。フラグが true に設定されている場合、有効期限コールバックが呼び出されると、まだリリースされていないグループ内のマークされていないメッセージを出力チャネルに送信できます。

共有 MessageStore が異なる相関エンドポイントに使用される場合、適切な CorrelationStrategy を構成して、グループ ID の一意性を確保する必要があります。そうしないと、ある相関エンドポイントが他の相関エンドポイントからメッセージを解放または期限切れにしたときに、予期しない動作が発生する可能性があります。同じ相関キーを持つメッセージは、同じメッセージグループに保存されます。

一部の MessageStore 実装では、データを分割することにより、同じ物理リソースを使用できます。例: JdbcMessageStore には region プロパティがあり、MongoDbMessageStore には collectionName プロパティがあります。

MessageStore インターフェースとその実装の詳細については、メッセージストアを参照してください。

Flux Aggregator

バージョン 5.2 では、FluxAggregatorMessageHandler コンポーネントが導入されました。Project Reactor Flux.groupBy() および Flux.window() オペレーターに基づいています。受信メッセージは、このコンポーネントのコンストラクターで Flux.create() によって開始された FluxSink に送信されます。outputChannel が提供されないか、ReactiveStreamsSubscribableChannel のインスタンスではない場合、メイン Flux へのサブスクリプションは Lifecycle.start() 実装から行われます。それ以外の場合は、ReactiveStreamsSubscribableChannel 実装によって行われたサブスクリプションに延期されます。メッセージは、グループキーに CorrelationStrategy を使用して Flux.groupBy() によってグループ化されます。デフォルトでは、メッセージの IntegrationMessageHeaderAccessor.CORRELATION_ID ヘッダーが調べられます。

デフォルトでは、閉じられたウィンドウはすべて、生成するメッセージのペイロードの Flux として解放されます。このメッセージには、ウィンドウの最初のメッセージのすべてのヘッダーが含まれています。出力メッセージペイロードのこの Flux は、ダウンストリームでサブスクライブおよび処理する必要があります。このようなロジックは、FluxAggregatorMessageHandler の setCombineFunction(Function<Flux<Message<?>>, Mono<Message<?>>>) 構成オプションによってカスタマイズ(または置き換え)できます。例: 最終メッセージに List のペイロードを含める場合、次のように Flux.collectList() を構成できます。

fluxAggregatorMessageHandler.setCombineFunction(
                (messageFlux) ->
                        messageFlux
                                .map(Message::getPayload)
                                .collectList()
                                .map(GenericMessage::new));

FluxAggregatorMessageHandler には、適切なウィンドウ戦略を選択するためのいくつかのオプションがあります。

  • setBoundaryTrigger(Predicate<Message<?>>) - Flux.windowUntil() オペレーターに伝搬されます。詳細については、JavaDocs を参照してください。他のすべてのウィンドウオプションよりも優先されます。

  • setWindowSize(int) および setWindowSizeFunction(Function<Message<?>, Integer>) - Flux.window(int) または windowTimeout(int, Duration) に伝搬されます。デフォルトでは、ウィンドウサイズはグループの最初のメッセージとその IntegrationMessageHeaderAccessor.SEQUENCE_SIZE ヘッダーから計算されます。

  • setWindowTimespan(Duration) - ウィンドウサイズの構成に応じて、Flux.window(Duration) または windowTimeout(int, Duration) に伝達されます。

  • setWindowConfigurer(Function<Flux<Message<?>>, Flux<Flux<Message<?>>>>) - 公開されたオプションでカバーされていないカスタムウィンドウ操作に対して、グループ化されたフラックスに変換を適用する関数。

このコンポーネントは MessageHandler 実装であるため、@ServiceActivator メッセージングアノテーションとともに @Bean 定義として単純に使用できます。Java DSL を使用すると、.handle() EIP メソッドから使用できます。以下のサンプルは、実行時に IntegrationFlow を登録する方法と、FluxAggregatorMessageHandler をアップストリームのスプリッターと相関させる方法を示しています。

IntegrationFlow fluxFlow =
        (flow) -> flow
                .split()
                .channel(MessageChannels.flux())
                .handle(new FluxAggregatorMessageHandler());

IntegrationFlowContext.IntegrationFlowRegistration registration =
        this.integrationFlowContext.registration(fluxFlow)
                .register();

@SuppressWarnings("unchecked")
Flux<Message<?>> window =
        registration.getMessagingTemplate()
                .convertSendAndReceive(new Integer[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, Flux.class);

リシーケンサー

リシーケンサーはアグリゲーターに関連していますが、異なる目的を果たします。アグリゲーターはメッセージを結合しますが、リシーケンサーはメッセージを変更せずに通過させます。

機能性

リシーケンサーは、CORRELATION_ID を使用してメッセージをグループに保存するという意味で、アグリゲーターと同様の方法で機能します。違いは、Resequencer がメッセージを処理しないことです。代わりに、SEQUENCE_NUMBER ヘッダー値の順に解放します。

それに関しては、すべてのメッセージを一度に(SEQUENCE_SIZE によるシーケンス全体の後、および他の可能性に従って)解放するか、有効なシーケンスが利用可能になったらすぐに解放するかを選択できます。(この章で後述する「有効なシーケンス」の意味を説明します。)

リシーケンサは、小さなギャップのある比較的短いメッセージシーケンスを再シーケンスすることを目的としています。多数のギャップのあるばらばらのシーケンスが多数ある場合、パフォーマンスの問題が発生する可能性があります。

リシーケンサーの構成

Java DSL でのリシーケンサーの構成については、アグリゲーターとリシーケンサーを参照してください。

リシーケンサを構成するには、XML に適切な要素を含めるだけで済みます。

次の例は、リシーケンサーの構成を示しています。

<int:channel id="inputChannel"/>

<int:channel id="outputChannel"/>

<int:resequencer id="completelyDefinedResequencer"  (1)
  input-channel="inputChannel"  (2)
  output-channel="outputChannel"  (3)
  discard-channel="discardChannel"  (4)
  release-partial-sequences="true"  (5)
  message-store="messageStore"  (6)
  send-partial-result-on-expiry="true"  (7)
  send-timeout="86420000"  (8)
  correlation-strategy="correlationStrategyBean"  (9)
  correlation-strategy-method="correlate"  (10)
  correlation-strategy-expression="headers['something']"  (11)
  release-strategy="releaseStrategyBean"  (12)
  release-strategy-method="release"  (13)
  release-strategy-expression="size() == 10"  (14)
  empty-group-min-timeout="60000"  (15)

  lock-registry="lockRegistry"  (16)

  group-timeout="60000"  (17)
  group-timeout-expression="size() ge 2 ? 100 : -1"  (18)
  scheduler="taskScheduler" />  (19)
  expire-group-upon-timeout="false" />  (20)
1 リシーケンサーの ID はオプションです。
2 リシーケンサーの入力チャンネル。必須。
3 リシーケンサーが並べ替えたメッセージを送信するチャネル。オプション。
4 再シーケンサーがタイムアウトしたメッセージを送信するチャネル(send-partial-result-on-timeout が false に設定されている場合)。オプション。
5 順序付けられたシーケンスが利用可能になったらすぐに送信するか、メッセージグループ全体が到着した後にのみ送信するか。オプション。(デフォルトは false です。)
6 メッセージのグループが完了するまで相関キーに保存するために使用できる MessageGroupStore への参照。オプション。(デフォルトは、揮発性のメモリ内ストアです。)
7 グループの有効期限が切れたときに、順序付きグループを送信するかどうか(メッセージの一部が欠落している場合でも)。オプション。(デフォルトは false です)アグリゲーターでの状態の管理: MessageGroupStore を参照してください。
8 応答 Message を output-channel または discard-channel に送信するときに待機するタイムアウト間隔。デフォルトは -1 で、無期限にブロックします。固定の「容量」を持つ QueueChannel など、出力チャネルに「送信」制限がある場合にのみ適用されます。この場合、MessageDeliveryException がスローされます。send-timeout は、AbstractSubscribableChannel 実装では無視されます。group-timeout(-expression) の場合、スケジュールされた期限切れタスクの MessageDeliveryException により、このタスクが再スケジュールされます。オプション。
9 メッセージ相関(グループ化)アルゴリズムを実装する Bean への参照。Bean は、CorrelationStrategy インターフェースまたは POJO の実装にすることができます。後者の場合、correlation-strategy-method 属性も定義する必要があります。オプション。(デフォルトでは、アグリゲーターは IntegrationMessageHeaderAccessor.CORRELATION_ID ヘッダーを使用します。)
10correlation-strategy によって参照される Bean で定義され、相関決定アルゴリズムを実装するメソッド。オプション、制限あり(correlation-strategy が存在する必要があります)。
11 相関戦略を表す SpEL 式。例: "headers['something']" correlation-strategy または correlation-strategy-expression のいずれかのみが許可されます。
12 リリース戦略を実装する Bean への参照。Bean は、ReleaseStrategy インターフェースまたは POJO の実装にすることができます。後者の場合、release-strategy-method 属性も定義する必要があります。オプション(デフォルトでは、アグリゲーターは IntegrationMessageHeaderAccessor.SEQUENCE_SIZE ヘッダー属性を使用します)。
13release-strategy によって参照される Bean で定義され、完了決定アルゴリズムを実装するメソッド。オプション、制限あり(release-strategy が存在する必要があります)。
14 リリース戦略を表す SpEL 式。式のルートオブジェクトは MessageGroup です。例: "size() == 5" release-strategy または release-strategy-expression のいずれかのみが許可されます。
15MessageGroupStoreReaper が <resequencer> MessageStore 用に構成されている場合にのみ適用されます。デフォルトでは、MessageGroupStoreReaper が部分グループを期限切れにするように構成されている場合、空のグループも削除されます。グループが正常に解放された後、空のグループが存在します。これは、遅れて到着したメッセージの検出と破棄を可能にするためです。部分的なグループを期限切れにするよりも長いスケジュールで空のグループを期限切れにする場合は、このプロパティを設定します。空のグループは、少なくともこのミリ秒数の間変更されない限り、MessageStore から削除されません。空のグループが期限切れになる実際の時間は、リーパーのタイムアウトプロパティの影響も受け、この値にタイムアウトを加えた時間になる可能性があることに注意してください。
16XML を使用したアグリゲーターの構成を参照してください。
17XML を使用したアグリゲーターの構成を参照してください。
18XML を使用したアグリゲーターの構成を参照してください。
19XML を使用したアグリゲーターの構成を参照してください。
20 デフォルトでは、タイムアウトにより(または MessageGroupStoreReaper により)グループが完了すると、空のグループのメタデータが保持されます。到着したメッセージはすぐに破棄されます。これを true に設定して、グループを完全に削除します。その後、遅れて到着したメッセージは新しいグループを開始し、グループが再びタイムアウトになるまで破棄されません。タイムアウトの原因となったシーケンス範囲の「ホール」のため、新しいグループは通常解放されません。空のグループは、empty-group-min-timeout 属性と一緒に MessageGroupStoreReaper を使用することにより、後で期限切れにする(完全に削除する)ことができます。バージョン 5.0 以降、空のグループも empty-group-min-timeout の経過後に削除されるようにスケジュールされています。デフォルトは "false" です。
再シーケンサーの Java クラスに実装されるカスタム動作はないため、アノテーションのサポートはありません。

メッセージハンドラーチェーン

MessageHandlerChain は MessageHandler の実装であり、フィルター、トランスフォーマー、スプリッターなどの他のハンドラーのチェーンに実際に委譲しながら、単一のメッセージエンドポイントとして構成できます。複数のハンドラーを固定された線形進行で接続する必要がある場合、これにより構成がより単純になります。例: 他のコンポーネントの前にトランスを提供することはかなり一般的です。同様に、チェーンの他のコンポーネントの前にフィルターを提供する場合、基本的に選択的コンシューマー (英語) を作成します。いずれの場合も、チェーンは単一の input-channel と単一の output-channel のみを必要とし、個々のコンポーネントごとにチャネルを定義する必要がなくなります。

Spring Integration の Filter は、ブールプロパティ throwExceptionOnRejection を提供します。同じポイントツーポイントチャネルで複数の選択的コンシューマーを異なる受け入れ条件で提供する場合、この値を "true" に設定する必要があります(デフォルトは false)。これにより、ディスパッチャーはメッセージが拒否されたことを認識し、その結果、メッセージを他のサブスクライバーに渡そうとします。例外がスローされなかった場合、ディスパッチャーには、それ以上の処理を防ぐためにフィルターがメッセージをドロップしたにもかかわらず、メッセージが正常に渡されたように見えます。実際にメッセージを「ドロップ」したい場合は、フィルターの "discard-channel" が役立つ場合があります。これは、ドロップされたメッセージに対して何らかの操作(JMS キューへの送信や書き込みなど)を実行する機会を与えるためです。ログへ)。

ハンドラーチェーンは、コンポーネント間の同程度の疎結合を内部的に維持しながら構成を簡素化します。ある時点で非線形配置が必要な場合、構成を変更するのは簡単です。

内部的には、チェーンは匿名チャネルで区切られたリストされたエンドポイントの線形セットアップに拡張されます。応答チャネルヘッダーは、チェーン内では考慮されません。最後のハンドラーが呼び出された後にのみ、結果のメッセージが応答チャネルまたはチェーンの出力チャネルに転送されます。このセットアップのため、最後を除くすべてのハンドラーは MessageProducer インターフェース('setOutputChannel()' メソッドを提供)を実装する必要があります。MessageHandlerChain の outputChannel が設定されている場合、最後のハンドラーには出力チャンネルのみが必要です。

他のエンドポイントと同様に、output-channel はオプションです。チェーンの最後に応答メッセージがある場合、出力チャネルが優先されます。ただし、使用できない場合、チェーンハンドラーは、フォールバックとして受信メッセージの応答チャネルヘッダーをチェックします。

ほとんどの場合、MessageHandler を自分で実装する必要はありません。次のセクションでは、チェーン要素のネームスペースサポートに焦点を当てます。サービスアクティベーターやトランスフォーマーなど、ほとんどの Spring Integration エンドポイントは、MessageHandlerChain 内での使用に適しています。

チェーンの構成

<chain> 要素は、input-channel 属性を提供します。チェーンの最後の要素が応答メッセージを生成できる場合(オプション)、output-channel 属性もサポートします。サブ要素は、フィルター、トランスフォーマー、スプリッター、サービスアクティベーターです。最後の要素は、ルーターまたは送信チャネルアダプターの場合もあります。次の例は、チェーン定義を示しています。

<int:chain input-channel="input" output-channel="output">
    <int:filter ref="someSelector" throw-exception-on-rejection="true"/>
    <int:header-enricher>
        <int:header name="thing1" value="thing2"/>
    </int:header-enricher>
    <int:service-activator ref="someService" method="someMethod"/>
</int:chain>

上記の例で使用されている <header-enricher> 要素は、thing1 という名前のメッセージヘッダーにメッセージの値 thing2 を設定します。ヘッダーエンリッチャーは、ヘッダー値のみに触れる Transformer の特殊化です。ヘッダーを変更する MessageHandler を実装し、それを Bean として接続することで同じ結果を得ることができますが、ヘッダーエンリッチャーはより簡単なオプションです。

<chain> は、メッセージフローの最後の「ブラックボックス」コンシューマーとして構成できます。このソリューションでは、次の例に示すように、< チェーン> の最後に < 送信チャネルアダプター> を配置できます。

<int:chain input-channel="input">
    <int-xml:marshalling-transformer marshaller="marshaller" result-type="StringResult" />
    <int:service-activator ref="someService" method="someMethod"/>
    <int:header-enricher>
        <int:header name="thing1" value="thing2"/>
    </int:header-enricher>
    <int:logging-channel-adapter level="INFO" log-full-message="true"/>
</int:chain>
許可されていない属性と要素

order や input-channel などの特定の属性は、チェーン内で使用されるコンポーネントで指定することはできません。同じことが poller サブ要素にも当てはまります。

Spring Integration コアコンポーネントの場合、XML スキーマ自体がこれらの制約の一部を実施します。ただし、非コアコンポーネントまたは独自のカスタムコンポーネントの場合、これらの制約は XML スキーマではなく、XML 名前空間パーサーによって適用されます。

これらの XML 名前空間パーサー制約は Spring Integration 2.2 で追加されました。許可されていない属性と要素を使用しようとすると、XML 名前空間パーサーは BeanDefinitionParsingException をスローします。

"id" 属性の使用

Spring Integration 3.0 以降、チェーン要素に id 属性が指定されている場合、要素の Bean 名は、チェーンの id と要素自体の id の組み合わせです。id 属性のない要素は Bean として登録されませんが、それぞれにチェーン id を含む componentName が与えられます。次の例について考えてみます。

<int:chain id="somethingChain" input-channel="input">
    <int:service-activator id="somethingService" ref="someService" method="someMethod"/>
    <int:object-to-json-transformer/>
</int:chain>

前の例では:

  • <chain> ルート要素には 'somethingChain' の id があります。その結果、AbstractEndpoint 実装 (input-channel 型に応じて PollingConsumer または EventDrivenConsumer) Bean は、この値を Bean 名として取得します。

  • MessageHandlerChain Bean は Bean エイリアス ('somethingChain.handler' ) を取得し、これにより BeanFactory からこの Bean に直接アクセスできるようになります。

  • <service-activator> は完全なメッセージングエンドポイントではありません(PollingConsumer または EventDrivenConsumer ではありません)。<chain> 内の MessageHandler です。この場合、BeanFactory に登録されている Bean 名は "somethingChain$child.somethingService.handler" です。

  • この ServiceActivatingHandler の componentName は同じ値を取りますが、接尾辞 ".handler" はありません。"somethingChain$child.somethingService" になります。

  • 最後の <chain> サブコンポーネント <object-to-json-transformer> には、id 属性がありません。componentName は、<chain> での位置に基づいています。この場合、"somethingChain$child#1" です。(名前の最後の要素は、"#0" で始まるチェーン内の順序です)。このトランスフォーマーは、アプリケーションコンテキスト内で Bean として登録されていないため、beanName を取得しないことに注意してください。ただし、その componentName には、ロギングやその他の目的に役立つ値があります。

<chain> 要素の id 属性により、JMX エクスポートに適格になり、メッセージ履歴で追跡可能になります。前述のように、適切な Bean 名を使用して、BeanFactory からそれらにアクセスできます。

<chain> 要素に明示的な id 属性を提供して、ログ内のサブコンポーネントの識別を簡素化し、BeanFactory などからそれらへのアクセスを提供すると便利です。

チェーン内からチェーンを呼び出す

場合によっては、チェーン内から別のチェーンにネストされた呼び出しを行ってから、元のチェーン内で戻って実行を継続する必要があります。これを実現するには、次の例に示すように、<gateway> 要素を含めることでメッセージングゲートウェイを使用できます。

<int:chain id="main-chain" input-channel="in" output-channel="out">
    <int:header-enricher>
      <int:header name="name" value="Many" />
    </int:header-enricher>
    <int:service-activator>
      <bean class="org.foo.SampleService" />
    </int:service-activator>
    <int:gateway request-channel="inputA"/>  
</int:chain>

<int:chain id="nested-chain-a" input-channel="inputA">
    <int:header-enricher>
        <int:header name="name" value="Moe" />
    </int:header-enricher>
    <int:gateway request-channel="inputB"/> 
    <int:service-activator>
        <bean class="org.foo.SampleService" />
    </int:service-activator>
</int:chain>

<int:chain id="nested-chain-b" input-channel="inputB">
    <int:header-enricher>
        <int:header name="name" value="Jack" />
    </int:header-enricher>
    <int:service-activator>
        <bean class="org.foo.SampleService" />
    </int:service-activator>
</int:chain>

上記の例では、nested-chain-a は、そこに構成された 'gateway' 要素によって main-chain 処理の終わりに呼び出されます。nested-chain-a では、ヘッダーの強化後に nested-chain-b の呼び出しが行われます。その後、フローは戻って nested-chain-b で実行を終了します。最後に、フローは main-chain に戻ります。<gateway> 要素のネストされたバージョンがチェーンで定義されている場合、service-interface 属性は必要ありません。代わりに、現在の状態のメッセージを取得し、request-channel 属性で定義されたチャネルに配置します。そのゲートウェイによって開始されたダウンストリームフローが完了すると、Message がゲートウェイに返され、現在のチェーン内でその旅を続けます。

スキャッターギャザー

バージョン 4.1 から、Spring Integration はスキャッターギャザー (英語) エンタープライズ統合パターンの実装を提供します。これは、メッセージを受信者に送信し、結果を集約することを目的とする複合エンドポイントです。エンタープライズ統合パターン (英語) に記載されているように、「最良の見積もり」などのシナリオのコンポーネントであり、複数のサプライヤーに情報をリクエストし、リクエストされたアイテムに最適な用語を提供するサプライヤーを決定する必要があります。

以前は、個別のコンポーネントを使用してパターンを構成できました。この機能強化により、より便利な構成が実現します。

ScatterGatherHandler は、PublishSubscribeChannel (または RecipientListRouter)と AggregatingMessageHandler を組み合わせたリクエスト / 応答エンドポイントです。リクエストメッセージは scatter チャネルに送信され、ScatterGatherHandler はアグリゲーターが outputChannel に送信する応答を待ちます。

機能性

Scatter-Gather パターンは、「オークション」と「配信」の 2 つのシナリオを提案しています。どちらの場合も、aggregation 機能は同じであり、AggregatingMessageHandler で利用可能なすべてのオプションを提供します。(実際には、ScatterGatherHandler はコンストラクター引数として AggregatingMessageHandler のみを必要とします)詳細については、アグリゲーターを参照してください。

競売

オークション Scatter-Gather バリアントは、リクエストメッセージに「パブリッシュ / サブスクライブ」ロジックを使用します。ここで、「スキャッター」チャネルは apply-sequence="true" の PublishSubscribeChannel です。ただし、このチャネルは任意の MessageChannel 実装にすることができます(ContentEnricher の request-channel の場合のように - コンテンツエンリッチャーを参照)。ただし、この場合、aggregation 関数用に独自のカスタム correlationStrategy を作成する必要があります。

分布

配布 Scatter-Gather バリアントは、RecipientListRouter (RecipientListRouter を参照)に基づいており、RecipientListRouter で使用可能なすべてのオプションがあります。これは、2 番目の ScatterGatherHandler コンストラクター引数です。recipient-list-router および aggregator のデフォルトの correlationStrategy のみに依存する場合は、apply-sequence="true" を指定する必要があります。それ以外の場合は、aggregator 用のカスタム correlationStrategy を提供する必要があります。PublishSubscribeChannel バリアント(オークションバリアント)とは異なり、recipient-list-router selector オプションを使用すると、メッセージに基づいてターゲットサプライヤーをフィルタリングできます。apply-sequence="true" では、デフォルトの sequenceSize が提供され、aggregator はグループを正しく解放できます。配布オプションは、オークションオプションと相互に排他的です。

オークションとディストリビューションの両方のバリエーションで、リクエスト(スキャター)メッセージは gatherResultChannel ヘッダーで強化され、aggregator からの応答メッセージを待機します。

デフォルトでは、すべてのサプライヤーは結果を replyChannel ヘッダーに送信する必要があります(通常、最終エンドポイントから output-channel を省略します)。ただし、gatherChannel オプションも提供されているため、サプライヤーは集約のためにそのチャネルに応答を送信できます。

スキャッターギャザーエンドポイントの構成

次の例は、Scatter-Gather の Bean 定義の Java 構成を示しています。

@Bean
public MessageHandler distributor() {
    RecipientListRouter router = new RecipientListRouter();
    router.setApplySequence(true);
    router.setChannels(Arrays.asList(distributionChannel1(), distributionChannel2(),
            distributionChannel3()));
    return router;
}

@Bean
public MessageHandler gatherer() {
	return new AggregatingMessageHandler(
			new ExpressionEvaluatingMessageGroupProcessor("^[payload gt 5] ?: -1D"),
			new SimpleMessageStore(),
			new HeaderAttributeCorrelationStrategy(
			       IntegrationMessageHeaderAccessor.CORRELATION_ID),
			new ExpressionEvaluatingReleaseStrategy("size() == 2"));
}

@Bean
@ServiceActivator(inputChannel = "distributionChannel")
public MessageHandler scatterGatherDistribution() {
	ScatterGatherHandler handler = new ScatterGatherHandler(distributor(), gatherer());
	handler.setOutputChannel(output());
	return handler;
}

前の例では、applySequence="true" と受信者チャネルのリストを使用して RecipientListRouter distributor Bean を構成します。次の Bean は AggregatingMessageHandler 用です。最後に、これらの Bean を両方とも ScatterGatherHandler Bean 定義に挿入し、それを @ServiceActivator としてマークして、スキャッターギャザーコンポーネントを統合フローにワイヤリングします。

次の例は、XML 名前空間を使用して <scatter-gather> エンドポイントを構成する方法を示しています。

<scatter-gather
		id=""  (1)
		auto-startup=""  (2)
		input-channel=""  (3)
		output-channel=""  (4)
		scatter-channel=""  (5)
		gather-channel=""  (6)
		order=""  (7)
		phase=""  (8)
		send-timeout=""  (9)
		gather-timeout=""  (10)
		requires-reply="" > (11)
			<scatterer/>  (12)
			<gatherer/>  (13)
</scatter-gather>
1 エンドポイントの ID。ScatterGatherHandler Bean は、エイリアス id + '.handler' で登録されています。RecipientListRouter Bean は、エイリアス id + '.scatterer' で登録されています。AggregatingMessageHandler`bean is registered with an alias of `id + '.gatherer' オプション。(BeanFactory は、デフォルトの id 値を生成します。)
2 アプリケーションコンテキストの初期化中にエンドポイントを起動する必要があるかどうかを示すライフサイクル属性。さらに、ScatterGatherHandler は Lifecycle も実装し、gather-channel が提供された場合に内部で作成される gatherEndpoint を開始および停止します。オプション。(デフォルトは true です。)
3ScatterGatherHandler で処理するリクエストメッセージを受信するチャネル。必須。
4ScatterGatherHandler が集約結果を送信するチャネル。オプション。(受信メッセージは、replyChannel メッセージヘッダーで応答チャネル自体を指定できます)。
5 オークションシナリオの散布メッセージの送信先のチャネル。オプション。<scatterer> サブ要素と相互に排他的。
6 集計に対する各サプライヤーからの返信を受信するチャネル。スキャッタメッセージの replyChannel ヘッダーとして使用されます。オプション。デフォルトでは、FixedSubscriberChannel が作成されます。
7 複数のハンドラーが同じ DirectChannel にサブスクライブされる場合のこのコンポーネントの順序(負荷分散の目的で使用)。オプション。
8 エンドポイントを開始および停止するフェーズを指定します。起動順序は最低から最高に進み、シャットダウン順序は最高から最低になります。デフォルトでは、この値は Integer.MAX_VALUE です。これは、このコンテナーが可能な限り遅く起動し、できるだけ早く停止することを意味します。オプション。
9 応答 Message を output-channel に送信するときに待機するタイムアウト間隔。デフォルトでは、送信は 1 秒間ブロックされます。出力チャネルにいくつかの「送信」制限がある場合にのみ適用されます。たとえば、満杯の固定「容量」を持つ QueueChannel などです。この場合、MessageDeliveryException がスローされます。AbstractSubscribableChannel 実装では、send-timeout は無視されます。group-timeout(-expression) の場合、スケジュールされた期限切れタスクの MessageDeliveryException により、このタスクが再スケジュールされます。オプション。
10scatter-gather が応答メッセージを待ってから戻るまでの時間を指定できます。デフォルトでは、無期限に待機します。応答がタイムアウトになると、"null" が返されます。オプション。デフォルトは -1 で、無期限に待機することを意味します。
11scatter-gather が null 以外の値を返す必要があるかどうかを指定します。この値は、デフォルトでは true です。そのため、基になるアグリゲーターが gather-timeout の後に NULL 値を返すと、ReplyRequiredException がスローされます。null が可能性がある場合、無期限の待機を避けるために gather-timeout を指定する必要があります。
12<recipient-list-router> オプション。オプション。scatter-channel 属性と相互に排他的。
13<aggregator> オプション。必須。

エラー処理

Scatter-Gather は複数のリクエスト / 応答コンポーネントであるため、エラー処理にはさらに複雑さが伴います。場合によっては、ReleaseStrategy がプロセスがリクエストよりも少ない応答で終了することを許可する場合、ダウンストリームの例外をキャッチして無視する方が良い場合があります。その他の場合、エラーが発生したときにサブフローから戻るために「カバレッジメッセージ」のようなものを考慮する必要があります。

すべての非同期サブフローは、MessagePublishingErrorHandler から送信される適切なエラーメッセージの errorChannel ヘッダーで構成する必要があります。そうでない場合、共通のエラー処理ロジックを使用して、エラーがグローバル errorChannel に送信されます。非同期エラー処理の詳細については、エラー処理を参照してください。

同期フローは、ExpressionEvaluatingRequestHandlerAdvice を使用して例外を無視したり、カバレッジメッセージを返したりする場合があります。サブフローの 1 つから ScatterGatherHandler に例外がスローされると、上流に再スローされます。このようにして、他のすべてのサブフローは何もせずに機能し、その応答は ScatterGatherHandler で無視されます。これは予期される動作である場合がありますが、ほとんどの場合、他のすべておよび Gatherer の期待に影響を与えることなく、特定のサブフローのエラーを処理する方が適切です。

バージョン 5.1.3 以降、ScatterGatherHandler には errorChannelName オプションが付属しています。スキャッタメッセージの errorChannel ヘッダーに入力され、非同期エラーが発生した場合に使用されるか、エラーメッセージを直接送信するための通常の同期サブフローで使用できます。

以下のサンプル構成は、補正メッセージを返すことによる非同期エラー処理を示しています。

@Bean
public IntegrationFlow scatterGatherAndExecutorChannelSubFlow(TaskExecutor taskExecutor) {
    return f -> f
            .scatterGather(
                    scatterer -> scatterer
                            .applySequence(true)
                            .recipientFlow(f1 -> f1.transform(p -> "Sub-flow#1"))
                            .recipientFlow(f2 -> f2
                                    .channel(c -> c.executor(taskExecutor))
                                    .transform(p -> {
                                        throw new RuntimeException("Sub-flow#2");
                                    })),
                    null,
                    s -> s.errorChannel("scatterGatherErrorChannel"));
}

@ServiceActivator(inputChannel = "scatterGatherErrorChannel")
public Message<?> processAsyncScatterError(MessagingException payload) {
    return MessageBuilder.withPayload(payload.getCause().getCause())
            .copyHeaders(payload.getFailedMessage().getHeaders())
            .build();
}

適切な応答を生成するには、MessagePublishingErrorHandler によって scatterGatherErrorChannel に送信された MessagingException の failedMessage からヘッダー(replyChannel および errorChannel を含む)をコピーする必要があります。このようにして、応答メッセージグループの完了のために、ターゲット例外が ScatterGatherHandler の Gatherer に返されます。このような例外 payload は、Gatherer の MessageGroupProcessor でフィルターで除外するか、スキャッター / ギャザーエンドポイントの後にダウンストリームで処理できます。

散乱結果を収集者に送信する前に、ScatterGatherHandler は、応答チャネルとエラーチャネル(存在する場合)を含むリクエストメッセージヘッダーを復元します。このようにして、スキャッター受信者サブフローに非同期ハンドオフが適用されている場合でも、AggregatingMessageHandler からのエラーが呼び出し元に伝播されます。操作を成功させるには、gatherResultChanneloriginalReplyChanneloriginalErrorChannel ヘッダーを転送して、スキャッター受信者サブフローからの応答に戻す必要があります。この場合、合理的で有限の gatherTimeout を ScatterGatherHandler 用に構成する必要があります。それ以外の場合は、デフォルトで、収集者からの応答を永久に待ってブロックされます。

スレッドバリア

他の非同期イベントが発生するまで、メッセージフロースレッドを中断する必要がある場合があります。例: メッセージを RabbitMQ に発行する HTTP リクエストを検討します。RabbitMQ ブローカーがメッセージを受信したという確認を発行するまで、ユーザーに返信したくない場合があります。

バージョン 4.2 では、Spring Integration はこの目的のために <barrier/> コンポーネントを導入しました。基になる MessageHandler は BarrierMessageHandler です。このクラスは MessageTriggerAction も実装します。trigger() メソッドに渡されたメッセージは、handleRequestMessage() メソッドの対応するスレッドを解放します(存在する場合)。

中断されたスレッドとトリガースレッドは、メッセージで CorrelationStrategy を呼び出すことにより関連付けられます。メッセージが input-channel に送信されると、スレッドは最大 timeout ミリ秒中断され、対応するトリガーメッセージを待機します。デフォルトの相関戦略では、IntegrationMessageHeaderAccessor.CORRELATION_ID ヘッダーを使用します。トリガーメッセージが同じ相関で到着すると、スレッドは解放されます。リリース後に output-channel に送信されるメッセージは、MessageGroupProcessor を使用して構築されます。デフォルトでは、メッセージは 2 つのペイロードの Collection<?> であり、ヘッダーは DefaultAggregatingMessageGroupProcessor を使用してマージされます。

trigger() メソッドが最初に呼び出された場合(またはメインスレッドがタイムアウトした後)、中断メッセージが到着するのを待つ timeout まで待機します。トリガースレッドを一時停止したくない場合は、代わりに TaskExecutor にハンドオフして、そのスレッドを一時停止することを検討してください。

requires-reply プロパティは、トリガーメッセージが到着する前に中断されたスレッドがタイムアウトした場合に実行するアクションを決定します。デフォルトでは、false です。これは、エンドポイントが null を返し、フローが終了し、スレッドが呼び出し元に戻ることを意味します。true の場合、ReplyRequiredException がスローされます。

trigger() メソッドをプログラムで呼び出すことができます(名前 barrier.handler を使用して Bean 参照を取得します。barrier はバリアエンドポイントの Bean 名です)。または、<outbound-channel-adapter/> を設定してリリースをトリガーできます。

同じ相関関係で中断できるスレッドは 1 つだけです。同じ相関を複数回使用できますが、同時に 1 回しか使用できません。2 番目のスレッドが同じ相関で到着すると、例外がスローされます。

次の例は、相関にカスタムヘッダーを使用する方法を示しています。

<int:barrier id="barrier1" input-channel="in" output-channel="out"
        correlation-strategy-expression="headers['myHeader']"
        output-processor="myOutputProcessor"
        discard-channel="lateTriggerChannel"
        timeout="10000">
</int:barrier>

<int:outbound-channel-adapter channel="release" ref="barrier1.handler" method="trigger" />

どちらが先にメッセージを受信したかによって、in にメッセージを送信するスレッドまたは release にメッセージを送信するスレッドは、他のメッセージが到着するまで最大 10 秒間待機します。メッセージが解放されると、out チャネルには、myOutputProcessor という名前のカスタム MessageGroupProcessor Bean を呼び出した結果を組み合わせたメッセージが送信されます。メインスレッドがタイムアウトし、トリガーが後で到着した場合、遅延トリガーが送信される破棄チャネルを構成できます。次の例は、そのための Java 構成を示しています。

@Configuration
@EnableIntegration
public class Config {

    @ServiceActivator(inputChannel="in")
    @Bean
    public BarrierMessageHandler barrier() {
        BarrierMessageHandler barrier = new BarrierMessageHandler(10000);
        barrier.setOutputChannel(out());
        barrier.setDiscardChannel(lateTriggers());
        return barrier;
    }

    @ServiceActivator (inputChannel="release")
    @Bean
    public MessageHandler releaser() {
        return new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                barrier().trigger(message);
            }

        };
    }

}

このコンポーネントの例については、バリアサンプルアプリケーション [GitHub] (英語) を参照してください。