ルーターの実装

コンテンツベースのルーティングにはドメイン固有のロジックが必要になることが多いため、ほとんどのユースケースでは、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 IntegrationFlow.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 IntegrationFlow.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 にルーティングされます。

次の例は、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 IntegrationFlow.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 IntegrationFlow.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 IntegrationFlow.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 式が評価され、この受信者を特定の入力メッセージの受信者リストに含める必要があるかどうかが判断されます。式の評価結果は boolean でなければなりません。この属性が定義されていない場合、チャネルは常に受信者のリストに含まれます。

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"/>
Message<?> addRecipientCommandMessage =
                     MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
                            .setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel2"))
                            .build();

アプリケーションから 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 のサンプル構成を示しています。

  • Java DSL

  • Kotlin DSL

  • Groovy DSL

  • XML DSL

@Bean
public IntegrationFlow someFlow() {
    return f -> f
            .routeByException(r -> r
                 .channelMapping(IllegalArgumentException.class, "illegalChannel")
                 .channelMapping(NullPointerException.class, "npeChannel")
                 .defaultOutputChannel("defaultChannel"));
}
@Bean
fun someFlow() =
    integrationFlow {
        routeByException {
                    channelMapping(IllegalArgumentException::class.java, "illegalChannel")
                    channelMapping(NullPointerException::class.java, "npeChannel")
                    defaultOutputChannel("defaultChannel")
                }
    }
@Bean
someFlow() {
    integrationFlow {
        routeByException {
            channelMapping IllegalArgumentException, 'illegalChannel'
            channelMapping NullPointerException, 'npeChannel'
            defaultOutputChannel 'defaultChannel'
        }
    }
}
<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" />