ルーター

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

概要

ルーターは、多くのメッセージングアーキテクチャで重要な要素です。メッセージチャネルからメッセージを消費し、一連の条件に応じて、消費した各メッセージを 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")
            .route(Message.class, 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 式言語の詳細については、「 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 に解決される可能性があるため、式ベースのルーターを含む他のすべてのルーターに適用されます。

channelMapping は AbstractMappingMessageRouter レベルで定義されているため、AbstractMappingMessageRouter のサブクラスであるルーター(ほとんどのフレームワーク定義のルーターを含む)は動的ルーターです。そのマップの setter メソッドは、"setChannelMapping" および "removeChannelMapping" メソッドとともに public メソッドとして公開されます。これらを使用すると、ルーター自体への参照がある限り、実行時にルーターマッピングを変更、追加、削除できます。また、これらの同じ構成オプションを 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)));
}