The Java EE 7 TutorialのJava Message Service Conceptsの章をテキトーに訳した・1 - kagamihogeの日記 の続き。
45.4 Using Advanced JMS Features
このセクションでは、アプリケーション要求における信頼性とパフォーマンスを実現するためのJMS APIの使い方について解説します。多くの人々がアプリケーションでJMSを使用していますが、その理由はメッセージの重複や欠落を許容しないことや、すべてのメッセージをただ一度だけ受信させたいからです。JMS APIはそれらの機能を提供しています。
メッセージをプロデュースするのに最も信頼性のある方法はPERSISTENT
を送信することで、また、メッセージ送信をトランザクション内に含めます。
JMSメッセージはデフォルトでPERSISTENT
で、このメッセージはJMSプロバイダの失敗イベントで消失することがありません。詳細については、Specifying Message Persistenceを参照してください。
トランザクションではアトミック操作において複数のメッセージ送信あるいは受信を許容します。Java EEプラットフォームでは、メッセージ送受信はアトミックトランザクションにおけるデータベースread/writeと組み合わせが可能です。トランザクションとは、メッセージ送受信などの一連の操作をグループ化した一つの単位であり、各操作はすべて成功か失敗かのどちらかになります。詳細については、Using JMS Local Transactionsを参照してください。
メッセージをコンシュームする最も信頼性のある方法は、トランザクション内でキューもしくはトピックのdurable subscriptionからコンシュームします。詳細については、Creating Durable Subscriptions, (Creating Temporary Destinations)http://docs.oracle.com/javaee/7/tutorial/jms-concepts004.htm#BNCGB, (Using JMS Local Transactions)http://docs.oracle.com/javaee/7/tutorial/jms-concepts004.htm#BNCGHを参照してください。
いくつかの機能を使うことで主にパフォーマンス向上が可能となります。例えば、特定時間後にメッセージを有効期限切れにする設定が可能で(Allowing Messages to Expire))、こうすると、コンシューマは期限切れした不要な情報を受信しません。非同期のメッセージ送信が可能です。Sending Messages Asynchronouslyを参照してください。
メッセージ確認(message acknowledgment)を通じた各種の制御レベルも指定可能です。Controlling Message Acknowledgmentを参照してくださ。
他の機能として信頼性とは無関係な機能がいくつか存在します。例えば、temporary destinationsを作成可能で、これはtemporary destinationsを生成したコネクションの期間中だけ存在します。詳細についてはCreating Temporary Destinationsを参照してください。
以降のセクションではアプリケーションクライアントもしくはJava SEクライアントに適用可能な機能について解説します。いくつかの機能についてはJava EE webあるいはEJBコンテナで異なる動作をします。そのケースについては、相違についてはここで触れ、詳細についてはUsing the JMS API in Java EE Applicationsで解説します。
45.4.1 Controlling Message Acknowledgment
JMSメッセージは確認が行われるまで、正常にコンシュームされたとは見なされません。メッセージの正常なコンシュームは通常三段階で行われます。
- クライアントがメッセージを受信する。
- クライアントがメッセージを処理する。
- メッセージが確認される。確認は、セッション確認モードに応じて、JMSプロバイダもしくはクライアントのどちらかが開始する。
locally transacted sessions(http://docs.oracle.com/javaee/7/tutorial/jms-concepts004.htm#BNCGH参照)では、セッションコミット時にメッセージは確認されます。トランザクションがロールバックする場合、すべてのコンシューム済みメッセージは再配信されます。
JTAトランザクション(Java EE webあるいはEJBコンテナ)ではトランザクションコミット時にメッセージは確認されます。
nontransacted sessionsでは、createContext
メソッドの引数として指定可能な値に応じてメッセージ確認の方法とタイミングが変わります。指定可能な引数値は以下の通りです。
JMSContext.AUTO_ACKNOWLEDGE
: アプリケーションクライアントとJava SEクライアントのデフォルト設定。クライアントがreceive
呼び出しから正常に返った、あるいは、メッセージ処理に呼び出されたMessageListener
が正常に返った場合、JMSContext
は自動的にメッセージのクライアント到達確認を行います。
自動確認(auto-acknowledgment)設定のJMSContext
における同期受信は、上述の三段階ステップのメッセージコンシュームの例外となります。この場合、メッセージ処理に続いて、受信と確認は一段階で行われます。*1JMSContext.CLIENT_ACKNOWLEDGE
: メッセージのacknowledge
メソッドを呼ぶことでクライアントがメッセージを確認する。このモードでは、確認はセッションレベルで行われます。そのセッションでコンシュームしたすべての受信メッセージを自動的に確認します。例えば、メッセージコンシューマが10メッセージをコンシュームして5メッセージを確認したとすると、すべての10メッセージが確認されます。
Note: Java EEプラットフォームでは、JMSContext.CLIENT_ACKNOWLEDGE
設定のみアプリケーションクライアントでは使用可能ですが、webコンポーネントもしくはEJBではその限りではありません。JMSContext.DUPS_OK_ACKNOWLEDGE
: このオプションはJMSContext
にメッセージ配信の遅延確認を指示します。これはJMSプロバイダが失敗する場合に重複メッセージを配信する可能性があるため、重複メッセージを許容可能なコンシューマでだけ使用すべきです。(JMSプロバイダがメッセージを再配信する場合、メッセージヘッダJMSRedelivered
にtrue
設定が必須です。)このオプションでセッションの重複を最小化することで、セッションオーバーヘッドを減らせます。
メッセージはキューから受信したがJMSContext
がクローズして確認していない場合、JMSプロバイダはメッセージを保持し、コンシューマがキューに次回アクセスした時に再配信します。また、durable subscriptionからメッセージをコンシュームしていたJMSContext
をアプリケーションがクローズする場合、プロバイダは未確認メッセージ(unacknowledged message)を保持します。(Creating Durable Subscriptions参照) nondurable subscriptionから受信した未確認メッセージはJMXContext
クローズ時には破棄されます。
キューもしくはdurable subscriptionを使う場合に、nontransacted JMSContext
を停止して最初の未確認メッセージで再開するにはJMSContext.recover
メソッドを使います。実際には、最後の確認メッセージ後のポイントに、JMSContext
の一連の配信メッセージがリセットされます。このとき配信されるメッセージは、メッセージが期限切れあるいは高プライオリティメッセージが到着した場合、元々配信していたメッセージとは異なる可能性があります。nondurable subscriptionのコンシューマでは、JMSContext.recover
メソッドが呼ばれた場合、プロバイダは未確認メッセージを破棄する可能性があります。
Acknowledging Messagesのサンプルプログラムでは、メッセージ処理が完了するまでメッセージ確認しないことを保証する二つの方法を載せています。
45.4.2 Specifying Options for Sending Messages
メッセージ送信時にいくつかのオプションを設定可能です。これらのオプションにより以下のタスク実行が可能となります。
- メッセージに永続化(persistent)を指定する場合、プロバイダの失敗イベント時における破棄禁止を意味する(Specifying Message Persistence)。
- メッセージにプライオリティレベルを設定する場合、メッセージ配信の順序に影響を及ぼす(Setting Message Priority Levels)。
- メッセージに有効期限を指定すると、メッセージ破棄時に配信されなくなる(Allowing Messages to Expire)。
- メッセージに配信ディレイを指定すると、指定時間を経過するまでそのメッセージは配信されない((Specifying a Delivery Delay)http://docs.oracle.com/javaee/7/tutorial/jms-concepts004.htm#BABGEADH)。
プロデューサー生成およびsend
メソッド呼び出し時に、一つ以上のオプション指定するにはメソッドチェーンが使えます。Using JMSProducer Method Chainingを参照してください。
45.4.2.1 Specifying Message Persistence
JMS APIは、JMSプロバイダが失敗する場合にメッセージを破棄するかどうかを指定する二つの配信モードをサポートしています。配信モードはDeliveryMode
インタフェースのフィールドです。
- デフォルトの配信モードは
PERSISTENT
で、JMSプロバイダ失敗時に配信中のメッセージが破棄されないことを保証するようJMSプロバイダに指示します。この配信モードで送信されたメッセージはストレージ(stable storage)にログが残されます。 NON_PERSISTENT
配信モードは、プロバイダ失敗時にメッセージを破棄しないことを保証する、もしくは、メッセージの保存をJMSプロバイダに要求しません*2。
配信モードを指定するには、プロデューサーが送信する全メッセージに配信モードを設定するJMSProducer
インタフェースのsetDeliveryMode
メソッドを使います。
プロデューサー生成およびメッセージ送信時に配信モードを設定するのにメソッドチェーンを使えます。以下の呼び出しはNON_PERSISTENT
配信モードでプロデューサーを生成し、メッセージ送信にそのプロデューサーを使用しています。
context.createProducer() .setDeliveryMode(DeliveryMode.NON_PERSISTENT).send(dest, msg);
配信モードを指定しない場合、デフォルトはPERSISTENT
です。NON_PERSISTENT
配信モードを使うことでパフォーマンスの向上とストレージオーバーヘッドを減らせる可能性がありますが、アプリケーションがメッセージ消失を許容可能な場合にだけ使用すべきです。
45.4.2.2 Setting Message Priority Levels
緊急を要するメッセージを最初に配信するようJMSプロバイダに指示するためには、メッセージのプライオリティレベルを使います。プロデューサーが送信するすべてのメッセージにプライオリティレベルを設定するにはJMSProducer
インタフェースのsetPriority
メソッドを使います。
プロデューサー生成とメッセージ送信時にプライオリティレベルを設定する際にはメソッドチェーンが使えます。例えば、以下のコード例はプロデューサーにプライオリティレベル7を設定してからメッセージ送信をしています。
context.createProducer().setPriority(7).send(dest, msg);
プライオリティレベルの範囲は0(最低)から9(最高)までの10レベルです。プライオリティレベルを指定しない場合、デフォルトレベルは4です。JMSプロバイダは低プライオリティよりも高プライオリティのメッセージ配信を試みますが、正確にプライオリティの順序でメッセージ配信されるわけではありません。
45.4.2.3 Allowing Messages to Expire
デフォルトでは、メッセージに有効期限切れはありません。一定期間後にメッセージを破棄する場合、有効期限を設定できます。プロデューサーが送信するすべてのメッセージにデフォルトの有効期限を設定するにはJMSProducer
インタフェースのsetTimeToLive
メソッドを使います。
例えば、株価など頻繁に変わるデータを含むメッセージを数分後に破棄するには、数分後に有効期限切れするようメッセージを設定します。
プロデューサ生成およびメッセージ送信時に有効期限を設定するのにメソッドチェーンを使えます。例えば、以下の呼び出しはプロデューサーに5分の有効期限を設定してからメッセージ送信をしています。
context.createProducer().setTimeToLive(300000).send(dest, msg);
timeToLive
に0
を指定する場合、メッセージは有効期限切れになりません。
メッセージ送信時、有効期限を知るには現在時刻にtimeToLive
を加えます。有効期限時間よりも前に配信されなかったメッセージは破棄されます。メッセージを破棄することでストレージと計算リソースが節約されます。
45.4.2.4 Specifying a Delivery Delay
メッセージ送信後の、JMSプロバイダのメッセージ配信前に、待機時間を指定可能です。プロデューサーが送信するすべてのメッセージに配信ディレイを設定するにはJMSProducer
インタフェースのsetDeliveryDelay
を使います。
プロデューサー生成およびメッセージ送信時に配信ディレイを設定するのにメソッドチェーンを使えます。例えば、以下のコードはプロデューサーに3秒の配信ディレイを設定してからメッセージを送信しています。
context.createProducer().setDeliveryDelay(3000).send(dest, msg);
45.4.2.5 Using JMSProducer Method Chaining
JMSProducer
インタフェースのセッターメソッドはJMSProducer
オブジェクトを返すので、メソッドチェーンでプロデューサの生成・プロパティの複数設定・メッセージ送信が行えます。例えば、以下のメソッドチェーンは、プロデューサーを生成し、ユーザ定義プロパティ、有効期限、配信モード、プライオリティを設定してから、キューにメッセージを送信しています。
context.createProducer() .setProperty("MyProperty", "MyValue") .setTimeToLive(10000) .setDeliveryMode(NON_PERSISTENT) .setPriority(2) .send(queue, body);
なお、メッセージにプロパティを設定するのにJMSProducer
のメソッドを呼び出し、それからsend
メソッドでメッセージ送信することも可能です。メッセージに直接メッセージプロパティを設定することも可能です。
45.4.3 Creating Temporary Destinations
通常、JMSのdestinations(キューとトピック)はプログラミングではなく管理者が作成します。JMSプロバイダにはdestinationsの作成と削除を行うツールが含まれ、基本的にdestinationsは存在し続けます。
JMS APIではdestinations(TemporaryQueue
とTemporaryTopic
オブジェクト)を生成可能で、このdestinationsを生成したコネクションの間だけ存在し続けます。以下のようなコードで、JMSContext.createTemporaryTopic
とJMSContext.createTemporaryQueue
を使うことで動的にdestinationsを生成します。
TemporaryTopic replyTopic = context.createTemporaryTopic();
temporary destinationからコンシューム可能なメッセージコンシューマは、そのtemporary destinationを作成したのと同一コネクションが生成したコンシューマだけです。任意のメッセージプロデューサがtemporary destinationに送信可能です。temporary destinationの属するコネクションを切断する場合、temporary destinationは閉じられ内容は破棄されます。
単純なリクエスト/リプライメカニズムを実装するのにtemporary destinationを使えます。temporary destinationを生成してメッセージ送信時にJMSReplyTo
メッセージヘッダーに値を指定する場合、メッセージのコンシューマはリプライ送信先のdestinationとしてJMSReplyTo
フィールドの値を使用可能です。リプライメッセージのJMSCorrelationID
ヘッダーフィールドにリクエストのJMSMessageID
ヘッダーフィールドを設定することで、コンシューマはオリジナルのリクエストを参照可能になります。例えば、onMessage
メソッドはJMSContext
を作成可能なので、受信メッセージに対しリプライを送信できます。そのコードは以下のようになります。
replyMsg = context.createTextMessage("Consumer processed message: "
+ msg.getText());
replyMsg.setJMSCorrelationID(msg.getJMSMessageID());
context.createProducer().send((Topic) msg.getJMSReplyTo(), replyMsg);
例えば、Using an Entity to Join Messages from Two MDBsを参照してください。
45.4.4 Using JMS Local Transactions
トランザクションは一連の操作をアトミックな作業単位にグループ化します。操作のうちいずれか一つが失敗する場合、そのトランザクションはロールバック可能となり、最初からのやり直しが可能となります。すべての操作が成功する場合、トランザクションはコミット可能となります。
アプリケーションクライアントあるいはJava SEクライアントでは、メッセージの送受信をグループ化するのにローカルトランザクションを使えます。トランザクションをコミットするにはJMSContext.commit
メソッドを使います。単一トランザクション内で複数のメッセージ送信が可能で、トランザクションをコミットするまでそのメッセージはキューもしくはトピックに追加されません。トランザクションで複数メッセージを受信する場合、トランザクションをコミットするまでメッセージ確認は行われません。
トランザクションをロールバックするにはJMSContext.rollback
メソッドを使います。トランザクションロールバックをすると、すべてのプロデュース済みメッセージが破棄され、すべてのコンシューム済みメッセージが有効期限に達しない場合に限りリカバリーされて再配信されます(Allowing Messages to Expireを参照)。
transacted sessionは常にトランザクションに含まれます。transacted sessionを生成するには、以下のようにcreateContext
メソッドを呼びます。
JMSContext context = connectionFactory.createContext(JMSContext.SESSION_TRANSACTED);
commit
もしくはrollback
を呼ぶとすぐに一つのトランザクションが終了して別のトランザクションが開始します。transacted sessionをクローズすると、送受信待ちを持つ実行中のトランザクションはロールバックします。
Java EE webもしくはEJBコンテナで実行中のアプリケーションでは、ローカルトランザクションは使用出来ません。その代わりに、Using the JMS API in Java EE Applicationsで解説するJTAトランザクションを使います。
同一のJMSContext
で実行する場合に限り、単一のJMSローカルトランザクションで複数の送受信を組み合わせ可能です
メッセージを送信してそのメッセージへのリプライを受信する、リクエスト/リプライメカニズムを使用する場合は単一トランザクションを使用してはいけません。この場合に単一トランザクションを使おうとすると、プログラムはハングします。これはトランザクションがコミットするまで送信できないためです。この問題を説明するためのコードは以下の通りです。
// Don't do this!
outMsg.setJMSReplyTo(replyQueue);
context.createProducer().send(outQueue, outMsg);
consumer = context.createConsumer(replyQueue);
inMsg = consumer.receive();
context.commit();
トランザクション中のメッセージ送信はトランザクションをコミットするまで実際には行われないため、そのメッセージ送信に対するリプライに依存する受信は出来ません。
メッセージのプロデュースとコンシュームを共に同一トランザクションの一部にすることはできません。その理由は、メッセージのプロデュースとコンシュームの仲介を行うJMSプロバイダとクライアントとの間に、トランザクションがあるためです。この相互作用を図にしたのがFigure 45-8です。
Figure 45-8 Using JMS Local Transactions
Client 1からの一つ以上のdestinationsへの一つ以上のメッセージ送信は単一トランザクションを形成可能ですが、その理由は単一のJMSContext
を通してJMSプロバイダとの単一セットの相互作用を作成可能なためです。同様に、Client 2で一つ以上のdestinationから一つ以上のメッセージ受信は、単一のJMSContext
を通して単一トランザクションを形成します。しかし、各クライアントは直接の相互作用は無く、二つの異なるJMSContext
を使用するため、それぞれのトランザクションは異なるJMSContext
をまたぐことは出来ません。
Another way of putting this is that a transaction is a contract between a client and a JMS provider that defines whether a message is sent to a destination or whether a message is received from the destination. It is not a contract between the sending client and the receiving client.*3
メッセージングと同期化処理との間には根本的な違いがあります。送信者とメッセージの受信者を密結合する代わりに、JMSはdestinationとメッセージの送信者を結合し、また、それとは別にそのdestinationとメッセージの受信者を結合します。よって、送信者と受信者はそれぞれJMSプロバイダに密結合するものの、送信者と受信者間の結合はありません。
JMSContext
生成時、createContext
メソッドの引数にJMSContext.SESSION_TRANSACTED
を与えることでトランザクションかどうかを指定出来ます。
ローカルトランザクションのcommit
およびrollback
メソッドはJMSContext
下のセッションに関連付けられます。操作の実行に同一セッションを使用する場合の単一トランザクション下では、一つ以上のキューもしくはトピック、あるいはキューとトピックの組み合わせに対し、操作の組み合わせが可能です。例えば、同一トランザクションでキューからのメッセージ受信とトピックへのメッセージ送信を行うには同一のJMSContext
を使います。
JMSローカルトランザクションの使用方法についてはUsing Local Transactionsの例を参照してください。
45.4.5 Sending Messages Asynchronously
通常、永続化メッセージ(persistent message)を送信する場合、JMSプロバイダがメッセージ送信が正常終了したことを確認するまでsend
メソッドはブロックします。非同期送信メカニズム(asynchronous send mechanism)により、アプリケーションでメッセージ送信をしたあと完了を待機する間に処理の続行が可能となります。
この機能は、現在、アプリケーションクライアントとJava SEクライアントでのみ利用可能です。
非同期メッセージ送信の際にはコールバックオブジェクトを渡します。onCompletion
を持つCompletionListener
を指定します。例えば、以下のコードはSendListener
という名前のCompletionListener
をインスタンス化しています。それから、このプロデューサーは非同期かつ引数のリスナーを使用して送信するよう指定するのにsetAsync
メソッドを使っています。
CompletionListener listener = new SendListener();
context.createProducer().setAsync(listener).send(dest, message);
CompletionListener
クラスはonCompletion
とonException
の二つのメソッドの実装が必須です。onCompletion
メソッドは送信正常終了時に呼び出され、onException
は失敗の場合に呼び出されます。これらメソッドの単純な実装例は以下のとおりとなります。
@Override public void onCompletion(Message message) { System.out.println("onCompletion method: Send has completed."); } @Override public void onException(Message message, Exception e) { System.out.println("onException method: send failed: " + e.toString()); System.out.println("Unsent message is: \n" + message); }
45.5 Using the JMS API in Java EE Applications
このセクションでは、JMS APIのアプリケーションクライアントでの使用ではなく、EJBやwebアプリケーションでの使用方法について解説します。
Java EEプラットフォーム仕様の一般的なルールは、EJBもしくはwebコンテナ内でJMS APIを使用するすべてのJava EEコンポーネントに適用されます。webおよびEJBコンテナ内のアプリケーションコンポーネントで、コネクションごとに一つ以上のアクティブ(クローズされていない)Session
を生成しようとすることは禁止されています。複数のJMSContext
オブジェクトは許可されていますが、これは単一コネクションと単一セッションの組み合わせだからです。
このルールはアプリケーションクライアントには適用されません。アプリケーションクライアントのコンテナはコネクションごとに複数のセッションの生成をサポートしています。
45.5.1 Creating Resources for Java EE Applications
Java EEのEJBやwebコンポーネントでは、アプリケーション固有のコネクションファクトリとdestinationsをアノテーションで作成出来ます。この方法で作成するリソースは、そのリソースを作成するアプリケーションでだけ参照可能です。
リソースの作成にはデプロイメント記述子を使うことも出来ます。デプロイメント記述子で指定する要素はアノテーションの要素をオーバーライドします。デプロイメント記述子に関する基本的な情報はPackaging Applicationsを参照してください。アプリケーションクライアント用のアプリケーション固有リソースを作成するにはデプロイメント記述子の利用が必須です。
destinationを作成するには、クラスに以下のような@JMSDestinationDefinition
アノテーションを付与します。
@JMSDestinationDefinition( name = "java:app/jms/myappTopic", interfaceName = "javax.jms.Topic", destinationName = "MyPhysicalAppTopic" )
name
, interfaceName
, destinationName
要素は必須です。任意でdescription
要素を指定可能です。複数のdestinationsを作成するには、@JMSDestinationDefinitions
アノテーション内にカンマ区切りで指定します。
コネクションファクトリーを作成するには、クラスに以下のような@JMSConnectionFactoryDefinition
アノテーションを付与します。
@JMSConnectionFactoryDefinition( name="java:app/jms/MyConnectionFactory" )
name
要素は必須です。任意で他の要素も指定可能で、description
や、durable subscriptions用のコネクションファクトリーを使用したい場合はclientId
を指定します。interfaceName
要素を指定しない場合、デフォルトインタフェースはjavax.jms.ConnectionFactory
となります。複数のコネクションファクトリーを作成するには、@JMSConnectionFactoryDefinitions
アノテーション内にカンマ区切りで指定します。
任意の数のコンポーネントにおいて、指定アプリケーションに一度だけアノテーションを指定する必要があります。
Note:
アプリケーションが一つ以上のメッセージ駆動ビーンを含む場合、メッセージ駆動ビーンの一つにアノテーションを設置したいかもしれません。アプリケーションクライアントなど送信コンポーネントにアノテーションを設置する場合、activation configuration specificationのdestinationLookup
プロパティを使う代わりに、トピックをルックアップするためのmappedName
要素を指定する必要があります。
コンポーネントにリソースをインジェクとする場合、@Resource
アノテーションのlookup
要素の値に上述のアノテーションで定義した要素のnama
を使います。
@Resource(lookup = "java:app/jms/myappTopic") private Topic topic;
以下のポータブルJNDI名前空間(portable JNDI namespaces)が利用可能です。アプリケーションのパッケージ方法に応じたものを使います。
java:global
: すべてのデプロイアプリケーションで利用可能なリソースを生成する場合。java:app
: 単一アプリケーション内におけるすべてのモジュールのすべてのコンポーネントで利用可能なリソースを生成する場合。java:module
: 指定モジュール内のすべてのコンポーネントで利用可能なリソースを生成する場合(例えば、EJBモジュール内のすべてのEJBなど)。java:comp
: 単一コンポーネントでだけ利用可能なリソースを生成する場合(java:module
と同等なwebアプリケーションを除く*4)。
各アノテーションの詳細についてはAPIドキュメントを参照してください。Sending and Receiving Messages Using a Simple Web ApplicationのサンプルにおけるSending Messages from a Session Bean to an MDBとUsing an Entity to Join Messages from Two MDBsはすべて@JMSDestinationDefinition
を使用しています。他のJMSのサンプルではこれらのアノテーションを使用していません。アプリケーションサーバにデプロイしないアプリケーションクライアントのみのサンプルでの通信では、個々のアプリケーションとは別途に管理者ツールでリソースを作成しておく必要があります。
45.5.2 Using Resource Injection in Enterprise Bean or Web Components
Java EEアプリケーションでは管理オブジェクトとJMSContet
オブジェクトをリソースインジェクションできます。
45.5.2.1 Injecting a ConnectionFactory, Queue, or Topic
基本的に、Java EEアプリケーションにConnectionFactory
, Queue
, Topic
をインジェクトするには@Resource
アノテーションを使います。これらのオブジェクトは、アプリケーションをデプロイする前に、管理ツール等で生成しておく事が必須となります。デフォルトのコネクションファクトリを使う場合はJNDI名がjava:comp/DefaultJMSConnectionFactory
のものを使います。
アプリケーションクライアントコンポーネントにリソースインジェクションする場合、通常はJMSリソースをstaticに宣言します。
@Resource(lookup = "java:comp/DefaultJMSConnectionFactory") private static ConnectionFactory connectionFactory; @Resource(lookup = "jms/MyQueue") private static Queue queue;
ただし、セッションビーン・メッセージ駆動ビーン・webコンポーネントで上記のアノテーションを使う場合、staticにリソースを宣言してはいけません。
@Resource(lookup = "java:comp/DefaultJMSConnectionFactory") private ConnectionFactory connectionFactory; @Resource(lookup = "jms/MyTopic") private Topic topic;
上記のコンポーネントでstaticにリソースを宣言するとランタイムエラーが発生します。
45.5.2.2 Injecting a JMSContext Object
EJBやwebコンポーネントでJMSContext
オブジェクトにアクセスするには、ConnectionFactory
リソースをインジェクトしてJMSContext
を作成するのではなく、JMSContext
をインジェクトするのに@Inject
と@JMSConnectionFactory
アノテーションを使います。デフォルトコネクションファクトリを使うには以下のようなコートとなります。
@Inject private JMSContext context1;
自前のコネクションファクトリを使うには以下のようなコードとなります。
@Inject @JMSConnectionFactory("jms/MyConnectionFactory") private JMSContext context2;
45.5.3 Using Java EE Components to Produce and to Synchronously Receive Messages
メッセージのプロデュースや同期受信を行うアプリケーションは、各種の操作を実行するのにマネージドビーン・サーブレット・セッションビーンなどのJava EEのwebやEJBコンポーネントを使用可能です。Sending Messages from a Session Bean to an MDBのサンプルではトピックへのメッセージ送信にステートレスセッションビーンを使用しています。Sending and Receiving Messages Using a Simple Web Applicationのサンプルではメッセージのコンシュームとプロデュースにマネージドビーンを使用しています。
タイムアウト指定のない同期受信はサーバリソースを占有するため、このメカニズムはwebないしEJBコンポーネントではベストのアプリケーション設計とは基本的には言えません。その代わりに、タイムアウト指定ありの同期受信か、非同期なメッセージ受信を行えるメッセージ駆動ビーンを使用してください。同期受信の詳細については、JMS Message Consumersを参照してください。
Java EEコンポーネントでJMS APIを使うことは、多くの点でアプリケーションクライアントでの使用と似ています。主な違いはリソース管理とトランザクションに関する点です。
45.5.3.1 Managing JMS Resources in Web and EJB Components
JMSのリソースにはコネクションとセッションがあり、通常はJMSContext
オブジェクトと結び付けられています。基本的には、使われなくなったJMSリソースを解放することは重要です。以下に従うべきプラクティスを示します。
- ビジネスメソッドの生存期間だけJMSリソースを維持したいのであれば、
JMSContext
の作成にはtry-with-resourcesを使います。これにより、tryブロックの最後で自動的にクローズが行われます。 - トランザクションもしくはリクエスト期間中にJMSリソースを維持するには、Injecting a JMSContext Objectで解説した方法で
JMSContext
をインジェクトします。これにより、必要でなくなった時点でリソース解放も行われます。 - EJBインスタンスの生存期間でJMSリソースを維持したい場合には、リソースの生成には
@PostConstruct
コールバックメソッドを使用し、リソースのクローズには@PreDestroy
コールバックメソッドを使います。しかし、通常はアプリケーションサーバがコネクションプールの維持を行うので、これは基本的には不要です。ステートフルセッションビーンを使用してcached stateでJMSリソースを維持したい場合、@PrePassivate
コールバックメソッドでのリソースのクローズおよび値にnull
の代入が必須で、かつ、@PostActivate
コールバックメソッドで再度の生成が必須です。
45.5.3.2 Managing Transactions in Session Beans
ローカルトランザクションではなく、JTAトランザクションを使います。コンテナマネージドかビーンマネージドのどちらかを使います。通常、送信あるいは受信を実行するビーンメソッドには、EJBコンテナがトランザクション境界を処理可能な、コンテナマネージドトランザクションを使います。コンテナマネージドトランザクションはデフォルトなので、特にこれといった設定は必要はありません。
ビーンマネージドトランザクションとjavax.transaction.UserTransaction
インタフェースのトランザクション境界メソッドを使用可能ですが、アプリケーションが特殊な要求を持ち、かつ、あなたがトランザクションのエキスパートの場合に限り、これを使うべきです。基本的には、コンテナマネージドトランザクションが最適かつ正確な振る舞いを提供します。このチュートリアルではビーンマネージドトランザクションのサンプルは提供しません。
45.5.4 Using Message-Driven Beans to Receive Messages Asynchronously
http://docs.oracle.com/javaee/7/tutorial/ejb-intro003.htm#GIPKOとhttp://docs.oracle.com/javaee/7/tutorial/jms-concepts001.htm#BNCDWのセクションではJava EEプラットフォームがメッセージ駆動ビーンという特殊なEJBをサポートしている点について解説しており、これによりJava EEアプリケーションはJMSメッセージの非同期処理が可能になっています。他のJava EE webとEJBコンポーネントでは同期的なメッセージ送受信は可能ですが非同期には出来ません。
メッセージ駆動ビーンとは、キューあるいはトピックからのメッセージを受信するメッセージリスナーです。メッセージの送信先は、任意のJava EEコンポーネント(アプリケーションクライアント・別のEJB・webコンポーネント)・アプリケーション・Java EEではないシステム、などです。
メッセージ駆動ビーンクラスには以下が必要です。
- デプロイメント記述子を使わない場合は
@MessageDriven
アノテーションの付与が必須。 - クラスは
public
必須、abstract
もしくはfinal
は禁止。 - 引数なしpublicコンストラクタが必須。
推奨だが必須でないこととして、メッセージ駆動ビーンクラスはそのクラスがサポートするメッセージ型のメッセージリスナーインタフェースを実装します。javax.jms.MessageListener
インタフェースを実装するJMS APIを使用するビーンは、以下のシグネチャを持つonMessage
が必須という意味になります。
void onMessage(Message inMessage)
メッセージが対象ビーンで処理されるために到着した段階で、ビーンのコンテナはonMessage
メソッドを呼び出します。このメソッドはメッセージ処理のビジネスロジックを持ちます。メッセージ駆動ビーンの責務はメッセージのパースとビジネスロジックの実行です。
メッセージ駆動ビーンは以下の点でアプリケーションクライアントのメッセージリスナーとは異なります。
- アプリケーションクライアントでは、
JMSContext
を生成し、それからJMSConsumer
を生成し、そのあとリスナーのアクティベートにsetMessageListener
を呼ぶことが必須です。メッセージ駆動ビーンでは、クラスとアノテーション定義だけでよく、あとはEJBコンテナが生成を行います。 @MessageDriven
アノテーションを使うビーンクラスでは、大抵の場合、ビーンないしコネクションファクトリーが使うプロパティを指定する@ActivationConfigProperty
を含むactivationConfig
要素を持ちます。これらのプロパティには、コネクションファクトリー・destination type・durable subscription・メッセージセレクタ・確認モード(acknowledgment mode)、を指定可能です。Chapter 46, "Java Message Service Examples"のサンプルではこれらのプロパティを設定しています。デプロイメント記述子でもプロパティの設定は可能です。- アプリケーションクライアントのコンテナは
MessageListener
のインスタンスを一つだけ持ち、単一のスレッド上で呼び出されます。一方で、メッセージ駆動ビーンはコンテナが設定する複数インスタンスを持つことが出来て、複数スレッドによりコンカレントな処理が可能です(とはいえ、各インスタンスは一つだけのスレッドから呼び出されますが)。よって、メッセージ駆動ビーンはメッセージリスナーよりも高速なメッセージ処理が可能です。 - ビーンマネージドトランザクションを使用する場合を除いて、メッセージ確認を指定する必要はありません。メッセージは
onMessage
メソッドが呼び出されたトランザクションでコンシュームされます。
Table 45-3はJMS仕様が定義するactivation configuration propertiesの一覧です。
Table 45-3 @ActivationConfigProperty Settings for Message-Driven Beans
プロパティ名 | 内容 |
---|---|
acknowledgeMode |
ビーンマネージドトランザクションでだけ使われる確認モード。デフォルトはAuto-acknowledge (Dups-ok-acknowledge も使用可能) |
destinationLookup |
ビーンがメッセージ受信するキューもしくはトピックのルックアップ名 |
destinationType |
javax.jms.Queue もしくはjavax.jms.Topic のどちらか |
subscriptionDurability |
durable subscriptionsの場合Durable を設定する。詳細はCreating Durable Subscriptionsを参照 |
clientId |
durable subscriptionsの場合コネクション用のクライアントIDを指定(非必須) |
subscriptionName |
durable subscriptionsの場合のサブスクリプション名 |
messageSelector |
メッセージをフィルタする文字列。詳細はJMS Message Selectorsを参照 |
connectionFactoryLookup |
メッセージ受信するビーンがJMSプロバイダに接続するのに使うコネクションファクトリのルックアップ名 |
例として、Receiving Messages Asynchronously Using a Message-Driven Beanで使われているメッセージ駆動ビーンのコードが以下になります。
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/MyQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") }) public class SimpleMessageBean implements MessageListener { @Resource private MessageDrivenContext mdc; static final Logger logger = Logger.getLogger("SimpleMessageBean"); public SimpleMessageBean() { } @Override public void onMessage(Message inMessage) { try { if (inMessage instanceof TextMessage) { logger.log(Level.INFO, "MESSAGE BEAN: Message received: {0}", inMessage.getBody(String.class)); } else { logger.log(Level.WARNING, "Message of wrong type: {0}", inMessage.getClass().getName()); } } catch (JMSException e) { logger.log(Level.SEVERE, "SimpleMessageBean.onMessage: JMSException: {0}", e.toString()); mdc.setRollbackOnly(); } } }
JMSがリソースアダプタを使用するアプリケーションサーバと統合されている場合、JMSのリソースアダプターがEJBコンテナ向けのタスクを処理します。
大抵の場合ビーンクラスはMessageDrivenContext
リソースをインジェクトします。このリソースはトランザクション管理(setRollbackOnly
など)で使用可能なメソッドなどを提供します。
@Resource private MessageDrivenContext mdc;
メッセージ駆動ビーンがローカルないしリモートインタフェースを持つことはありません。Instead, it has only a bean class.
メッセージ駆動ビーンはいくつかの点でステートレスセッションビーンと似ています。インスタンスは比較的短命で特定のクライアントに対する状態を保持しません。メッセージ駆動便のインスタンス変数はクライアントメッセージ処理中は、データベース接続・EJBオブジェクトに対する参照など、の状態は持てます。
ステートレスセッションビーン同様に、メッセージ駆動ビーンは同時に実行中の多数のinterchangeable instancesを持つことが可能です。メッセージのストリームをコンカレント処理可能とする、それらのインスタンスをコンテナでプール可能です。コンテナは時系列順に、メッセージ処理の同時並行性を損なわない場合において、メッセージ配信を試行しますが、メッセージ駆動ビーンインスタンスに配信されるメッセージの正確な順序についての保証はありません。メッセージ順序がアプリケーションにおいて不可欠の場合、メッセージ駆動ビーンのインスタンスが唯一つだけになるようアプリケーションサーバを設定してください。
メッセージ駆動ビーンのライフサイクルの詳細ついては、The Lifecycle of a Message-Driven Beanを参照してください。
45.5.5 Managing JTA Transactions
Java EEアプリケーションクライアントとJava SEクライアントは、特定のJMSセッション内で送受信をグループ化する、JMSローカルトランザクション(Using JMS Local Transactions))を使用します。webないしEJBコンテナ上で動作するJava EEアプリケーションは、大抵の場合、外部リソースアクセスとの整合性の保証にJTAトランザクションを用います。JTAトランザクションとJMSトランザクションの主要な違いは、JTAトランザクションはアプリケーションサーバのトランザクションマネージャが制御します。JTAトランザクションは分散(distributed)が可能で、JMSプロバイダとデータベースなど、同一トランザクションに複数リソースを含められます。
例えば、分散トランザクションにより、複数アプリケーションが同一データベースにアトミックな更新を行えるようになり、また、単一アプリケーションが複数データベースにアトミックな更新を行うことも可能となります。
JMS APIを使用するJava EEアプリケーションでは、データベース更新を伴うメッセージ送信もしくは受信と、その他のリソースマネージャ関連操作とを、組み合わせることが可能です。単一トランザクション内で複数のアプリケーションコンポーネント由来のリソースにアクセスが可能です。例えば、サーブレットがトランザクションを開始し、複数データベースにアクセスし、JMSメッセージを送信するEJBを実行し、Connector Architectureを使用するEISシステムを更新する別のEJBを実行し、最後にトランザクションをコミットします。ただし、アプリケーションにおいて、同一トランザクション内でJMSメッセージ送信してそのリプライを受信することは出来ません。
EJBとwebコンテナにおけるJTAトランザクションは以下の二種類のうちどちらかとなります。
- コンテナマネージドトランザクション(Container-managed transactions): 明示的な
commit
もしくはrollbak
無しにトランザクションの整合性をコンテナが制御します。コンテナマネージドトランザクションはビーンマネージドトランザクションよりも使用が容易です。EJBのメソッドに適切なトランザクション属性を指定出来ます。
Required
トランザクション属性(デフォルト)により、このメソッドは常にトランザクションの一部となります。メソッド呼び出し時にトランザクションが存在する場合、メソッドはそのトランザクションの一部となります。そうでない場合、新規トランザクションがメソッド呼び出し前に開始されてreturn時にコミットされます。詳細についてはTransaction Attributes を参照してください。 - ビーンマネージドトランザクション(Bean-managed transactions):
javax.transaction.UserTransaction
インタフェースはトランザクション境界を確定するcommit
とrollback
メソッドを提供します。ビーンマネージドトランザクションはトランザクションプログラミングに精通している場合にだけ使用を推奨します。
メッセージ駆動ビーンは、コンテナマネージドトランザクションあるいはビーンマネージドトランザクションと組み合わせて使用可能です。全メッセージの受信とトランザクションコンテキスト内での処理を保証するには、コンテナマネージドトランザクションを使用してonMessage
メソッドにRequired
トランザクション属性(デフォルト)を指定します。
コンテナマネージドトランザクションを使う場合、以下のMessageDrivenContext
のメソッドが使えます。
setRollbackOnly
: エラー処理にこのメソッドを使用する。例外発生時にsetRollbackOnly
が現行トランザクションをマークすることで、そのトランザクションはロールバックされる。getRollbackOnly
: 現行トランザクションがロールバックのマークがされているかどうかを確認するのに用いる。
ビーンマネージドトランザクションを使う場合、onMessage
メソッドへのメッセージ配信はJTAトランザクションコンテキストの外側で発生します。onMessage
メソッドの中でUserTransaction.begin
を呼ぶことでトランザクションを開始し、UserTransaction.commit
ないしUserTransaction.rollback
で終了します。Connection.createSession
メソッドの呼び出しはトランザクション内で行うことが必須です。
ビーンマネージドトランザクションを使う場合、一つ以上のトランザクションでメッセージを処理したり、メッセージ処理の一部をトランザクションコンテキストの外側で行うことが可能です。一方、コンテナマネージドトランザクションを使う場合、MDBがメッセージを受信してそれと同一のトランザクション内でonMessage
メソッドの処理を実行します。ビーンマネージドトランザクションではこの振る舞いは実現出来ません。
(webやEJBコンテナの)JTAトランザクション内でJMSContext
を作成する場合、引数で指定する値はコンテナが無視します。これはコンテナがすべてのトランザクションプロパティを管理するためです。webやEJBコンテナでJMSContext
を作成するがJTAトランザクションが無い場合、createContext
メソッドに渡す値はJMSContext.AUTO_ACKNOWLEDGE
かJMSContext.DUPS_OK_ACKNOWLEDGE
のどちらかにすべきです。
コンテナマネージドトランザクションを使う場合、基本的には、EJBのビジネスメソッド用にはRequired
(デフォルト)トランザクション属性を使います。
コンテナマネージドトランザクションを使用するメッセージ駆動ビーンを使う場合、activation configuration propertyのacknowledgeMode
を指定してはいけません。トランザクションコミット時にコンテナが自動的にメッセージの確認を実行します。
メッセージ駆動ビーンでビーンマネージドトランザクションを使う場合、メッセージ受信をビーンマネージドトランザクションの一部にすることは出来ません。メッセージ駆動ビーンをどの確認モードでメッセージ受信させたいかを指定するには、activation configuration propertyのacknowledgeMode
にAuto-acknowledge
もしくはDups-ok-acknowledge
を設定します。
onMessage
がRuntimeException
をスローする場合、コンテナはメッセージの確認処理を実行しません。この場合、JMSプロバイダは未確認メッセージを再配信します。
45.6 Further Information about JMS
JMSに関するより詳細な情報については以下を参照してください。
- Java Message Service website:
http://www.oracle.com/technetwork/java/index-jsp-142945.html - Java Message Service specification, version 2.0, available from:
http://jcp.org/en/jsr/detail?id=343
*1:In this case, the receipt and acknowledgment take place in one step, followed by the processing of the message.が原文。
*2:The NON_PERSISTENT delivery mode does not require the JMS provider to store the message or otherwise guarantee that it is not lost if the provider fails.が原文。or otherwiseがどう訳すか良くわからん。
*3:よくわからん
*4:except in a web application, where it is equivalent to java:moduleが原文