Java Message Service (JMS) の仕様は、Javaに基づいたP2P (Point-to-Point) およびP/S (Publish/Subscribe) のメッセージングの標準を明確に述べています。Sunは現在、ライセンスを受けた12のJMSインプリメンターとライセンスを受けていない16のインプリメンターをリストアップしています。JMSはアーキテクチャーとしてはJava Database Connectivity (JDBC) APIに類似しています。というのは、どちらも定義されているクラスの数は少ないが、定義されているインターフェースが多いからです。これらのインターフェースは実装を念頭において定義されており、それらに準拠して実装を行えば、振る舞いは同じになる筈です。
ほとんどのデータベースにおいて、振る舞い上の類似性は、JDBCインターフェースの実装までにとどまります。SQLに準拠している程度の差と、ベンダー独自の手続き型SQL拡張 (OracleのPL/SQLやSybaseのTransact-SQLなど) の使用により、データベース・サービスにアクセスして使用するために作成されるコードに、かなりの差異が生じる可能性があります。
しかし、JMSでは、そのようなことはありません。わずかな骨折りと、今回の記事で私が推薦する方法を採用することで、使用しているベンダーの実装に煩わされることなく、JMSクライアント・コードを作成することができます。ここでは、皆さんがJMSメッセージ処理の基本を理解していることを前提として説明しますが、まず、基本的な概念と用語についての、簡単な説明から始めましょう。
メッセージの送受信の基礎は接続です。接続によって、JVMの外部のリソースの割り当てが行われます。JMSベンダーは通常、少なくともP2Pトランザクション用のQueueConnection とP/Sトランザクション用のTopicConnection を実装します。これらの接続によって、Session が提供されます。これは、メッセージ送受信を管理するためのコンストラクト(構成要素)です。
P2Pトランザクション管理の基本コンストラクトはQueueSender とQueueReceiver であり、P/Sトランザクション管理の基本コンストラクトはTopicSubscriber とTopicPublisher です。トピック・オブジェクトとキュー・オブジェクトによって、各メッセージのターゲットとソースを示す特定の情報がカプセル化されます。図1は、この階層を示しています。
図1 JMSのクラス階層
要求/応答サポート・クラスやアプリケーション・サーバーに固有の機能など、その他のコンストラクトも、JMS標準の中に見出すことができます (参考文献を参照)。
接続は、JMSサーバーとの対話に関して、入口となるものなので、接続インターフェースの実装はそれぞれ、各JMSサーバーのインスタンスに対する接続方法を知る必要があります。基となる接続プロトコルの細かな点はベンダーごとに異なるため、アクティブな接続の設定に必要な情報もベンダーごとに異なります。
ほとんどのベンダーにおいて、動的な接続設定が可能です。これはつまり、ほとんどのベンダーは、接続クラスのコンストラクターをpublicとして定義しており、必要な接続情報をプログラマーが定義できるようにしているということです。また、ほとんどのベンダーは、呼び出されると一つ接続を戻すファクトリー・クラスを提供しています。
接続ファクトリーの場合、ファクトリー・クラスは、ベンダー独自の接続情報と共に事前にロードされている接続を、戻すことができます。ベンダー定義のファクトリー・クラスは、プログラマーが接続パラメーターを設定するためのメソッドを提供すると思われます。これらの接続パラメーターによって、ファクトリーから戻される接続の性質が決定されます。
これらをより具体的に説明するために、QueueConnection とQueueConnectionFactory の実装のためのコンストラクター、接続ファクトリー、設定メソッドについて見てみましょう。(いくつかのケースにおいては多重定義されたコンストラクターも多くありますが、ここでは各ケースに対して1つのコンストラクターについてのみ説明します。)
IIT SwiftMQ 2.1.3 QueueConnectionFactoryコンストラクター・パラメーター
-
java.lang.String socketFactory: ソケット・ファクトリーのクラス名 -
java.lang.String hostname: JMSサーバーのホスト名 -
int port: JMSサーバーのポート -
long keepalive: 保持時間
以下のコードは、SwiftMQQueueConnectionFactory オブジェクトの作成方法を示しています。
QueueConnectionFactory qcf = (QueueConnectionFactory) new
com.swiftmq.jms.ConnectionFactoryImpl
("com.swiftmq.net.PlainSocketFactory", "myhost",4001,60000);
|
Progress SonicMQ 3.5 QueueConnectionコンストラクター・パラメーター
-
java.lang.String brokerURL: URL ([protocol://]hostname[:port] 形式) -
java.lang.String connectID: 接続を識別するIDストリング -
java.lang.String username: デフォルトのユーザー名 -
java.lang.String password: デフォルトのパスワード
以下は、Progress SonicMQQueueConnectionFactory オブジェクトを作成するサンプル・コードです。
progress.message.jclient.QueueConnection queueConnection = new
progress.message.jclient.QueueConnection("tcp://myhost:2506",
"ServiceRequest", "username", "password");
|
MQSeries (MA88)
最後の例は、IBM MQSeriesの実装です。MQSeriesは接続コンストラクターを使用しません。その代わりに、動的な接続を行うために、接続ファクトリーの作成が必要です。このファクトリーは接続のためのメソッドを提供します。パラメーターなしのコンストラクターを作成するコードを以下に示します。
MQQueueConnectionFactory = new MQQueueConnectionFactory(); |
接続ファクトリーのコンストラクターにはパラメーターがないため、ファクトリーには、その提供する接続のプロパティーを管理する際に呼び出すことができる変更(mutator)メソッドがあります。
-
setTransportType(int x): 以下のいずれかのオプションにトランスポート・タイプを設定します。-
JMSC.MQJMS_TP_BINDINGS_MQ: MQSeriesサーバーがクライアントと同じホスト上にある場合に使用されます。 -
JMSC.MQJMS_TP_CLIENT_MQ_TCPIP: MQSeriesサーバーがクライアントと異なるホスト上にある場合に使用されます。
-
-
setQueueManager(String x): キュー・マネージャー名を設定します。 -
setHostName(String hostname): ホスト名を設定します (クライアントのみ)。 -
setPort(int port): クライアント接続用のポートを設定します。 -
setChannel(String x): 使用するチャネルを設定します (クライアントのみ)。
MQSeriesQueueConnectionFactory を作成し、特定のキュー・マネージャーへの接続を取得するサンプル・コードを以下に示します。
com.ibm.mq.jms.MQQueueConnectionFactory factory = new
com.ibm.mq.jms.MQQueueConnectionFactory();
factory.setQueueManager("QMGR");
com.ibm.mq.jms.MQQueueConnection connection =
factory.createQueueConnection();
|
以上見てきたように、各ベンダーはそれぞれ独自の異なる接続パラメーター・セットを使用しています。では、作成するコードにおいて、これらすべてをどのようにして透過的にサポートすればよいのでしょうか。標準的なソリューションとしては、ネーミング・サービスを使用して、事前に設定されたConnectionFactory を維持しておく方法があります。実行時において、コードがそのConnectionFactory を検索し、そこから戻された接続をJMSサーバーに透過的に接続することができます。正確に設定された接続ファクトリーをネーミング・サービスにおいて簡単に維持できるようになるため、コードの維持と再作成は必要なくなります。
Java Naming and Directory Interface (JNDI) は、ネーミング・サービスを接続する最も一般的な方法です。JNDIは、実装されるインターフェース・セットを定義するという点でJMSに似ています。また、JNDIを実装するすべてのネーミング・サービスは、1つの標準APIによってアクセスすることができます。
JNDIは、ベンダーに依存しないネーミング・サービスへのアクセス方法を提供するため、ここで示されるようなベンダーに依存しないコードを記述する際の中心となります。JNDIによって、前述のようなベンダー独自の実装を気にする必要はなくなり、ネーミング・サービスから的確なオブジェクトを検索するためのコードの記述のみに集中できるようになります。
接続ファクトリーを作成してそれを事前に設定し、ネーミング・サービスにバインドすることによって、メッセージング・サービスにおけるベンダー固有の接続パラメーターを隠すことができます。コードに関する限り、一般的なjavax.jms.Connection オブジェクトを使用していれば、ベンダーの実装はインターフェースの背後に隠すことができます。
JMSの仕様では、アドミニストレーターによって作成され、JMS管理オブジェクトとしてJMSクライアントによって使用される設定情報を持つオブジェクトについて言及しています。管理オブジェクトはJNDIに依存しませんが、JNDIネームスペースへのバインドおよびそこでの検索はいうまでもなく可能です。
リスト1と2は、JMSサーバー (この場合SwiftMQ) に接続するための2つのメソッドを示しています。一方はベンダー依存のコードを使用し、もう一方はベンダーに依存しないコードを使用しています。
リスト1. ベンダー依存の接続メソッド
1.QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) new
com.swiftmq.jms.ConnectionFactoryImpl
("com.swiftmq.net.PlainSocketFactory", "localhost",4001,60000);
2.QueueConnection queueConnection =
queueConnectionFactory.createQueueConnection();
|
リスト2. ベンダーに依存しない接続メソッド
1.Properties p = new Properties();
2.p.put(Context.INITIAL_CONTEXT_FACTORY,
"com.swiftmq.jndi.InitialContextFactoryImpl");
3.p.put(Context.PROVIDER_URL,"smqp://localhost:4001");
4.ctx = new InitialContext(p);
5.qcf = (QueueConnectionFactory)ctx.lookup("MyQCF");
6.oQueueConnection queueConnection =
queueConnectionFactory.createQueueConnection();
|
皆さんは、ベンダーに依存しないコードの方が少し行が多いことにまずお気付きになるでしょう。これはネーミング・サービスへの接続が必要となるためです。ネーミング・サービスへの接続が必要とされるのは、プログラム全体で1回限りで、これらの行はそのためのものです。(リモートのコンテキストの使用が必要な場合は、その度にそれをインスタンス化するのではなく、このネーミング・サービスを再利用してください。)
ネーミング・サービスとの対話、およびその準備は、ベンダーに依存しないメッセージング・コードの記述にとってきわめて重要です。ベンダーに依存するコード例では、SwiftMQ実装のQueueConnectionFactory コンストラクターを使用して、接続を提供するファクトリーを作成しました。この実装の場合、リスト1の1行目に示されているように、ベンダー独自のクラスを置くだけでなく、QueueConnectionFactory コンストラクターにベンダー固有のパラメーターを渡すことも必要です。
ベンダーに依存しないコードの例では、ベンダー固有のコードはありませんが、最初のコンテキスト・ファクトリー、ネーミング・サービスのプロバイダーのURL、およびQueueConnectionFactory のバインド名が必要となります。バインド名に関しては、適切なネーミング・サービスを維持することによって、オブジェクトをどのベンダーのJNDIツリーにもバインドすることができ、ベンダーが変わってもバインド名の変更は必要ありません。JNDIコンテキストに関しては、パラメーター・ストリング (リスト2の2行目と3行目) をプロパティー・ファイルに保存し、必要に応じて読み込む方法が一般的です。このように、JMSベンダーの変更は、プロパティー・ファイルの簡単な変更によって可能です。
また、この方法によって、ネーミング・サービスに関する柔軟性と移植性が得られる点も注目に値するでしょう。多くのJMSベンダー (FioranoやSwiftMQなど) はそれぞれJNDIサービスを提供していますが、ネーミング・サービスとJMSサービスとを切り離すこともできます。(たとえば、接続ファクトリーを中央のLDAPサーバーに格納することもできます。)
以下は、さまざまなJNDI接続におけるプロパティー・ファイルのエントリー例です。
SwiftMQ JNDIサービス
-
java.naming.provider.url=smqp://myhost:4001 -
java.naming.factory.initial=com.swiftmq.jndi.InitialContextFactoryImpl
IBM WebSphere JNDIサービス
-
java.naming.provider.url=iiop://myhost:9001 -
java.naming.factory.initial=com.ibm.websphere.naming.WsnInitialContextFactory
iPlanetディレクトリー・サーバー (LDAP)
-
java.naming.provider.url=ldap://myhost:389 -
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
BEA WebLogic JNDIサービス
-
java.naming.provider.url=t3://myhost:7001 -
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
File System JNDIサービス
-
java.naming.provider.url=file:/tmp/stuff -
java.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory
ソース・コードは直接にはベンダー・クラスを参照しませんが、ベンダー・クラスはその名前によって動的にJVMにロードされるため、実行時にはプログラムのクラス・パスに置かれる必要があります。これは、JNDIクラスとJMSクラスの両方に共通しています。
ここまでで、コードを再コンパイルすることなく、JNDIサービスに接続して、それぞれ異なるJNDIおよびJMSの実装から接続を取得する方法がわかりました。プロパティー・ファイルの設定方法とJNDI接続を可能にする際のその役割について考えながら、今までのところをまとめてみましょう。
JNDI接続のための基本クラスはjavax.naming.InitialContext です。InitialDirContext など、ディレクトリー操作に固有のInitialContext のサブクラスもありますが、この一般的なクラスを使用することができます。InitialContext が作成されると、その環境 (システム・プロパティーまたはアプレット・パラメーター)からJNDIパラメーター値を得るか、または、特定のjndi. プロパティー・ファイルを探します。
この操作がJ2SE 1.3.1 javadocでどのように説明されるかを以下に示します。
JNDIは、以下の2つのソースから順番に値をマージすることによって、各プロパティーの値を決定します。
- コンストラクターの環境パラメーター、および (適切なプロパティーに対する) アプレット・パラメーターとシステム・プロパティーからのプロパティーのうち、最初の出現
- アプリケーション・リソース・ファイル (jndi. プロパティー)
ここまで、プロバイダーのURLとInitialContext ファクトリー名の2つのパラメーターについて見てきました。しかし実際には、もっと多くのプロパティーがあります。上記の2つのパラメーターの他に最も一般的なものは、JNDIストアに対するアクセスをセキュアなものとするためのユーザー名とパスワードです。以下にそのパラメーターを示します。
-
java.naming.security.principal(ユーザー名) -
java.naming.security.credentials(パスワード)
すべてのアプリケーションの実行時構成パラメーターを1つのアプリケーション・プロパティー・ファイルに置き、JNDIパラメーターもそこに置く方法を推奨します。1つの場所にすべてのパラメーターを置くことによって、不確実性を解消することができます。アプリケーション・プロパティー・ファイルのロードには、いくつかの方法があります。ここでは2つの例を示します。その1つは、ファイルをリソース・バンドルでロードする方法であり、もう1つは、プロパティー・ファイルの名前と場所をコマンド行パラメーターとして渡す方法です。これらの方法には、それぞれ異なる利点があります。
ファイルの場所をコマンド行パラメーターとして渡す方法は、コード作成における最も簡単な方法です。アプリケーションの起動を変更するだけで、パラメーターの変更が可能です。
リソース・バンドルとしてファイルをロードする方法には2つの利点があります。
- まず、JVMのロケールに従い、さまざまなリソース・バンドルをロードすることができます。たとえば、application_en_US. プロパティー・ファイルはニューヨークのJNDIサービスを示し、application_fr. プロパティー・ファイルはパリのJNDIサービスを示します。
- また、リソース・バンドルからプロパティーをロードする方法は、プロパティー・ファイルをロードすることに関してアーキテクチャーおよびプラットフォームに依存しません。リソース・バンドルはクラスパスからロードすることができるため、コードは、JVMのコマンド行パラメーターの読み込みが可能であるかどうかに左右されません。また、EJBコンポーネントなどのいくつかのコンポーネントは、ファイルI/Oを直接使用しないため、リソース・バンドルの使用は、プロパティー・ファイルのコンテンツをロードする際の一層便利な方法となります。
私は、環境設定の競合を回避するために、必ず、プロパティー・ファイルから読み込んだJNDI値によってプロパティー・インスタンスを設定するようにしています。
このセクションのコード・リストは、プロパティーの初期化について両方のやりかた (コマンド行パラメーターとリソース・バンドル) と一般的なJNDI検索を示しています。リスト3に示したPropertiesManagement.propertiesという設定ファイルのサンプルから見ていきましょう。
リスト3. PropertiesManagement.properties
java.naming.provider.url=smqp://localhost:4001
java.naming.factory.initial=com.swiftmq.jndi.InitialContextFactoryImpl
java.naming.security.principal=admin
java.naming.security.credentials=secret
com.nickman.neutraljms.QueueConnectionFactory=myQueueConnectionFactory
com.nickman.neutraljms.TopicConnectionFactory=myQueueConnectionFactory
com.nickman.neutraljms.Queue=testqueue@router1
com.nickman.neutraljms.Topic=testtopic
|
ファイル内の最初の4項目は、JNDI環境プロパティーです。ここでは、わかりやすくするために、認証プロパティーを追加しました。次の4項目は、JMSオブジェクトがバインドされるネーミング・サービス名です。これらの名前は、接続ファクトリー、キュー、トピックの検索に使用します。LDAPを使用している場合、名前は若干複雑になります。以下のようなものになります。
-
java.naming.provider.url = ldap://myhost:389/o=nickman.com -
com.nickman.neutraljms.QueueConnectionFactory =cn=myQueueConnectionFactory,ou=jmsTree
次に、プロパティー・ファイルを読み込むためのコードについて考えます。前述のように、JNDI接続パラメーターの決定には2つの方法があります。リスト4は、JNDIプロパティーの検索のためのサンプル・コードです。
リスト4. JNDIプロパティーの検索
package com.nickman.jndi;
import javax.naming.*; // For JNDI Interfaces
import java.util.*;
import java.io.*;
import javax.jms.*;
public class PropertiesManagement {
Properties jndiProperties = null;
Context ctx = null;
public static void main(String[] args) {
PropertiesManagement pm = new PropertiesManagement(args);
.
.
public PropertiesManagement(String[] args) {
jndiProperties = new Properties();
if(args.length>0) {
try {
loadFromFile(args[0]);
.
.
} else {
try {
loadFromResourceBundle();
.
.
private void loadFromFile(String fileName) throws Exception {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileName);
jndiProperties.load(fis);
} finally {
try { fis.close(); } catch (Exception erx){}
}
}
private void loadFromResourceBundle() throws Exception {
String key = null;
String value = null;
ResourceBundle rb =
ResourceBundle.getBundle("PropertiesManagement");
Enumeration enum = rb.getKeys();
while(enum.hasMoreElements()) {
key = enum.nextElement().toString();
value = rb.getString(key);
jndiProperties.put(key, value);
}
}
|
リスト4は、2つの異なる方法でプロパティー・ファイルをロードするためのコードを示しています。コマンド行パラメーターが渡された場合、コードは、それが完全に適格なプロパティー・ファイル名であると想定し、loadFromFile(String fileName) メソッドを使用してプロパティーがロードされます。
クラスは以下のように呼び出すことができます。
java com.nickman.jndi.PropertiesManagement c:\config\PropertiesManagement.properties |
コマンド行パラメーターが渡されない場合、コードはloadFromResourceBundle() メソッドを呼び出します。このメソッドはCLASSPATH上でプロパティー・ファイルを見つけるため、このファイルのディレクトリーをクラスパスに置く必要があります。どちらの場合も、プロパティーはプロパティー変数jndiPropertiesにロードされます。
リスト5は、JNDIサービスへの接続を示しています。
リスト5. JNDI接続
public void connectToJNDI() throws javax.naming.NamingException {
// jndiProperties was loaded from PropertiesManagement.properties
ctx = new InitialContext(jndiProperties);
System.out.println("Connected to " +
ctx.getEnvironment().get(Context.PROVIDER_URL));
}
|
上記の接続コードは、きわめて簡単なものです。jndiProperties 変数はInitialContext コンストラクターに渡され、結果のContext がJNDIサービスへの「ハンドル」となります。インターフェースjavax.naming.Context に、利用可能なすべての環境プロパティーを示す定数のセットが含まれている点に注意しましょう。
リスト6で示されているように、Context の設定によって、JMSオブジェクトの検索を続けることができます。
リスト6. JNDI検索
public QueueConnectionFactory lookupQueueConnectionFactory()
throws javax.naming.NamingException {
return
(QueueConnectionFactory)ctx.lookup(jndiProperties.get
("com.nickman.neutraljms.QueueConnectionFactory").toString());
}
public Queue lookupQueue() throws javax.naming.NamingException {
return
(Queue)ctx.lookup(jndiProperties.get
("com.nickman.neutraljms.Queue").toString());
}
|
検索は、Context のlookup(String name) メソッドを呼び出し、オブジェクトがバインドされる名前を渡すという簡単な作業です。戻されたオブジェクトは、正しいクラスにキャストされなければなりません。この場合それは、標準javax.jms インターフェースの1つです。
javax.jms.Destination は、メッセージが送信される特定のターゲットをカプセル化するインターフェースです。Queue およびTopic のインターフェースはどちらもDestination インターフェースを拡張します。Destination はJMS被管理オブジェクトであるため、Queue とTopic もJMS被管理オブジェクトになります。
JMS APIにはTopic セッション・クラスとQueue セッション・クラスにおける2つのメソッドが含まれることに皆さんはお気付きでしょう。
-
Topic TopicSession.createTopic(java.lang.String topicName) -
Queue QueueSession.createQueue(java.lang.String topicName)
ここで次のような疑問が生じます。それは「単一のストリングでキューやトピックを参照できるのに、なぜわざわざJNDIでキューやトピックを保存しなければならないのか」という疑問です。その理由は少し難しいため、javadocから直接引用して説明しましょう。
Destination オブジェクトは、プロバイダー固有のアドレスをカプセル化します。また、JMS APIは標準のアドレス構文を定義していません。標準のアドレス構文というのも考えられますが、既存のメッセージ指向ミドルウェア (MOM) 製品間のアドレス・セマンティクスの相違はきわめて大きいため、1つの構文で対応することは難しいという判断がされました。
Destination は被管理オブジェクトであるため、そのアドレスの他にプロバイダー固有の設定情報が含まれます。
簡単に言えば、これは、特定のJMSプロバイダーの持つ細かな点を、JNDIでネームスペースを検索する際に使用する簡単な名前のうしろに隠すことができるということを意味します。また、私はJMSクライアントと実際のJMSの宛先との間に間接的なレイヤーを置くことによってアーキテクチャーがより柔軟なものになることを経験しました。クライアント・コードは、myQueue というJNDIのネームスペースを参照できますが、アドミニストレーターは、そのネームスペースのオブジェクトをどのベンダーのキューの宛先にも設定することができます。この概念を図2に示します。
図2宛先の柔軟性
JMSのPublish/Subscribeフレームワークにおける機能のいくつかは、ベンダーに依存しないという目的の妨げとなる場合があります。JMSサーバーの多くは、階層ネームスペースの概念をサポートしています。これによって、P/Sメッセージを階層に分類することができます。トピックに自分を登録するクライアントは、JMSサーバーに接続する場合、階層の特定のセクションに適合するメッセージを要求することができます。例として、図3の階層について考えてみましょう。
図3階層のサンプル
これをもっとよく理解するために、1つの例をあげましょう。たとえば、サブスクライバー・クライアントが、提供されているすべてのUS株式価格に自分を登録するとします。これが静的なサブスクリプションである場合、クライアントは、US株式に自分を登録するために事前に設定されたトピックを示すJMS管理オブジェクトを検索します。階層サブスクリプションを記述する構文はJMSベンダーごとに異なるため、これが理想的であり、JNDIで検索したオブジェクトの背後にそれを隠すことによって、クライアントはJMSの実装に気付くことはありません。
しかし、50の異なるレベルを持ち、何百もの異なる「セル」を提供する階層に自分を登録する場合を考えてみてください。また、階層が動的であり、アドミニストレーターが絶えず追加を行うという場合を考えてみてください。そのような場合、すべての可能なサブスクリプションを示すのに必要なすべてのJMS管理オブジェクトをアドミニストレーターが作成するというのは、現実的ではありません。さらに、クライアント・アプリケーションをサポートするために、階層の選択はきわめて柔軟で動的なものであることが必要です。
そのような場合、トピック名の定義は実行時に行うのが適しているでしょう。しかしここでの問題点は、階層を示すために使用される構文がベンダーによって異なるという点です。以下のコード・フラグメントは、3つの異なるベンダーが使用するサブスクリプション構文を示しています。
MQSeries JMS
Topic topicEqUs = topicSession.createTopic("topic://Prices/Equity/US");
Topic topicEqAll = topicSession.createTopic("topic://Prices/Equity/*");
|
SonicMQ 3.5
Topic topicEqUs = topicSession.createTopic("Prices.Equity.US");
Topic topicEqAll = topicSession.createTopic("Prices.Equity.*"); |
SwiftMQ 2.1.3
Topic topicEqUs = topicSession.createTopic("Prices.Equity.US");
Topic topicEqAll = topicSession.createTopic("Prices.Equity.%"); |
MQSeries JMSの実装は、他の2つの実装とは異なったトピック区切り文字を使用しています (ほとんどのベンダーは点を使用しますが、ここではスラッシュが使用されています)。
上記の文字の他にも、ベンダー固有のストリングがトピック名で使用されています。たとえば、SonicMQは、上位の階層を区切る際にポンド記号を使用し、またMQSeriesは、通常API用に予約されているオプションを示すためにトピック名の接尾辞を使用します。ほとんどすべてのJMSプロバイダーが別々のワイルドカード文字を使用しているため、実行時にトピック名を一様に定義することは困難です。しかし幸いなことに、すべての特殊文字を参照ソースに格納し、実行時にそのソースからロードして使用することによって、この問題を回避することができます。
参照ソースは、アプリケーション・プロパティー・ファイルか、またはJNDIサービスです。これを説明するために、リスト7にあるように、PropertiesManagement.propertiesファイルにトピック文字を追加します。
リスト7. PropertiesManagement.propertiesとトピック文字
#Topic Delimiter For Sonic and Swift
com.nickman.neutraljms.TopicDelemiter=.
#Topic Delimiter for MQSeries
#com.nickman.neutraljms.TopicDelemiter=/
#Topic Wild Card For Sonic and MQSeries
com.nickman.neutraljms.TopicWildCard=*
#Topic Wild Card For Swift
#com.nickman.neutraljms.TopicWildCard=%
#Topic Prefix For MQSeries
#com.nickman.neutraljms.TopicPrefix=topic://
#Topic Prefix For All Others
com.nickman.neutraljms.TopicPrefix=
|
以下のような場合には、これらのエントリーを追加することによって、トピック・サブスクリプション・コードはベンダーに依存しないコードになります。
リスト8. トピック・サブスクリプション・コード
String delim =
jndiProperties.get("com.nickman.neutraljms.TopicDelemiter").toString();
String wildcard =
jndiProperties.get("com.nickman.neutraljms.TopicWildCard").toString();
Strung prefix =
jndiProperties.get("com.nickman.neutraljms.TopicPrefix").toString();
Topic topicEqUs = topicSession.createTopic("Prices" + delim +
"Equity" + delim + "US");
Topic topicEqAll = topicSession.createTopic("Prices" + delim +
"Equity" + delim + wildcard);
|
リスト7に示されている外部設定の方法がわかれば、それを拡張して、他のベンダー固有のオプションにも対応することができます。たとえば、JMS仕様は、セッションが実装できる2つのデリバリー・モードを定義していますが、Sonic MQはさらに3つのデリバリー・モードをサポートしています。デリバリー・モードを外部で定義することによって、ベンダーに依存しないというコードの特性を維持しながらSonic MQ独自の拡張も実装することができます。
今回の記事でご紹介した方法は、決して包括的なものではありません。この記事における私の目標は、ベンダーに依存しないJMSソリューションの実装を皆さんが始めるためのきっかけを提供するということです。また、これらのテクニックは、新たなJMSの実装を採用する際に生じる違いを統合する皆さんの能力を向上させるものであるべきです。
すでにお気付きのことと思いますが、この記事で示されたテクニックは、すべてJMS管理オブジェクトをJNDIに格納するという方法に大きく依存するものです。JMSとJNDIを一緒に使用する方法やJ2EEアプリケーション・サーバーとJNDIサービスを統合するという別の方法については、次回以降の記事で説明することにしましょう。
- 「次期エンタープライズ・アプリケーションにJMSの採用を」 (developerWorks、2002年2月) で、コラムニストのBrian Goetzは、Javaアプリケーションでメッセージ・キューイングを使用する利点について説明し、どのような問題に対してMQテクノロジーを最もうまく利用することができるかについて述べています。
- 「Get the message?」 (developerWorks、2002年2月) の記事でDaniel Drasinは、IBM MQSeries (現在のWebSphere MQ) をJMSサーバーとして設定した場合の利点、落とし穴、および実際の設定方法について説明しています。
- Java Message Service APIに関する詳細は、Sun MicrosystemsのJMSホーム・ページをご覧ください。
-
JMS javadoc で、その機能に関する情報を得ることができます。
- また、JNDI javadoc もご覧ください。
- JMSプログラミングに関しては、JGuruのJMS FAQ をご覧ください。
- この記事で使用されたJMSの実装は、以下のようなものです。
- IBM developerWorksのJavaテクノロジー・ゾーンには、Javaプログラミングに関するたくさんの記事があります。
Nicholas Whiteheadは、finetix LLC のJava設計者です。彼は現在、ニューヨーク・シティーでJP Morgan Chaseプロジェクトに携わっています。Nicholas Whiteheadの連絡先は、nwhitehe@yahoo.com です。