Spring Integration サンプル

Spring Integration 2.0 以降、Spring Integration ディストリビューションにはサンプルが含まれなくなりました。代わりに、より良いコミュニティ参加と、理想的にはより多くの貢献を促進する、はるかに単純なコラボレーションモデルに切り替えました。サンプルに専用の GitHub リポジトリが追加されました。サンプル開発にも独自のライフサイクルがあり、フレームワークリリースのライフサイクルに依存しませんが、互換性の理由から、リポジトリには引き続き各メジャーリリースのタグが付けられています。

コミュニティにとっての大きなメリットは、サンプルをさらに追加して、次のリリースを待たずにすぐに利用できるようになったということです。実際のフレームワークに束縛されない独自の GitHub リポジトリを持つことも大きな利点です。サンプルを提案したり、既存のサンプルの課題を報告したりできる専用の場所ができました。サンプルをプルリクエストとして送信することもできます。あなたのサンプルが付加価値をもたらすと思われる場合、喜んでそれを「サンプル」リポジトリに追加し、作成者であると適切にクレジットします。

サンプルの入手先

Spring Integration サンプルプロジェクトは GitHub (英語) でホストされています。サンプルをチェックアウトまたはクローンするには、システムに Git クライアントがインストールされている必要があります。多くのプラットフォームで利用できる GUI ベースの製品がいくつかあります (Eclipse IDE の EGit (英語) など)。簡単な Google 検索で見つけることができます。Git (英語) のコマンドラインインターフェースを使用することもできます。

Git をインストールまたは使用する方法の詳細が必要な場合は、https://git-scm.com/ (英語) にアクセスしてください。

Git コマンドラインツールを使用して Spring Integration サンプルリポジトリを複製(チェックアウト)するには、次のコマンドを発行します。

$ git clone https://github.com/spring-projects/spring-integration-samples.git

上記のコマンドは、サンプルリポジトリ全体を、git コマンドを発行した作業ディレクトリ内の spring-integration-samples という名前のディレクトリに複製します。サンプルリポジトリはライブリポジトリであるため、定期的なプル(更新)を実行して、新しいサンプルと既存のサンプルの更新を取得することができます。そのためには、次の git pull コマンドを発行します。

$ git pull

サンプルまたはサンプルリクエストの送信

新しいサンプルとサンプルのリクエストの両方を送信できます。良いアイデアの共有など、サンプルの改善に向けたあらゆる努力に感謝します。

自分のサンプルを提供するにはどうすればよいですか?

GitHub はソーシャルコーディング用です。独自のコード例を Spring Integration サンプルプロジェクトに送信する場合は、このリポジトリのフォーク (英語) からのプルリクエスト (英語) による貢献をお勧めします。この方法でコードを提供したい場合は、可能であれば、サンプルに関する詳細を提供する GutHub の課題 [GitHub] (英語) を参照してください。

コントリビューターライセンス契約に署名する

とても重要: Spring Integration サンプルを受け入れる前に、SpringSource コントリビューターライセンス契約(CLA)に署名する必要があります。コントリビューターの同意書に署名しても、メインリポジトリへのコミット権は誰にも付与されませんが、コントリビュートを受け入れることができることを意味します。CLA を読んで署名するには、以下にアクセスしてください。

プロジェクトドロップダウンから、Spring Integration を選択します。プロジェクトリーダーは ArtemBilan です。

コード投稿プロセス

実際のコード貢献プロセスについては、Spring Integration のコントリビューターガイドラインを参照してください。サンプルプロジェクトにも応募します。github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc (英語) で見つけることができます

このプロセスにより、すべてのコミットがピアレビューされるようになります。実際のところ、コアコミッターはまったく同じルールに従います。Spring Integration のサンプルをお待ちしております。

サンプルリクエスト

前述のように、Spring Integration サンプルプロジェクトは、バグ追跡システムとして GitHub 課題を使用します。新しいサンプルリクエストを送信するには、github.com/spring-projects/spring-integration-samples/issues (英語) にアクセスしてください。

サンプル構造

Spring Integration 2.0 以降、サンプルの構造が変更されました。より多くのサンプルを計画することで、すべてのサンプルが同じゴールを持っているわけではないことがわかりました。それらはすべて、Spring Integration フレームワークを適用して操作する方法を示すという共通のゴールを共有しています。ただし、技術的なユースケースに重点を置いているサンプルもあれば、ビジネスユースケースに重点を置いているサンプルもある点で異なります。また、いくつかのサンプルは、特定のシナリオ(技術とビジネスの両方)に対処するために適用できるさまざまな手法を紹介するものです。サンプルの新しい分類により、各サンプルが対処する問題に基づいてサンプルをより適切に整理できると同時に、ニーズに合った適切なサンプルを簡単に見つけることができます。

現在、4 つのカテゴリがあります。サンプルリポジトリ内には、各カテゴリに独自のディレクトリがあり、カテゴリ名にちなんで名付けられています。

基本 (samples/basic)

これは始めるのに適した場所です。ここのサンプルは技術的に動機付けられており、構成とコードに関して最低限のものを示しています。これらは、Spring Integration の基本概念、API、構成、エンタープライズ統合パターン(EIP)を導入することで、すぐに始めるのに役立ちます。例: サービスアクティベーターを実装してメッセージチャネルに接続する方法、メッセージングゲートウェイをメッセージ交換のファサードとして使用する方法、または MAIL、TCP/UDP などの使用を開始する方法に関する回答を探している場合モジュール、これは良いサンプルを見つけるのに適した場所です。一番下の行は、samples/basic を開始するのに適した場所です。

中間 (samples/intermediate)

このカテゴリは、Spring Integration フレームワークにすでに精通している開発者を対象としていますが、メッセージングアーキテクチャへの切り替え後に発生する可能性のあるより高度な技術的課題を解決するために、さらにガイダンスが必要です。例: さまざまなメッセージ交換シナリオでエラーを処理する方法、または集約のために一部のメッセージが届かない状況にアグリゲーターを適切に構成する方法、または基本的な実装を超えるその他の課題に関する回答を探している場合特定のコンポーネントの構成および「その他」の型の課題を公開します。これは、これらの型のサンプルを見つけるのに適した場所です。

拡張 (samples/advanced)

このカテゴリは、Spring Integration フレームワークに精通している開発者を対象としていますが、Spring Integration のパブリック API を使用して特定のカスタムニーズに対応できるように拡張しようとしています。例: カスタムチャネルまたはコンシューマー(イベントベースまたはポーリングベース)の実装方法を示すサンプルを探している場合、または Spring Integration Bean の上にカスタム Bean パーサーを実装する最も適切な方法を見つけようとしている場合パーサー階層(おそらく、カスタムコンポーネントの独自の名前空間とスキーマを実装する場合)、これは適切な場所です。ここでは、アダプターの開発に役立つサンプルも見つけることができます。Spring Integration には、リモートシステムを Spring Integration メッセージングフレームワークに接続できるようにするアダプターの広範なライブラリが付属しています。ただし、コアフレームワークがアダプターを提供しないシステムと統合する必要がある場合があります。もしそうなら、あなた自身のものを実装することに決めるかもしれません(それを貢献することを考慮してください)。このカテゴリには、方法を示すサンプルが含まれます。

アプリケーション (samples/applications)

このカテゴリは、メッセージ駆動型アーキテクチャと EIP を十分に理解しており、特定のビジネス問題に対処するサンプルを探している Spring と Spring Integration を平均以上理解している開発者とアーキテクトを対象としています。言い換えれば、このカテゴリのサンプルの重点は、ビジネスユースケースと、特にメッセージ駆動型アーキテクチャ、特に Spring Integration で解決する方法です。例: Spring Integration を使用してローンブローカーまたは旅行代理店のプロセスを実装および自動化する方法を確認する場合は、これらの型のサンプルを見つけるのに最適な場所です。

Spring Integration はコミュニティ主導のフレームワークです。コミュニティへの参加は重要です。それにはサンプルが含まれます。探しているものが見つからない場合は、お知らせください。

サンプル

現在、Spring Integration にはかなりの数のサンプルが付属しており、さらに期待できます。よりよくナビゲートするために、各サンプルには、サンプルに関するいくつかの詳細をカバーする独自の readme.txt ファイルが付属しています (たとえば、対処する EIP パターン、解決しようとしている問題、サンプルの実行方法、その他の詳細))。ただし、特定のサンプルについては、より詳細で、場合によってはグラフィカルな説明が必要です。このセクションでは、特別な注意が必要と思われるサンプルの詳細を確認できます。

ローンブローカー

このセクションでは、Spring Integration サンプルに含まれている融資会社のサンプルアプリケーションについて説明します。このサンプルは、Gregor Hohpe と Bobby Woolf の本エンタープライズ統合パターン (英語) に掲載されているサンプルの 1 つに触発されています。

次の図は、プロセス全体を示しています。

loan broker eip
図 1: ローンブローカーのサンプル

EIP アーキテクチャの中核は、非常にシンプルでありながら強力なパイプ、フィルター、もちろんメッセージの概念です。エンドポイント(フィルター)は、チャネル(パイプ)を介して互いに接続されます。生成エンドポイントはチャネルにメッセージを送信し、消費エンドポイントはメッセージを取得します。このアーキテクチャは、エンドポイント間で情報がどのように交換されるかを記述するさまざまなメカニズムを定義することを目的としています。非常に疎結合で柔軟なコラボレーションモデルを提供すると同時に、統合の問題をビジネスの問題から切り離します。EIP はさらに以下を定義することにより、このアーキテクチャを継承します。

  • パイプの種類 (ポイントツーポイントチャネル、パブリッシュ / サブスクライブチャネル、チャネルアダプターなど)

  • フィルターがパイプと連携する方法に関するコアフィルターとパターン (メッセージルーター、スプリッターおよびアグリゲーター、さまざまなメッセージ変換パターンなど)

EIP ブックの第 9 章では、このユースケースの詳細とバリエーションについて詳しく説明していますが、簡単な概要を以下に示します。

  • コンシューマーの事前スクリーニング (たとえば、コンシューマーの信用履歴の取得と確認)

  • 最も適切な銀行の決定 (たとえば、コンシューマーの信用履歴またはスコアに基づいて)

  • 選択した各銀行へのローン見積もりリクエストの送信

  • 各銀行からの回答の収集

  • コンシューマーの要件に基づいて、レスポンスをフィルタリングし、最良の見積もりを決定します。

  • ローンの見積もりをコンシューマーに返します。

ローンの見積もりを取得する実際のプロセスは、一般的にもう少し複雑です。ただし、私たちのゴールは、エンタープライズ統合パターンが Spring Integration 内でどのように実現および実装されるかを実証することであるため、ユースケースはプロセスの統合のアスペクトのみに集中するよう簡素化されています。これは、コンシューマー金融に関するアドバイスを提供する試みではありません。

ローンブローカーと契約することにより、コンシューマーはローンブローカーの業務の詳細から隔離され、各ローンブローカーの業務は競争上の優位性を維持するために互いに延期される可能性があるため、変更を導入できるように、組み立てて実装するものは柔軟でなければなりません迅速かつ無痛。

ローンブローカーのサンプルは、実際には「架空の」銀行や信用調査機関と話をしません。これらのサービスはスタブアウトされています。

ここでのゴールは、プロセス全体の統合の側面を組み立て、調整し、テストすることです。そうしてはじめて、そのようなプロセスを実際のサービスに接続することについて考え始めることができます。その時点で、特定のローンブローカーが取引する銀行の数や、通信に使用される通信メディア(またはプロトコル)の種類(JMS、WS、TCP など)に関係なく、組み立てられたプロセスとその構成は変わりません。これらの銀行と。

デザイン

前述の 6 つの要件を分析すると、それらはすべて統合の問題であることがわかります。例: コンシューマーの事前審査のステップでは、コンシューマーとコンシューマーの要望に関する追加情報を収集し、追加のメタ情報でローンリクエストを充実させる必要があります。次に、そのような情報をフィルタリングして、銀行などの最も適切なリストを選択する必要があります。強化、フィルター、選択はすべて、EIP がパターンの形式でソリューションを定義する統合の問題です。Spring Integration は、これらのパターンの実装を提供します。

次の図は、メッセージングゲートウェイの表現を示しています。

gateway
図 2: メッセージングゲートウェイ

メッセージングゲートウェイパターンは、ローンブローカーなどのメッセージングシステムにアクセスするためのシンプルなメカニズムを提供します。Spring Integration では、ゲートウェイを単純な古い java インターフェースとして定義し(実装を提供する必要はありません)、XML <gateway> 要素または Java のアノテーションを使用して構成し、他の Spring Bean と同様に使用できます。Spring Integration は、メッセージを生成し(ペイロードがメソッドの入力パラメーターにマップされる)、指定されたチャネルに送信することにより、メソッド呼び出しをメッセージングインフラストラクチャに委譲およびマッピングします。次の例は、このようなゲートウェイを XML で定義する方法を示しています。

<int:gateway id="loanBrokerGateway"
  default-request-channel="loanBrokerPreProcessingChannel"
  service-interface="org.springframework.integration.samples.loanbroker.LoanBrokerGateway">
  <int:method name="getBestLoanQuote">
    <int:header name="RESPONSE_TYPE" value="BEST"/>
  </int:method>
</int:gateway>

現在のゲートウェイには、呼び出すことができる 2 つのメソッドがあります。1 つは最良の単一引用符を返し、もう 1 つはすべての引用符を返します。どういうわけか、ダウンストリームでは、発信者が必要とする応答の型を知る必要があります。メッセージングアーキテクチャでこれを実現する最良の方法は、メッセージのコンテンツを、意図を説明するメタデータで強化することです。コンテンツエンリッチャーは、これに対処するパターンの 1 つです。Spring Integration は、便宜上、メッセージヘッダーを任意のデータで強化するための個別の構成要素を提供します(後述)。ただし、gateway 要素は初期メッセージの作成を担当するため、新しく作成されたメッセージを任意のメッセージヘッダーで強化する機能が含まれます。この例では、getBestQuote() メソッドが呼び出されるたびに、値が BEST の RESPONSE_TYPE ヘッダーを追加します。他の方法では、ヘッダーを追加しません。これで、このヘッダーの存在をダウンストリームで確認できます。その存在とその値に基づいて、発信者が望む応答の型を判別できます。

ユースケースに基づいて、一部のプレミア銀行は最低信用スコア要件を満たすコンシューマーからの見積もりリクエストのみを受け入れるため、コンシューマーの信用スコアの取得と評価など、いくつかの事前スクリーニング手順を実行する必要があることもわかっています。メッセージが銀行に転送される前に、そのような情報が充実していればよいのですが。また、そのようなメタ情報を提供するために複数のプロセスを完了する必要がある場合、それらのプロセスを 1 つのユニットにグループ化できれば便利です。このユースケースでは、信用スコアを決定し、信用スコアと何らかのルールに基づいて、見積もりリクエストの送信先となるメッセージチャネル (銀行チャネル) のリストを選択する必要があります。

構成されたメッセージプロセッサー

合成メッセージプロセッサーパターンは、複数のメッセージプロセッサーで構成されるメッセージフローの制御を維持するエンドポイントの構築に関するルールを記述します。Spring Integration では、構成されたメッセージプロセッサーパターンは <chain> 要素によって実装されます。

次の図は、チェーンパターンを示しています。

chain
図 3: チェーン

前のイメージは、CREDIT_SCORE ヘッダーと値(クレジットサービスの呼び出しによって決定される単純な POJOSpringBean によって決定される)でメッセージのコンテンツをさらに豊かにする内部 header-enricher 要素を持つチェーンがあることを示しています "creditBureau" 名)。次に、メッセージルーターに委譲します。

次の図は、メッセージルータパターンを示しています。

bank router
図 4: メッセージルーター

Spring Integration は、メッセージルーティングパターンのいくつかの実装を提供します。この場合、ルータを使用して、クレジットスコア(前のステップで決定)を調べる式(Spring Expression Language)の評価に基づいてチャネルのリストを決定し、Map Bean からチャネルのリストを選択します。クレジットスコアの値に基づいて、値が premier または secondary である banks の id。チャネルのリストが選択されると、メッセージはそれらのチャネルにルーティングされます。

ローンブローカーが銀行からローンの見積もりを受け取り、コンシューマーごとに集約し(コンシューマー間で見積もりを表示したくない)、コンシューマーの選択条件に基づいてレスポンスを組み立てる必要がある最後の 1 つ(単一の最高の見積もり)またはすべての引用符)とコンシューマーに返信を送信します。

次のイメージは、メッセージ集約パターンを示しています。

quotes aggregator
図 5: メッセージアグリゲーター

アグリゲーターパターンは、関連するメッセージを 1 つのメッセージにグループ化するエンドポイントを記述します。集約および相関戦略を決定するための条件とルールを提供できます。Spring Integration は、アグリゲーターパターンのいくつかの実装と、便利な名前空間ベースの構成を提供します。

次の例は、アグリゲーターを定義する方法を示しています。

<int:aggregator id="quotesAggregator"
      input-channel="quotesAggregationChannel"
      method="aggregateQuotes">
  <beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
</int:aggregator>

ローンブローカーは、<aggregator> 要素を使用して "quotesAggregator" Bean を定義します。これにより、デフォルトの集計および相関戦略が提供されます。デフォルトの相関戦略は、correlationId ヘッダーに基づいてメッセージを相関させます(EIP book の相関識別子パターン (英語) を参照)。このヘッダーに値を提供したことがないことに注意してください。銀行チャネルごとに個別のメッセージを生成したときに、ルーターによって以前に自動的に設定されました。

メッセージが相互に関連付けられると、実際のアグリゲーター実装に解放されます。Spring Integration はデフォルトのアグリゲーターを提供しますが、その戦略(すべてのメッセージからペイロードのリストを収集し、このリストをペイロードとして新しいメッセージを作成する)は要件を満たしていません。私たちのコンシューマーは単一の最良の引用またはすべての引用を必要とするかもしれないため、メッセージにすべての結果を含めることは問題です。コンシューマーの意図を伝えるために、プロセスの早い段階で RESPONSE_TYPE ヘッダーを設定しました。次に、このヘッダーを評価して、すべての見積もり(デフォルトの集計戦略が機能する)または最良の見積もり(どのローン見積もりが最適かを判断する必要があるため、デフォルトの集計戦略は機能しない)のいずれかを返す必要があります。

より現実的なアプリケーションでは、最適な見積もりの選択は、アグリゲーターの実装と構成の複雑さに影響を与える可能性のある複雑な条件に基づいている場合があります。ただし、今のところは、単純化しています。コンシューマーが最高の見積もりを希望する場合は、最低金利の見積もりを選択します。これを実現するために、LoanQuoteAggregator クラスはすべてのクオートを金利でソートし、最初のクオートを返します。LoanQuote クラスは Comparable を実装して、レート属性に基づいて見積もりを比較します。レスポンスメッセージが作成されると、プロセスを開始したメッセージングゲートウェイのデフォルトのレスポンスチャネル(したがって、コンシューマー)に送信されます。私たちのコンシューマーはローンの見積もりを受け取りました!

結論として、POJO(既存またはレガシー)ロジックと、軽量で埋め込み可能なメッセージングフレームワーク(Spring Integration)に基づいて、かなり複雑なプロセスが組み立てられました。ESB のようなエンジンまたは独自の開発およびデプロイ環境。開発者として、Swing またはコンソールベースのアプリケーションを ESB のようなサーバーに移植したり、独自のインターフェースを実装したりする必要はありません。これは、統合に関する懸念があるからです。

このサンプルとこのセクションの他のサンプルは、エンタープライズ統合パターン上に構築されています。これらはソリューションの「ビルドブロック」であると考えることができます。完全なソリューションを意図したものではありません。統合の懸念は、すべてのタイプのアプリケーション(サーバーベースかどうか)に存在します。私たちのゴールは、アプリケーションの統合に設計、テスト、デプロイ戦略の変更を必要としないようにすることです。

カフェのサンプル

このセクションでは、Spring Integration サンプルに含まれている cafe サンプルアプリケーションについて説明します。このサンプルは、Gregor Hohpe のとりとめ (英語) で紹介されている別のサンプルに触発されています。

ドメインはカフェのものであり、次の図は基本的なフローを示しています。

cafe eip
図 6: カフェサンプル

Order オブジェクトには、複数の OrderItems が含まれる場合があります。オーダーが行われると、スプリッターが複合オーダーメッセージを飲み物ごとに 1 つのメッセージに分割します。次に、これらのそれぞれが、OrderItem オブジェクトの "isIced" プロパティをチェックして、飲み物が熱いか冷たいかを判断するルーターによって処理されます。Barista は各飲み物を準備しますが、温かい飲み物と冷たい飲み物の準備は 2 つの異なる方法で処理されます: "prepareHotDrink" と "prepareColdDrink"。準備された飲み物は Waiter に送られ、そこで Delivery オブジェクトに集約されます。

次のリストは、XML 構成を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/integration
  https://www.springframework.org/schema/integration/spring-integration.xsd
  http://www.springframework.org/schema/integration/stream
  https://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd">

    <int:gateway id="cafe" service-interface="o.s.i.samples.cafe.Cafe"/>

    <int:channel  id="orders"/>
    <int:splitter input-channel="orders" ref="orderSplitter"
                  method="split" output-channel="drinks"/>

    <int:channel id="drinks"/>
    <int:router  input-channel="drinks"
                 ref="drinkRouter" method="resolveOrderItemChannel"/>

    <int:channel id="coldDrinks"><int:queue capacity="10"/></int:channel>
    <int:service-activator input-channel="coldDrinks" ref="barista"
                           method="prepareColdDrink" output-channel="preparedDrinks"/>

    <int:channel id="hotDrinks"><int:queue capacity="10"/></int:channel>
    <int:service-activator input-channel="hotDrinks" ref="barista"
                           method="prepareHotDrink" output-channel="preparedDrinks"/>

    <int:channel id="preparedDrinks"/>
    <int:aggregator input-channel="preparedDrinks" ref="waiter"
                    method="prepareDelivery" output-channel="deliveries"/>

    <int-stream:stdout-channel-adapter id="deliveries"/>

    <beans:bean id="orderSplitter"
                class="org.springframework.integration.samples.cafe.xml.OrderSplitter"/>

    <beans:bean id="drinkRouter"
                class="org.springframework.integration.samples.cafe.xml.DrinkRouter"/>

    <beans:bean id="barista" class="o.s.i.samples.cafe.xml.Barista"/>
    <beans:bean id="waiter"  class="o.s.i.samples.cafe.xml.Waiter"/>

    <int:poller id="poller" default="true" fixed-rate="1000"/>

</beans:beans>

各メッセージエンドポイントは、入力チャネル、出力チャネル、その両方に接続します。各エンドポイントは独自のライフサイクルを管理します(デフォルトでは、エンドポイントは初期化時に自動的に起動します。これを防ぐため、auto-startup 属性に値 false を追加します)。最も重要なことは、オブジェクトが、厳密に型指定されたメソッド引数を持つ単純な POJO であることに注意してください。次の例は、スプリッターを示しています。

public class OrderSplitter {
    public List<OrderItem> split(Order order) {
        return order.getItems();
    }
}

ルーターの場合、戻り値は MessageChannel インスタンスである必要はありません (ただし、そうすることもできます)。この例では、次のように、チャネル名を保持する String 値が代わりに返されます。

public class DrinkRouter {
    public String resolveOrderItemChannel(OrderItem orderItem) {
        return (orderItem.isIced()) ? "coldDrinks" : "hotDrinks";
    }
}

XML に戻ると、2 つの <service-activator> 要素があることがわかります。これらはそれぞれ、同じ Barista インスタンスに委譲されていますが、異なる方法(prepareHotDrink または prepareColdDrink)を使用しており、それぞれがオーダー品がルーティングされた 2 つのチャネルの 1 つに対応しています。次のリストは、バリスタクラスを示しています (prepareHotDrink および prepareColdDrink メソッドが含まれています)

public class Barista {

    private long hotDrinkDelay = 5000;
    private long coldDrinkDelay = 1000;

    private AtomicInteger hotDrinkCounter = new AtomicInteger();
    private AtomicInteger coldDrinkCounter = new AtomicInteger();

    public void setHotDrinkDelay(long hotDrinkDelay) {
        this.hotDrinkDelay = hotDrinkDelay;
    }

    public void setColdDrinkDelay(long coldDrinkDelay) {
        this.coldDrinkDelay = coldDrinkDelay;
    }

    public Drink prepareHotDrink(OrderItem orderItem) {
        try {
            Thread.sleep(this.hotDrinkDelay);
            System.out.println(Thread.currentThread().getName()
                    + " prepared hot drink #" + hotDrinkCounter.incrementAndGet()
                    + " for order #" + orderItem.getOrder().getNumber()
                    + ": " + orderItem);
            return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
                    orderItem.isIced(), orderItem.getShots());
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }

    public Drink prepareColdDrink(OrderItem orderItem) {
        try {
            Thread.sleep(this.coldDrinkDelay);
            System.out.println(Thread.currentThread().getName()
                    + " prepared cold drink #" + coldDrinkCounter.incrementAndGet()
                    + " for order #" + orderItem.getOrder().getNumber() + ": "
                    + orderItem);
            return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
                    orderItem.isIced(), orderItem.getShots());
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }
}

上記のコードの抜粋からわかるように、Barista メソッドにはさまざまな遅延があります(ホットドリンクの準備には 5 倍の時間がかかります)。これは、異なるレートで完了する作業をシミュレートします。CafeDemo の 'main' メソッドが実行されると、100 回ループし、毎回 1 つのホットドリンクと 1 つのコールドドリンクを送信します。実際には、Cafe インターフェースで "placeOrder" メソッドを呼び出してメッセージを送信します。以前の XML 構成では、<gateway> 要素が指定されていることがわかります。これにより、特定のサービスインターフェースを実装し、チャネルに接続するプロキシの作成がトリガーされます。以下のインターフェース定義が示すように、チャネル名は Cafe インターフェースの @Gateway アノテーションで提供されます。

public interface Cafe {

    @Gateway(requestChannel="orders")
    void placeOrder(Order order);

}

最後に、CafeDemo 自体の main() メソッドを参照してください。

public static void main(String[] args) {
    AbstractApplicationContext context = null;
    if (args.length > 0) {
        context = new FileSystemXmlApplicationContext(args);
    }
    else {
        context = new ClassPathXmlApplicationContext("cafeDemo.xml", CafeDemo.class);
    }
    Cafe cafe = context.getBean("cafe", Cafe.class);
    for (int i = 1; i <= 100; i++) {
        Order order = new Order(i);
        order.addItem(DrinkType.LATTE, 2, false);
        order.addItem(DrinkType.MOCHA, 3, true);
        cafe.placeOrder(order);
    }
}
このサンプルと他の 8 つのサンプルを実行するには、メインのディストリビューションの samples ディレクトリ内の README.txt を参照してください(この章の冒頭で説明します)。

cafeDemo を実行すると、最初は冷たい飲み物が熱い飲み物よりも早く準備されていることがわかります。アグリゲーターが存在するため、冷たい飲み物は、熱い飲み物の準備の速度によって事実上制限されます。これは、1000 および 5000 ミリ秒のそれぞれの遅延に基づいて予想されるものです。ただし、同時タスクエグゼキューターを使用してポーラーを構成すると、結果を劇的に変更できます。例: コールドドリンクバリスタをそのままに保ちながら、ホットドリンクバリスタに 5 つのワーカーを備えたスレッドプールエグゼキュータを使用できます。次のリストは、このような配置を構成しています。

<int:service-activator input-channel="hotDrinks"
                     ref="barista"
                     method="prepareHotDrink"
                     output-channel="preparedDrinks"/>

  <int:service-activator input-channel="hotDrinks"
                     ref="barista"
                     method="prepareHotDrink"
                     output-channel="preparedDrinks">
      <int:poller task-executor="pool" fixed-rate="1000"/>
  </int:service-activator>

  <task:executor id="pool" pool-size="5"/>

また、各呼び出しでワーカースレッド名が表示されることに注意してください。ホットドリンクがタスクエグゼキュータースレッドによって準備されていることがわかります。より短いポーラー間隔(100 ミリ秒など)を指定すると、タスクスケジューラ(呼び出し元)に操作を呼び出すように強制することで、入力を時々調整することがわかります。

ポーラーの同時実行設定の実験に加えて、「トランザクション」子要素を追加して、コンテキスト内の PlatformTransactionManager インスタンスを参照することもできます。

XML メッセージングのサンプル

basic/xml の XML メッセージングサンプルは、XML ペイロードを処理する提供されたコンポーネントの使用方法を示しています。このサンプルでは、XML で表された書籍のオーダーを処理するという考え方を使用しています。

このサンプルは、ネームスペースプレフィックスが任意のものであることを示しています。通常、XML コンポーネントの統合には int-xml を使用しますが、サンプルでは si-xml を使用します。(int は「統合」の略で、si は "Spring Integration" の略です。)

まず、オーダーは複数のメッセージに分割され、各メッセージは XPath スプリッタコンポーネントからの単一のオーダーアイテムを表します。次のリストは、スプリッターの構成を示しています。

<si-xml:xpath-splitter id="orderItemSplitter" input-channel="ordersChannel"
              output-channel="stockCheckerChannel" create-documents="true">
      <si-xml:xpath-expression expression="/orderNs:order/orderNs:orderItem"
                                namespace-map="orderNamespaceMap" />
  </si-xml:xpath-splitter>

次に、サービスアクティベータはメッセージをストックチェッカー POJO に渡します。オーダー品目ドキュメントには、オーダー品目の在庫レベルに関する在庫チェッカーからの情報が追加されています。この強化されたオーダー項目メッセージは、メッセージのルーティングに使用されます。オーダー品の在庫がある場合、メッセージは倉庫にルーティングされます。次のリストは、メッセージをルーティングする xpath-router を構成します。

<si-xml:xpath-router id="inStockRouter" input-channel="orderRoutingChannel" resolution-required="true">
    <si-xml:xpath-expression expression="/orderNs:orderItem/@in-stock" namespace-map="orderNamespaceMap" />
    <si-xml:mapping value="true" channel="warehouseDispatchChannel"/>
    <si-xml:mapping value="false" channel="outOfStockChannel"/>
</si-xml:xpath-router>

オーダー品の在庫がない場合、メッセージは XSLT を使用してサプライヤーへの送信に適した形式に変換されます。以下のリストは、XSLT トランスフォーマーを構成します。

<si-xml:xslt-transformer input-channel="outOfStockChannel"
  output-channel="resupplyOrderChannel"
  xsl-resource="classpath:org/springframework/integration/samples/xml/bigBooksSupplierTransformer.xsl"/>