kagamihogeの日記

kagamihogeの日記です。

The Java EE 7 TutorialのJava Message Service Conceptsの章をテキトーに訳した・2

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メッセージは確認が行われるまで、正常にコンシュームされたとは見なされません。メッセージの正常なコンシュームは通常三段階で行われます。

  1. クライアントがメッセージを受信する。
  2. クライアントがメッセージを処理する。
  3. メッセージが確認される。確認は、セッション確認モードに応じて、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における同期受信は、上述の三段階ステップのメッセージコンシュームの例外となります。この場合、メッセージ処理に続いて、受信と確認は一段階で行われます。*1
  • JMSContext.CLIENT_ACKNOWLEDGE: メッセージのacknowledgeメソッドを呼ぶことでクライアントがメッセージを確認する。このモードでは、確認はセッションレベルで行われます。そのセッションでコンシュームしたすべての受信メッセージを自動的に確認します。例えば、メッセージコンシューマが10メッセージをコンシュームして5メッセージを確認したとすると、すべての10メッセージが確認されます。
    Note: Java EEプラットフォームでは、JMSContext.CLIENT_ACKNOWLEDGE設定のみアプリケーションクライアントでは使用可能ですが、webコンポーネントもしくはEJBではその限りではありません。
  • JMSContext.DUPS_OK_ACKNOWLEDGE: このオプションはJMSContextにメッセージ配信の遅延確認を指示します。これはJMSプロバイダが失敗する場合に重複メッセージを配信する可能性があるため、重複メッセージを許容可能なコンシューマでだけ使用すべきです。(JMSプロバイダがメッセージを再配信する場合、メッセージヘッダJMSRedeliveredtrue設定が必須です。)このオプションでセッションの重複を最小化することで、セッションオーバーヘッドを減らせます。

メッセージはキューから受信したが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

メッセージ送信時にいくつかのオプションを設定可能です。これらのオプションにより以下のタスク実行が可能となります。

プロデューサー生成および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);

timeToLive0を指定する場合、メッセージは有効期限切れになりません。

メッセージ送信時、有効期限を知るには現在時刻に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(TemporaryQueueTemporaryTopicオブジェクト)を生成可能で、このdestinationsを生成したコネクションの間だけ存在し続けます。以下のようなコードで、JMSContext.createTemporaryTopicJMSContext.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

Description of "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クラスはonCompletiononExceptionの二つのメソッドの実装が必須です。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 EEEJBや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 MDBUsing 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#GIPKOhttp://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-acknowledgeDups-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トランザクションは以下の二種類のうちどちらかとなります。

メッセージ駆動ビーンは、コンテナマネージドトランザクションあるいはビーンマネージドトランザクションと組み合わせて使用可能です。全メッセージの受信とトランザクションコンテキスト内での処理を保証するには、コンテナマネージドトランザクションを使用してonMessageメソッドRequiredトランザクション属性(デフォルト)を指定します。

コンテナマネージドトランザクションを使う場合、以下のMessageDrivenContextメソッドが使えます。

ビーンマネージドトランザクションを使う場合、onMessageメソッドへのメッセージ配信はJTAトランザクションコンテキストの外側で発生します。onMessageメソッドの中でUserTransaction.beginを呼ぶことでトランザクションを開始し、UserTransaction.commitないしUserTransaction.rollbackで終了します。Connection.createSessionメソッドの呼び出しはトランザクション内で行うことが必須です。

ビーンマネージドトランザクションを使う場合、一つ以上のトランザクションでメッセージを処理したり、メッセージ処理の一部をトランザクションコンテキストの外側で行うことが可能です。一方、コンテナマネージドトランザクションを使う場合、MDBがメッセージを受信してそれと同一のトランザクション内でonMessageメソッドの処理を実行します。ビーンマネージドトランザクションではこの振る舞いは実現出来ません。

(webやEJBコンテナの)JTAトランザクション内でJMSContextを作成する場合、引数で指定する値はコンテナが無視します。これはコンテナがすべてのトランザクションプロパティを管理するためです。webやEJBコンテナでJMSContextを作成するがJTAトランザクションが無い場合、createContextメソッドに渡す値はJMSContext.AUTO_ACKNOWLEDGEJMSContext.DUPS_OK_ACKNOWLEDGEのどちらかにすべきです。

コンテナマネージドトランザクションを使う場合、基本的には、EJBのビジネスメソッド用にはRequired(デフォルト)トランザクション属性を使います。

コンテナマネージドトランザクションを使用するメッセージ駆動ビーンを使う場合、activation configuration propertyのacknowledgeModeを指定してはいけません。トランザクションコミット時にコンテナが自動的にメッセージの確認を実行します。

メッセージ駆動ビーンでビーンマネージドトランザクションを使う場合、メッセージ受信をビーンマネージドトランザクションの一部にすることは出来ません。メッセージ駆動ビーンをどの確認モードでメッセージ受信させたいかを指定するには、activation configuration propertyのacknowledgeModeAuto-acknowledgeもしくはDups-ok-acknowledgeを設定します。

onMessageRuntimeExceptionをスローする場合、コンテナはメッセージの確認処理を実行しません。この場合、JMSプロバイダは未確認メッセージを再配信します。

45.6 Further Information about JMS

JMSに関するより詳細な情報については以下を参照してください。

*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が原文