ルーター
このセクションでは、ルーターの動作について説明します。次のトピックが含まれます。
概要
ルーターは、多くのメッセージングアーキテクチャで重要な要素です。メッセージチャネルからメッセージを消費し、一連の条件に応じて、消費した各メッセージを 1 つ以上の異なるメッセージチャネルに転送します。
Spring Integration は、次のルーターを提供します。
ルーターの実装は多くの構成パラメーターを共有します。ただし、ルーター間には一定の違いがあります。さらに、構成パラメーターの可用性は、ルーターがチェーンの内部で使用されるか外部で使用されるかによって異なります。簡単な概要を提供するために、使用可能なすべての属性を次の 2 つの表にリストします。
次の表は、チェーンの外部のルーターで使用可能な構成パラメーターを示しています。
属性 | ルーター | ヘッダー値ルーター | xpath ルーター | ペイロード型ルーター | 受信者リストルート | 例外型ルーター |
---|---|---|---|---|---|---|
apply-sequence | ||||||
default-output-channel | ||||||
resolution-required | ||||||
ignore-send-failures | ||||||
timeout | ||||||
id | ||||||
auto-startup | ||||||
input-channel | ||||||
order | ||||||
method | ||||||
ref | ||||||
expression | ||||||
header-name | ||||||
evaluate-as-string | ||||||
xpath-expression-ref | ||||||
converter |
次の表は、チェーン内部のルーターで使用可能な構成パラメーターを示しています。
属性 | ルーター | ヘッダー値ルーター | xpath ルーター | ペイロード型ルーター | 受信者リストルーター | 例外型ルーター |
---|---|---|---|---|---|---|
apply-sequence | ||||||
default-output-channel | ||||||
resolution-required | ||||||
ignore-send-failures | ||||||
timeout | ||||||
id | ||||||
auto-startup | ||||||
input-channel | ||||||
order | ||||||
method | ||||||
ref | ||||||
expression | ||||||
header-name | ||||||
evaluate-as-string | ||||||
xpath-expression-ref | ||||||
converter |
Spring Integration 2.1 の時点で、すべてのルーターの実装でルーターパラメーターがより標準化されました。その結果、いくつかの小さな変更により、古い Spring Integration ベースのアプリケーションが壊れる可能性があります。 Spring Integration 2.1 以降、動作を これらの変更の前は、 メッセージを静かにドロップしたい場合は、 |
共通のルーターパラメーター
このセクションでは、すべてのルーターパラメーターに共通のパラメーターについて説明します(この章で前述した 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 以降、チャネル解決の動作はより明確になりました。例: 基本的に、デフォルトでは、ルーターはメッセージを少なくとも 1 つのチャネルに正常にルーティングできる必要があります。メッセージを本当にドロップしたい場合は、 |
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 が動的ルーティングをサポートする方法の詳細に入る前に、ルーターの典型的なフローを考慮する必要があります。
チャネル識別子を計算します。これは、ルーターがメッセージを受信すると計算される値です。通常、これは String または実際の
MessageChannel
のインスタンスです。チャネル識別子をチャネル名に解決します。このセクションの後半で、このプロセスの詳細を説明します。
チャネル名を実際の
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 つの手順は次のように実現されます。
ペイロード型の完全修飾名(たとえば、
java.lang.String
)であるチャネル識別子を計算します。チャネル識別子をチャネル名に解決します。この場合、前の手順の結果を使用して、
mapping
要素で定義されたペイロード型マッピングから適切な値を選択します。前のステップの結果で識別されたアプリケーションコンテキスト(できれば
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 つのステップがどのように機能するかを検討できます。
header-name
属性によって識別されるヘッダーの値であるチャネル識別子を計算します。チャネル識別子 a をチャネル名に解決します。ここで、前の手順の結果を使用して、
mapping
要素で定義された一般的なマッピングから適切な値を選択します。前のステップの結果で識別されたアプリケーションコンテキスト(できれば
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)));
}