WebLogic Serverでは、JMSラッパーを用いることで、EJBやサーブレットなどのJava EEコンポーネント内部でWebLogic JMSを簡単に使用できるようにするとともに、次のような多数の拡張されたユーザービリティ機能とパフォーマンス機能を提供しています。
JMS接続オブジェクトおよびセッション・オブジェクトの自動プーリング(およびメッセージ・プロデューサ・オブジェクトの一定のプーリング)
WebLogic JMSの実装と、2フェーズ・コミット・トランザクション(XAプロトコル)をサポートするサード・パーティのJMSプロバイダに対応した自動トランザクション登録
JMS接続のテストおよび接続失敗後の再接続
EJBコンテナまたはサーブレット・コンテナによって管理されるセキュリティ資格証明
次の項では、WebLogic JMSラッパーの使用方法について説明します。
WebLogic Server 12.2.1リリースは、JMS 2.0簡略化APIをサポートしているため、次のように、@Inject
アノテーションを使用して、アプリケーションにJMSContext
オブジェクトをインジェクトできます。
@Inject @JMSConnectionFactory("myJMSCF") @JMSPasswordCredential( userName="admin", password="mypassword")private JMSContext context;
@Inject
アノテーションは、コンテナがいつJMSContext
オブジェクトを作成する必要があるかを決定します。
注意:
クラスに対してインジェクションが有効になっている必要があります。使用するクラスと、それがパッケージ化されているアーカイブによっては、beans.xmlファイルの指定が必要になることがあります。詳細は、『Oracle WebLogic Serverアプリケーションの開発』のJava EEプラットフォームのContexts and Dependency Injectionの使用に関する項を参照してください。
インジェクトされたJMSContext
がNULLの場合と、アプリケーションに障害が発生した場合は、サーバー・ログを確認してください。接続ファクトリが見つからない場合、サーバー・ログでそのエラーを参照できます。サーバー・ログにエラーがない場合、可能性の高いアプリケーションの障害の原因は、beans.xml
ファイルがないことです。
JMSContext
オブジェクトをインジェクトする場合、@JMSConnectionFactory
アノテーションを使用して、接続ファクトリの製品固有のグローバルJNDIルックアップ名をコンテナで使用するように指定できます。
注意:
接続ファクトリ・アノテーションで製品固有のグローバルJNDI名を提供した場合、コンテナのデプロイメント記述子のリソース参照を使用してそれをオーバーライドすることはできません。
別の方法として、次のように、java:comp/env/res-ref-name
という形式で、完全修飾リソース参照名を指定できます。
@Inject @JMSConnectionFactory("java:comp/env/res-ref-name") private JMSContext context;
この場合、リソース参照名を適切な製品固有のグローバルJNDI名にマップするデプロイメント記述子において、<resource-ref>
要素を使用してそれを定義する必要があります。詳細は、「デプロイメント記述子を使用したラップJMSファクトリの宣言」を参照してください。
@JMSConnectionFactory
アノテーションに対して参照名が用意されていない場合、Java EEプラットフォームのデフォルトのJMS接続ファクトリ(java:comp/DefaultJMSConnectionFactory
)が使用されます。
JMSContextインジェクションは、リソース参照を使用して、接続ファクトリがコンテナ認証とアプリケーション認証のどちらを使用するかを決定することができません。そのかわりに、@JMSPasswordCredential
アノテーションを使用して必要な認証のタイプを指定できます。
@JMSPasswordCredential
アノテーションを指定すると、接続ファクトリは、パスワード認証と、指定されたユーザーおよびパスワードを使用します。@JMSPasswordCredential
アノテーションが定義されていない場合、接続ファクトリはコンテナ認証を使用します。
次の項では、JMSオブジェクトのリソースとしての宣言について説明します。
EJBのパッケージ化の詳細は、『Oracle WebLogic Server Enterprise JavaBeansバージョン2.1の開発』のEnterprise JavaBeansの実装に関する項を参照してください。サーブレットのプログラミングの詳細は、『Oracle WebLogic Server Webアプリケーション、サーブレット、JSPの開発』のサーブレットの作成と構成に関する項を参照してください。
注意:
新しいアプリケーションは、デプロイメント記述子ではなくEJB 3.0アノテーションを使用する場合があります。アノテーションについては、「アノテーションを使用したJMS宛先および接続ファクトリの宣言」で説明します。
ejb-jar.xml
またはweb.xml
ファイルにresource-ref
要素を定義することによって、EJBまたはサーブレットの一部としてJMS接続ファクトリを宣言できます。この処理によって、「ラップされた」JMS接続ファクトリが作成され、「プールによるパフォーマンスの向上」で説明している、より高度なセッション・プーリング、自動トランザクション登録、接続のモニター、およびコンテナ管理によるセキュリティ機能のメリットを得ることができます
このような接続ファクトリ要素の例を次に示します。
<resource-ref> <res-ref-name>jms/QCF</res-ref-name> <res-type>javax.jms.QueueConnectionFactory</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> </resource-ref>
この要素は、JMSオブジェクトQueueConnectionFactory
をJNDIの次の場所にバインドすることを宣言しています。
java:comp/env/jms/QCF
このJNDI名は、resource-ref
を宣言したEJBまたはサーブレットのコンテキスト内部でのみ有効です。JNDIコンテキストjava:comp/env
はこのコンテキストを表します。
この要素以外にも、その場所に配置するJMS接続ファクトリをJava EEコンテナに指示するために、対応するresource-description
要素がweblogic-ejb-jar.xml
(EJB用)ファイルまたはweblogic.xml
(サーブレット用)ファイルになければなりません。例:
<resource-description> <res-ref-name>jms/QCF</res-ref-name> <jndi-name>weblogic.jms.ConnectionFactory</jndi-name> </resource-description>
ここで指定する接続ファクトリは、グローバルJNDIツリーにすでに存在している必要があります。(この例では、組込みのWebLogic JMSサーバーを使用した場合に自動的に作成されるデフォルトJMS接続ファクトリの1つを使用しています。)同じクラスタにある別のWebLogic JMS接続ファクトリを使用するには、その接続ファクトリのJNDI名をjndi-name
要素の内側に追加します。別のベンダーまたは別のWebLogic Serverクラスタから接続ファクトリを使用するには、外部JMSサーバーを作成します。
resource-description
要素に指定したJNDI名が正しくない場合でも、アプリケーションはデプロイされます。ただし、接続ファクトリを使用しようとしたときにエラーが返されます。
JMS宛先リソースは、Webモジュール、EJBモジュール、アプリケーション・クライアント・モジュールまたはjms-destination
やresource-env-ref
記述子要素を使用するアプリケーション・デプロイメント記述子で定義できます。
注意:
新しいアプリケーションは、デプロイメント記述子ではなくEJB 3.2アノテーションを使用する場合があります。アノテーションについては、「アノテーションを使用したJMS宛先および接続ファクトリの宣言」で説明します。
トランザクション登録、プーリング、接続モニターの機能は、宛先ではなく接続ファクトリ内で実行されます。ただし、この機能は一貫性を維持し、特定のWebLogic Server構成に対するアプリケーションの依存性を低減するために有用です。対応するjms-destination
またはresource-env-ref
の記述を変更するだけで簡単に宛先を変更でき、ソース・コードを再コンパイルする必要がないからです
ejb-jar.xml
またはweb.xml
デプロイメント記述子で、jms-destination
要素を使用してJMS宛先リソースを定義できます。宛先を作成し、指定されたネームスペースに基づいて、適切なネーミング・コンテキストにバインドします。
次の例は、場所java:app/MyJMSDestination
でJNDIにバインドされるキュー宛先myQueue1
を定義します。
<jms-destination> <description>JMS Destination definition</description> <name>java:app/MyJMSDestination</name> <interface-name>javax.jms.Queue</interface-name> <destination-name>myQueue1</destination-name> <property> <name>Property1</name> <value>10</value> </property> <property> <name>Property2</name> <value>20</value> </property> </jms-destination>
jms-destination
要素およびその属性の詳細は、http://xmlns.jcp.org/xml/ns/javaee/javaee_7.xsd
のスキーマを参照してください。
JMSキューまたはトピックの宛先をJNDIツリーjava:comp/env
にバインドすることもできます。そのためには、その宛先をejb-jar.xml
またはweb.xml
のデプロイメント記述子にresource-env-ref
要素として宣言します。
resource-env-ref
記述の場合、記述子内で指定されるキューまたはトピック宛先は、グローバルJNDIツリー内にすでに存在している必要があります。宛先が存在しない場合でも、アプリケーションはデプロイされますが、宛先を使用しようとすると例外がスローされます。
このようなキュー宛先要素の例を次に示します。
<resource-env-ref> <resource-env-ref-name>jms/TESTQUEUE</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref>
この要素は、JMS宛先オブジェクトQueue
をJNDIの次の場所にバインドすることを宣言しています。
java:comp/env/jms/TESTQUEUE
参照される接続ファクトリと同様に、このJNDI名は、resource-ref
を宣言したEJBまたはサーブレットのコンテキスト内部でのみ有効です。
また、対応するresource-env-description
要素をweblogic-ejb-jar.xml
またはweblogic.xml
ファイルに定義する必要があります。これによって間接レイヤーが提供され、対応するresource-env-ref
デプロイメント記述子を変更するだけで、参照される宛先を簡単に変更できます。
<resource-env-description> <resource-env-ref-name>jms/TESTQUEUE</resource-env-ref-name> <jndi-name>jmstest.destinations.TESTQUEUE</jndi-name> </resource-env-description>
JMSモジュールをエンタープライズ・アプリケーションにパッケージ化する際は、該当するすべてのJava EEアプリケーション・コンポーネントの記述子ファイルで、モジュール内のJMSリソースを参照する必要があります。以下に、該当する記述子ファイルの例を挙げます。
WebLogicエンタープライズ記述子ファイル(weblogic-application.xml
)
WebLogicデプロイメント記述子ファイル(weblogic-ejb-jar.xml
、weblogic.xml
など)
EJB (ejb-jar.xml
)ファイル、WebApp (web.xml
)ファイルなど、すべてのJava EE記述子ファイル
エンタープライズ・アプリケーションにJMSモジュールを含める際は、アプリケーションと一緒にパッケージ化されたweblogic-application.xml
記述子ファイルで、JMSタイプのモジュール要素として各JMSモジュールを列挙し、Java EEアプリケーションのルートからの相対パスを指定する必要があります。次に、WorkflowsというJMSモジュールへの参照の例を示します。
<module> <name>Workflows</name> <type>JMS</type> <path>jms/Workflows-jms.xml</path> </module>
EJB (weblogic-ejb-jar.xml
)、WebApp (weblogic.xml
)など、weblogic-
で始まるすべての記述子ファイルでは、JMSモジュールの名前の後ろにシャープ記号(#)の区切り文字を付加し、その後ろにモジュール内のリソースの名前を指定します。たとえば、OrderQueueというキューを含むWorkflowsというJMSモジュールであれば、Workflows#OrderQueueという名前になります。
<resource-env-description> <resource-env-ref-name>jms/OrderQueue</resource-env-ref-name> <resource-link>Workflows#OrderQueue</resource-link> </resource-env-description>
<resource-link>
要素はWebLogic Server固有の要素で、JMSモジュール内に定義されたリソースを他の様々なJava EEアプリケーション・コンポーネントから参照(リンク)するために使用します。
JMSモジュール内に指定されたJMS接続ファクトリ・リソースのname
要素は、これを参照するEJBまたはWebAppアプリケーション記述子ファイルに定義されたres-ref-name
要素と一致させる必要があります。res-ref-name
要素により、java:comp/env
で使用するリソース名が、EJBから参照されるモジュールにマップされます。
JMSモジュール内に指定されたキュー宛先リソースおよびトピック宛先リソースのname
要素の場合は、これを参照するモジュール記述子ファイルに定義されたres-env-ref
要素と一致させる必要があります。
この名前は、EJBまたはWebアプリケーション・モジュールで参照されるリソースと、JMSモジュールで定義されたリソースをリンクさせるためのものです。例:
<resource-ref> <res-ref-name>jms/OrderQueueFactory</res-ref-name> <res-type>javax.jms.ConnectionFactory</res-type> </resource-ref> <resource-env-ref> <resource-env-ref-name>jms/OrderQueue</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref>
WebLogic Server 10.0以降のリリースでは、アノテーションを使用してメタデータを構成するEJB 3.0プログラミング・モデルをサポートするため、デプロイメント記述子は必要ありません。『Oracle WebLogic Server Enterprise JavaBeansの開発』のEJB 3.0で使用される標準のJDKアノテーションに関する項の説明に従い、@Resourcesアノテーションを使用してJMSオブジェクトを宣言できます。
クラスに@Resourceを適用する場合、リソースはcomp/envコンテキストで入手できます。次に、EJB、MDB、サーブレットなどのJava EEアプリケーションにWebLogic JMS宛先および接続ファクトリのリソースを注入する方法の例を示します。
次に、ラップJMSプーリング・アノテーションの例を示します。
例4-1 ラップJMSプーリング・アノテーションの例
. . . // The "name=" or "type=" are not always required, // "mappedName=" is usually sufficient. @Resource(name="ReplyQueue", type=javax.jms.Queue.class, mappedName="jms/ReplyQueue") Destination rq; . . . @Resource(name="ReplyConnectionFactory", type=javax.jms.ConnectionFactory.class, mappedName = "jms/ConnectionFactory") ConnectionFactory cf; . . .
注入されたリソース依存性は、ホストEJBまたはサーブレットがインスタンス化されると解決されます。これは、次の理由により望ましくない場合があります。
インジェクションにより、コンテナがデプロイ時に参照を解決しようとする場合、アプリケーションの正常なデプロイメントが妨げられることがあります。
アプリケーションが最初に呼び出されるまで、参照の解決を延期する場合があります。
非注入リソース参照を設定するには、クラス定義の上に@Resources
アノテーションを配置する方法があります。アプリケーションは、Beanコンテキストで参照をルックアップして実行時にこのような参照を解決できます。ベスト・プラクティスでは、繰り返されるルックアップのオーバーヘッドを回避するために、Beanまたはサーブレットで結果をキャッシュする必要もあります。例:
完全な例については、「インジェクションを使用しないEJB 3.0ラッパー」を参照してください。
例4-2 非注入リソースの例
. . . @Resources ({ @Resource(name="targetCFRef", mappedName="TargetCFJNDIName", type=javax.jms.ConnectionFactory.class), @Resource(name="targetDestRef", mappedName="TargetDestJNDIName", type=javax.jms.Destination.class) }) @Stateless(mappedName="StatelessBean") public class MyStatelessBean implements MyStateless { @Resource private SessionContext sctx; // inject the bean context private ConnectionFactory targetCF; private Destination targetDest; public void completeWorkOrder() { // Lookup the JMS resources and cache for re-use. Note that a // "java:/comp/env" prefix isn't needed for EJB3.0 bean contexts. if (targetCF == null) targetCF = (javax.jms.ConnectionFactory)sctx.lookup("targetCFRef"); if (targetDest == null) targetDest = (javax.jms.Destination)sctx.lookup("targetDestRef"); . . .
リソース参照を活用し、リソース参照のラッピングとプーリングを無効にすることが望ましい場合があります。無効にするには、デプロイメント記述子の方法を使用しますが、接続ファクトリのスタンザresource-ref
でres-type
をjava.lang.Object.class
に変更します。アノテーションを使用して、ラッピングとプーリングを無効にする方法は現在承認されていません。
この節では、WebLogic ServerがJMSオブジェクトを包むラッパーを作成するときに実際に行われる処理について説明します。たとえば、「Java EEコンテナによるJMSメッセージの送信」にあるサンプル・コードでは、接続ファクトリがJNDIツリーjava:comp/env
からルックアップされたため、実際のJMS接続ファクトリではなくWebLogic固有のラッパー・クラスのインスタンスが返されています。このラッパー・オブジェクトはJMSプロバイダへの特定の呼出しをインターセプトし、正しいJava EEの動作を挿入します。これについて以下の節で説明します。
この機能は、WebLogic JMSの実装に対して、または2フェーズ・コミット・トランザクション(XAプロトコル)をサポートするサード・パーティのJMSプロバイダに対して有効です。ラップされたJMS接続のトランザクション・コンテキスト内部でメッセージが送受信されるとき、メッセージの送受信に使用されているJMSセッションがJMSプロバイダのXA機能によってトランザクションに自動的に登録されます。コンテナ管理によるトランザクションを有効にしてEJB内部でJMSコードを呼び出し、トランザクションを暗黙的に開始したか、Bean管理のトランザクションをサポートするサーブレットまたはEJBで、UserTransaction
インタフェースを使用してトランザクションを手動で開始したかにかかわらず、この処理が行われます。
ただし、EJBまたはサーブレットがトランザクション・コンテキストの内部でメッセージの送受信を試みた場合、JMSプロバイダがXAをサポートしていないと、send()
またはreceive()
呼出しは以下の例外をスローします。
[J2EE:160055] Unable to use a wrapped JMS session in the transaction because two-phase commit is not available.
したがって、トランザクション内部でメッセージを送受信するXAをサポートしていないJMSプロバイダを使用する場合は、トランザクション・モードとしてNotSupported
を指定してEJBを宣言するか、いずれかのJTA APIを使用してトランザクションを中断します。
WebLogic JMSでは、EJBコンテナまたはサーブレット・コンテナを呼び出したときにスレッド上に存在するセキュリティ資格証明が使用されます。ただし、外部JMSプロバイダでは、ejb-jar.xml
またはweb.xml
ファイルでresource-ref
要素を使用してJMS接続ファクトリを宣言する場合、res-auth
というオプションのサブ要素があります。この要素には次に示す2つのオプションのいずれかを設定できます。
Container - res-auth
要素にContainer
を設定すると、JMSプロバイダに対するセキュリティはJava EEコンテナによって管理されます。この場合、JMS接続ファクトリが外部JMS接続ファクトリ構成MBeanを使用してJNDIツリーにマップされた後、MBeanのユーザー名とパスワードが使用されます。それ以外の場合、WebLogic Serverはユーザー名またはパスワードの指定なしでプロバイダに接続し、createConnection()
メソッドを使用して接続ファクトリにユーザー名とパスワードを渡すとエラーをスローします。
Application - res-auth
要素にApplication
を設定すると、MBeanのユーザー名またはパスワードは無視されます。そのかわりに、アプリケーション・コードでは、JMS接続ファクトリのcreateConnection(String userName, String password)
メソッドに対してユーザー名とパスワードを指定するか、ユーザー名またはパスワードが必要ない場合はそれらを指定しないcreateConnection()
のバージョンを使用する必要があります。
注意:
アプリケーションにJMSContext
オブジェクトをインジェクトするとき、接続ファクトリのJNDI名が@JMSConnectionFactory
によって指定されていれば、コンテナ認証が使用されます。ユーザー/パスワードを指定する際に、@JMSPasswordCredential
アノテーション内でユーザー/パスワードを指定する場合は、アプリケーション認証が使用されます。「@Injectアノテーションを使用したJMSContextオブジェクトの宣言」を参照してください。
JMSラッパー・クラスは、JMSプロバイダに対して確立された各接続をモニターします。これは次の2つの方法で行われます。
JMSオブジェクトExceptionListener
を接続に登録します。
一時キューまたはトピックにメッセージを送信し、それを再度受信することによって、2分ごとに接続をテストします。
Java EE仕様では、特定のJMS API呼出しをJava EEアプリケーションの内部で実行してはならないとされています。制約違反があった場合、JMSラッパーは以下の例外をスローして制約を適用します。
接続オブジェクトに対しては、createConnectionConsumer()
、createDurableConnectionConsumer()
、setClientID()
、setExceptionListener()
、stop()
の各メソッドを呼び出してはいけません。
セッション・オブジェクトに対しては、getMessageListener()
およびsetMessageListener()
メソッドを呼び出してはいけません。
コンシューマ・オブジェクト(QueueReceiver
またはTopicSubscriber
オブジェクト)では、getMessageListener()
およびsetMessageListener()
メソッドを呼び出してはいけません。
さらに、createSession()
メソッドと、関連するcreateQueueSession()
およびcreateTopicSession()
メソッドは扱いが異なります。createSession()
メソッドには、「確認応答」モードと「トランザクション化」フラグの2つのパラメータを指定します。EJBの内部で使用した場合、これらの2つのパラメータは無視されます。トランザクションが存在する場合は、JMSセッションがトランザクションに登録されます(「トランザクションの自動登録」を参照)。存在しない場合は登録されません。デフォルトでは、確認応答モードは「自動確認」に設定されます。この動作はJava EE仕様で規定されています。
注意:
この仕様のため、EJB内部からのメッセージ受信はいっそう難しくなりますが、EJB内部からメッセージを受信するには、MDBを使用することをお薦めします(『Oracle WebLogic ServerメッセージドリブンBeanの開発』を参照)。
サーブレットの内部では、createQueueSession()
およびcreateTopicSession()
のパラメータが正常に処理され、様々なメッセージ確認応答モードがすべて使用できます。
「Java EEコンテナによるJMSメッセージの送信」に示した例のようなコードをより効率的にするために、JMSラッパーは様々なセッション・オブジェクトをプールします。プールされたJMS接続は、デプロイメント記述子でresource-ref
要素を使用してJMS接続ファクトリを定義しているEJBおよびサーブレットによって使用されるセッション・プールです(「デプロイメント記述子を使用したラップJMSファクトリの宣言」を参照)。
プールされた接続は、WebLogic Server管理コンソールを使用して監視できます。詳細は、Oracle WebLogic Server管理コンソール・オンライン・ヘルプのJMSサーバー: モニター: アクティブなプール済接続に関する項を参照してください。
JMSラッパーによって接続オブジェクトおよびその他のオブジェクトが自動的にプールされるため、「Java EEコンテナによるJMSメッセージの送信」に示すようにコードを記述すると効率的ですこの例では、メッセージを送信するたびに接続ファクトリ、接続、およびセッションの各オブジェクトが作成されます。実際には、これら3つのクラスは連携して動作し、例のような使い方をした場合、これらのオブジェクトが実行する動作はほとんどプールからセッション・オブジェクトを取得することだけです。
接続ファクトリ・オブジェクトと宛先オブジェクトのJNDIルックアップでは、パフォーマンスの負荷が大きくなる可能性があります。宛先オブジェクトが外部JMS宛先MBeanを指していて、そのためローカルでないJNDIプロバイダをルック・アップする場合は、特にその可能性があります。接続ファクトリ・オブジェクトと宛先オブジェクトはスレッド・セーフであるため、作成時にEJBまたはサーブレットの内部で1回ルック・アップすることができます。毎回ルック・アップを行う必要がなく、時間を節約できます。
サーブレットの内部では、このようなルックアップをinit()
メソッドの内部で実行できます。次に接続ファクトリ・オブジェクトと宛先オブジェクトをインスタンス変数に割り当て、メッセージ送信時に再利用できます。
EJBの内部では、このようなルックアップをejbCreate()
メソッドの内部で実行し、インスタンス変数に割り当てることができます。セッションBeanの場合、Beanの各インスタンスに専用のコピーがあります。ステートレス・セッションBeanはプールされるので、このメソッドは非常に効率的でもあります(さらにJava EE仕様完全準拠です)。これは、JMS接続オブジェクトをプールすることによって、ルックアップ発生回数が大幅に削減されるからです。(これらのオブジェクトをEJBクラスの静的メンバーにキャッシュすることもできますが、Java EE仕様では推奨されていません。)
ただし、これらのオブジェクトをejbCreate()
またはinit()
メソッドの内部にキャッシュする場合は、エラーが発生したときにオブジェクトを再作成する方法をEJBまたはサーブレットが備えている必要があります。この方法が必要な理由は、WebLogic JMSのように一部のJMSプロバイダは、サーバー障害発生後に宛先オブジェクトを無効化するためです。そのため、EJBがServer Aで動作していて、JMSがServer Bで動作している場合、Server AのEJBは、Server Bのオブジェクトをサーバー回復後に再度JNDIルックアップしなければなりません。「PoolTestBean.java」の例は、このキャッシング後の再ルックアップ処理を適切に実行するサンプルEJBを示しています。
接続ファクトリ・オブジェクトや宛先オブジェクトのプールが確立された後、接続オブジェクト、セッション・オブジェクト、プロデューサ・オブジェクトなどの他のオブジェクトをejbCreate()
メソッドの内部でキャッシュしようとする場合があります。この方法は機能しますが、必ずしも最も効率的な解決策ではありません。基本的に、この方法ではセッション・オブジェクトをキャッシュから削除し、特定のEJBに永続的に割り当てます。これに対し、設計どおりにJMSラッパーを使用すると、他のEJBおよびサーブレットもセッション・オブジェクトを共有できます。さらに、ラッパーは、JMSプロバイダとの通信障害が発生した場合にJMS接続の再確立と新しいセッション・オブジェクトの作成を試みますが、セッション・オブジェクトを独自にキャッシュすると、この機能は動作しません。
JMSのsend()
またはreceive()
処理をトランザクション内部で実行する場合、EJBまたはサーブレットは自動的にJMSプロバイダをトランザクションに登録します。トランザクションは、コンテナ管理のトランザクションを持つEJBまたはサーブレットの内部で自動的に開始するか、UserTransaction
インタフェースを使用して明示的に開始できます。いずれの場合も、コンテナはJMSプロバイダを自動的に登録します。ただし、EJBまたはサーブレットが使用する基底のJMS接続ファクトリがXAをサポートしていない場合、コンテナは例外をスローします。
トランザクション登録の実行はオーバーヘッドを伴います。さらに、XA接続ファクトリを使用していても、send()
またはreceive()
メソッドをトランザクションの外部で呼び出す場合は、どのJMSプロバイダを使用するかにかかわらず処理が適切に実行されるように、コンテナはJTAトランザクションを作成してsend()
またはreceive()
メソッドをラップする必要があります。これは1フェーズのみのコミットですが、サーバーの処理速度が低下する場合があります。
したがって、JMSリソースを非トランザクション形式で使用するEJBまたはサーブレットを記述する場合、最適な方法は、XAをサポートするように構成されていないJMS接続ファクトリを使用することです。
この項では、WebLogic Server管理コンソールでの外部JMSプロバイダのサポートについて簡単に説明します。詳細は、『Oracle WebLogic Server JMSリソースの管理』の外部JMSプロバイダへのアクセスに関する項を参照してください。この機能を使用すると、外部JMSプロバイダ(別のクラスタまたはドメインにあるWebLogic Serverのリモート・インスタンスを含む)を簡単にマップして、ローカルのJNDIツリーにローカルのJMSオブジェクトとして表示できます。
外部JMSプロバイダの別の機能セットを使用すると、サード・パーティのJNDIプロバイダ内のJMS接続ファクトリまたは宛先オブジェクトと、ローカルのWebLogic Server内部のオブジェクトとのシンボリック・リンクを作成できます。この機能を使用すると、ローカルのWebLogic JNDIツリーで別のクラスタまたはドメインにあるWebLogic Serverのリモート・インスタンスを参照することもできます。
このタスクに使用する3つのシステム・モジュールMBeanがあります。
外部サーバー - 初期コンテキスト・ファクトリ、URL、追加パラメータなど、リモートのJNDIプロバイダに関する情報を格納します。これは、外部接続ファクトリMBeanと外部宛先MBeanの親です。独立したWebLogic Serverまたはクラスタをターゲットとすることができます。詳細は、Oracle WebLogic Server MBeanリファレンスのForeignServerBeanに関する項を参照してください。
外部接続ファクトリ - 外部接続ファクトリを表します。リモートのJNDIプロバイダ内の接続ファクトリの名前、その接続ファクトリのマップ先となるサーバーのJNDIツリー内の場所、およびオプションのユーザー名とパスワードを格納します。ユーザー名とパスワードは、外部接続ファクトリをEJBまたはサーブレットのresource-reference
の内側に、Containerモードauthenticationを指定して使用している場合にのみ、使用されます。その場合、親の外部接続ファクトリMBeanの対象となっている各WebLogic Serverインスタンスに非レプリケートJNDIオブジェクトが作成されます(クラスタの各ノードにJNDIオブジェクトを作成するには、そのクラスタを親MBeanの対象にします)。詳細は、Oracle WebLogic Server MBeanリファレンスのForeignConnectionFactoryBeanに関する項を参照してください。
外部宛先 - 外部宛先を表します。外部JNDIプロバイダでルックアップする名前と、その名前をローカルのサーバーにマップする名前を格納します。
次のファイルは、EJBが呼び出されたときにWebLogic JMSラッパー関数を使用してトランザクション・メッセージ(sendXATransactional
)を送信する、簡単なステートレスEJBセッションBeanを記述しています。この例ではセッションBeanを使用していますが、同じXML記述子とBeanクラスを(ほとんど変更を加えることなく)メッセージドリブンBeanにも使用できます。
このセクションにはEJBコンポーネントを記述します。この節に示されている「JMSラッパー」コード・スニペットでは、ラップされたJMS接続ファクトリ(QueueConnectionFactory
)と参照されるJMS宛先(TESTQUEUE
)のresource-ref
およびresource-env-ref
要素をこのセクションで宣言しています。
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <?xml version="1.0"?> ... <ejb-jar> <enterprise-beans> <session> <ejb-name>PoolTestBean</ejb-name> <home>weblogic.jms.pool.test.PoolTestHome</home> <remote>weblogic.jms.pool.test.PoolTest</remote> <ejb-class>weblogic.jms.pool.test.PoolTestBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <resource-ref> <res-ref-name>jms/QCF</res-ref-name> <res-type>javax.jms.QueueConnectionFactory</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> </resource-ref> <resource-env-ref> <resource-env-ref-name>jms/TESTQUEUE</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>PoolTestBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
このセクションでは、キュー接続ファクトリおよびキュー宛先に対応するresource-description
要素を宣言します。この要素は、その位置に格納するJMS接続ファクトリと宛先をJava EEコンテナに指示します。
<!DOC<weblogic-ejb-jar xmlns="http://www.bea.com/ns/weblogic/920" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/920 http://www.bea.com/ns/weblogic/920/weblogic-ejb-jar.xsd"> ... <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>PoolTestBean</ejb-name> <stateless-session-descriptor> <pool> <max-beans-in-free-pool>8</max-beans-in-free-pool> <initial-beans-in-free-pool>2</initial-beans-in-free-pool> </pool> </stateless-session-descriptor> <resource-description> <res-ref-name>jms/QCF</res-ref-name> <jndi-name>weblogic.jms.XAConnectionFactory</jndi-name> </resource-description> <resource-env-description> <res-env-ref-name>jms/TESTQUEUE</res-env-ref-name> <jndi-name>TESTQUEUE</jndi-name> </resource-env-description> <jndi-name>PoolTest</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
このセクションでは、PoolTest
Beanに使用するリモート・インタフェースを定義します。sendXATransactional
というメソッドを1つ宣言します。
package weblogic.jms.pool.test; import java.rmi.*; import javax.ejb.*; public interface PoolTest extends EJBObject { public String sendXATransactional(String text) throws RemoteException; }
このセクションでは、PoolTest
Beanに使用する「ホーム」インタフェースを定義します。EJB仕様ではこのセクションは必須です。
package weblogic.jms.pool.test; import java.rmi.*; import javax.ejb.*; public interface PoolTestHome extends EJBHome { PoolTest create() throws CreateException, RemoteException; }
このセクションでは実際のEJBコードを定義します。sendXATransactional
メソッドが呼び出されたときは常にメッセージを送信します。
package weblogic.jms.pool.test; import java.lang.reflect.*; import java.rmi.*; import javax.ejb.*; import javax.jms.*; import javax.naming.*; import javax.transaction.*; public class PoolTestBean extends PoolTestBeanBase implements SessionBean { private SessionContext context; private QueueConnectionFactory qcf; private Queue destination; public void ejbActivate() { } public void ejbRemove() { } public void ejbPassivate() { } public void setSessionContext(SessionContext ctx) { context = ctx; } private void lookupJNDIObjects() throws NamingException { InitialContext ic = new InitialContext(); try { qcf = (QueueConnectionFactory)ic.lookup ("java:comp/env/jms/QCF"); destination = (Queue)ic.lookup("java:comp/env/jms/TESTQUEUE"); } finally { ic.close(); } } public void ejbCreate() throws CreateException { try { lookupJNDIObjects(); } catch (NamingException ne) { throw new CreateException(ne.toString()); } } public String sendXATransactional(String text) throws RemoteException { String id = "Not sent yet"; try { if ((qcf == null) || (destination == null)) { lookupJNDIObjects(); } QueueConnection connection = qcf.createQueueConnection(); try { QueueSession session = connection.createQueueSession (false, 0); TextMessage message = session.createTextMessage (text); QueueSender sender = session.createSender(destination); sender.send(message); id = message.getJMSMessageID(); } finally { connection.close(); } } catch (Exception e) { // Invalidate the JNDI objects if there is a failure // this is necessary because the destination object // may become invalid if the destination server has // been shut down qcf = null; destination = null; throw new RemoteException("Failure in EJB: " + e); } return id; } }
JMS接続ファクトリと宛先のリソースを宣言すると、そのリソースを使用して、EJBまたはサーブレット内部でJMSメッセージの送受信を行うことができます。次の項では、メッセージの送信方法の例を示します。
次のコード・フラグメントは、java:comp/env
JNDIツリーにマップする場合にメッセージを送信します。
例4-3 comp/envを使用したメッセージの送信
. . . InitialContext ic = new InitialContext(); QueueConnectionFactory qcf = (QueueConnectionFactory)ic.lookup("java:comp/env/jms/QCF"); Queue destQueue = (Queue)ic.lookup("java:comp/env/jms/TESTQUEUE"); ic.close(); QueueConnection connection = qcf.createQueueConnection(); try { QueueSession session = connection.createQueueSession(0, false); QueueSender sender = session.createSender(destQueue); TextMessage msg = session.createTextMessage("This is a test"); sender.send(msg); } finally { connection.close(); }
これはJava EE仕様に準拠した標準的なコードであり、Java EEを適切にサポートする任意のEJBまたはサーブレット製品上で動作します。違いは、WebLogic Serverでは様々なオブジェクトが背後でプールされるため、さらに効率的に実行できる点です(「プールされたJMS接続オブジェクト」を参照)。
このサンプル・コードでは、try...finally
ブロックを使用して、ブロックの内側にある文で例外が発生した場合でも、JMS接続オブジェクトのclose()
メソッドが確実に実行されるようにしている点に注意してください。接続プーリングが行われていない場合は、接続をクローズするため、およびサーバー・リソースの消費を避けるために、このブロックが必要です。しかし、WebLogic Serverはこのコードによって作成されたオブジェクトの一部をプールに入れるため、close()
を呼び出すことがさらに重要です。そうしないと、EJBコンテナまたはサーブレット・コンテナに、オブジェクトをプールにいつ戻すかが通知されません。
また、JMS APIのトランザクションXA拡張機能はこのサンプル・コードでは使用されていません。かわりに、トランザクション・コンテキスト内でJMSコードが使用されている場合、コンテナはそれらの拡張機能を内部的に使用します。しかし、XAが内部的に使用されるかどうかにかかわらず、ユーザーが記述するコードは同じであり、JMS XAクラスを使用しません。これはJava EEの仕様です。EJBコードをこのように記述すると、デプロイメント記述子を変更するだけで、トランザクションが存在する環境または非トランザクション環境でEJBを実行できます。
注意:
ラップされたJMS接続ファクトリは、resource-ref
機能を使用して取得し、java:comp/env/jms
JNDIツリー・コンテキストを使用してルックアップします。EJBでjavax.jms
XAのトランザクションXAインタフェースを使用することはできません。
次のコード・フラグメントは、変数に依存関係インジェクションを使用した場合にメッセージを送信します。
例4-4 依存関係インジェクションを使用したメッセージの送信
package test; // Example injected annotation. import javax.annotation.Resource; import javax.ejb.*; import javax.jms.*; @Stateless(mappedName="StatelessBean") public class MyStatelessBean implements MyStateless { @Resource(mappedName="myDestJNDIName") private Destination dest; @Resource(mappedName="weblogic.jms.XAConnectionFactory") private ConnectionFactory connectionFactory; public void completeWorkOrder() { Connection con = null; Session session = null; MessageProducer sender = null; try { System.out.println("completeWorkOrder called!"); con = connectionFactory.createConnection(); session = con.createSession(true, Session.AUTO_ACKNOWLEDGE); sender = session.createProducer(null); Message message = session.createTextMessage("work order complete!"); sender.send(dest, message); } catch(Exception e) { throw new EJBException("Exception sending message: " + e, e); } finally { try { if (con != null) con.close(); } catch(Exception e) { e.printStackTrace(); } } } }
この例では、注入されないリソースを参照するMDBのEJB 3.0アノテーションを示します。参照は、MDBインスタンスがインスタンス化されるときではなく、MDBが呼び出されると実行時に解決されます。
例4-5 非注入MDBの例
package test; import javax.annotation.Resources; import javax.annotation.Resource; import javax.naming.*; import javax.ejb.*; import javax.jms.*; import javax.ejb.ActivationConfigProperty; @MessageDriven( name = "MyMDB", mappedName = "JNDINameOfMDBSourceDest", activationConfig = { // the JMS interface type for the MDB destination, either javax.jms.Topic or javax.jms.Queue @ActivationConfigProperty( propertyName = "destinationType", propertyValue = "javax.jms.Queue"), // optionally specify a connection factory // there's no need to specify a connection factory if the source // destination is a WebLogic JMS destination @ActivationConfigProperty( propertyName = "connectionFactoryJndiName", propertyValue = "JNDINameOfMDBSourceCF"), }) // resources that are not injected @Resources ({ @Resource(name="targetCFRef", mappedName="TargetCFJNDIName", type=javax.jms.ConnectionFactory.class), @Resource(name="targetDestRef", mappedName="TargetDestJNDIName", type=javax.jms.Destination.class) }) public class MyMDB implements MessageListener { // inject a reference to the MDB context @Resource private MessageDrivenContext mdctx; // cache targetCF and targetDest for re-use (performance) private ConnectionFactory targetCF; private Destination targetDest; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void onMessage(Message message) { Connection jmsConnection = null; try { System.out.println("My MDB got message: " + message); if (targetCF == null) targetCF = (javax.jms.ConnectionFactory)mdctx.lookup("targetCFRef"); if (targetDest == null) targetDest = (javax.jms.Destination)mdctx.lookup("targetDestRef"); jmsConnection = targetCF.createConnection(); Session s = jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer mp = s.createProducer(null); if (message.getJMSReplyTo() != null) mp.send(message.getJMSReplyTo(), s.createTextMessage("My Reply")); else mp.send(targetDest, message); } catch (JMSException e) { throw new EJBException(e); } finally { // Return JMS resources to the resource reference pool for later re-use. // Closing a connection automatically also closes its sessions, etc. try { if (jmsConnection != null) jmsConnection.close(); } catch (JMSException ignored) {}; } } }