JMS (Java Message Service)

JMS (Java Message Service) は、メッセージ指向ミドルウェア (Message Oriented Middleware: MOM) に準拠したメッセ-ジング・システムにアクセスするための標準インタフェースです。

JMSの概要

JMSはJ2EE (Java2 Enterprise Edition)で規定されています。JMSで定義されている仕様はベンダ・ニュートラルであり、どのメッセージング・システムにアクセスする場合でも、JMSアプリケーションは共通のアクセス方法を使うことができます。

メッセージング製品をはじめとして、さまざまな製品でJMSがサポートされている。JMSをサポートしている製品の例を示す。

JMSクライアントとは、JMSインタフェースを利用してメッセージの送受信を行う、Javaアプリケーションのことを指す。

JMSプロバイダとは、JMSを実装したメッセージング機能を提供する製品や仕組みを意味する。

メッセージ

メッセージとは、JMSクライアントが扱うユーザデータを含むオブジェクトである。JMSで扱うメッセージは、ヘッダおよびプロパティ、ボディから構成される。

ヘッダ

メッセージを識別するためのIDや宛先情報、そのメッセージの優先度など、各メッセージング製品で共通の属性を保持する。これらの属性の中には、アプリケーションが設定できるものと、できないものがある。

プロパティ

ヘッダ部分に設定される基本的な属性に対し、付加的な情報を表す属性を保持する。例えば、このメッセージを作成したユーザやアプリケーションに関する情報などがある。また、アプリケーション独自の属性を追加することもできる。

ボディ

ユーザデータが格納される。データのタイプによって、JMSでは次の5つのメッセージタイプを定義している。

BytesMessage
Byteストリームを扱うメッセージで、イメージなどバイナリデータを扱う場合に使用する。
TextMessage
テキストデータを扱うためのメッセージ
MapMessage
String型の名前とその値(Javaの基本データ型)というペアを複数個格納したメッセージ。JMSクライアントは、メッセージに含まれるString型の名前を指定することで、メッセージ内の順番に関わらず、その名前に対応する値にアクセスできる。
StreamMessage
MapMessageと同様に、複数のJava基本データが格納されたメッセージであるが、MapMessageと異なり、各データに名前が付いていない。複数のデータが連続して並んでいるため、順番にデータをアクセスする必要がある。
ObjectMessage
java.io.Serializableを実装したJavaオブジェクトを扱うためのメッセージ。

JMSではメッセージを5種類に分類していて、それぞれの種類ごとにインタフェースを用意しています。5種類のインタフェースを次に示します。

これらのインタフェースは、すべて javax.jms.Message インタフェースから派生したサブ・インタフェースです。

ドメイン

JMSアプリケーションは、ドメインと呼ばれる2つのプログラミングパターンに分類される。

Point to Point 型

Point to Point 型のドメインでは、メッセージの作成・送信側をProducer、メッセージの受信側をConsumerと呼ぶ。

ひとつのQueueオブジェクトには、複数のProducerやConsumerが接続できる。あるProducerが送信したひとつのメッセージを受け取ることができるのは、Queueオブジェクトに接続しているConsumerの内、ひとつだけである。

Publish/Subscribe型

Publish/Subscribe型のドメインでは、メッセージの作成・送信側をPublisher、メッセージの受信側をSubscriberと呼ぶ。Pub/Sub型のドメインでは、Publisher、Subscriber共にそれぞれが関係するTopicと呼ばれるオブジェクトに接続し、このTopicを介して、メッセージの送受信を行う。

Publisherが送信したメッセージは、そのTopicに接続している全てのSubscriberが受け取ることができる。

CLASSPATHの設定

JMSインタフェースを使用するJavaプログラムのコンパイルや実行を行うには、あらかじめCLASSPATH 環境変数にJMSのクラスライブラリ・ファイルのパス名を含めておく必要があります。

UNIXのCシェルでCLASSPATH環境変数を設定する例を次に示します。

setenv CLASSPATH .:/opt/sample/lib/jms.jar

WindowsでCLASSPATH環境変数を設定する例を次に示します。

set CLASSPATH=.;C:\Program Files\sample\lib\jms.jar

CLASSPATH環境変数に含めるクラス・ライブラリ・ファイルのパス名は、使用するメッセージング・システムによって異なります。

プログラムの流れ

JMSプログラムの流れを次に示す。

  1. 必要なパッケージのimport
  2. ConnectionFactoryの取得
  3. Connectionの生成
  4. Sessionの生成
  5. メッセージの送受信
  6. オブジェクトのクローズ

JMSパッケージのインポート

JMSを使用するJavaプログラムは、JMSパッケージをインポートする必要がある。

import javax.jms.*;

また、ConnectionFactoryをJNDIネームスペースから取得する場合には、javax.namingパッケージをインポートする必要がある。

import javax.naming.*;

ConnectionFactoryの取得

JMSでは、アプリケーション・プログラムとJMSプロバイダである各製品の性質を分離するために、ネームスペース(ディレクトリ・サービス)を利用する。それぞれの製品独自の属性やコンテキストを必要に応じて登録しておく。アプリケーション・プログラムは実行時にlookup()を行なって、必要な情報を入力することで、各製品の機能を利用できるようになる。ただし、このような仕組みを利用せず、アプリケーション・プログラムが実行時に直接必要なコンテキストを生成する方法もある。

ConnectionFactoryは、JMSクライアントがJMSプロバイダと接続するために必要な情報である。ConnectionFactoryを取得するには、JNDIなどを利用して、あらかじめ登録済みの各種情報にアクセスする。

ConnectionFactory connectionFactory;
Context context = new InitialContext(...);

connectionFactory = (ConnectionFactory)context.lookup(...);

Connectionの生成

ConnectionFactoryオブジェクトをもとにConnectionオブジェクトを生成する。

Connection connection = connectionFactory.createConnection();

Sessionの生成

ConnectionオブジェクトをもとにSessionオブジェクトを生成する。

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

メッセージの送受信

Point to Point 型

Producer
MessageProducerの生成

SessionオブジェクトのcreateProducer()を利用して、MessageProducerオブジェクトを生成する。

メッセージの作成

SessionオブジェクトからMessageオブジェクトを生成する。ただし、MapMessageやTextMessageなどのメッセージ・タイプごとにメソッドが異なる。

Consumer
MessageConsumberの生成

SessionオブジェクトのcreateConsumer()を利用して、MessageConsumerオブジェクトを生成する。

メッセージの受信

メッセージを読み出すためには、MessageConsumerオブジェクトに実装されているメソッドを使う。

メッセージ・タイプの確認と処理

JMSでは5つのメッセージ・タイプがあり、それぞれのメッセージ・タイプごとにその処理を行なうメソッドが異なる。メッセージを読み取った時点では、そのメッセージがどのタイプなのかまだ分からないので、そのタイプを確認する必要がある。

ただし、送られてくるメッセージのタイプがあらかじめ決まっているのであれば別である。

Publish/Subscribe 型

Publish
Subscriber

オブジェクトのクローズ

Sessionオブジェクトをクローズする。

if (session != null) {
  try {
      session.close();
  } catch (JMSException e) {
      System.err.println("Session could not be closed.");
      System.err.println(e);
  }
}

Connectionオブジェクトをクローズする。

if (connection != null) {
  try {
      connection.close();
  } catch (JMSException e) {
      System.err.println("Connection could not be closed.");
      System.err.println(e);
  }
}

JMSパッケージがスローする例外

JMSパッケージのメソッドは、java.jms.JMSException例外をスローすることがあります。したがって、JMSパッケージのメソッドを呼び出す箇所では、これをキャッチまたはスロー宣言する必要があります。

java.jms.JMSException例外をキャッチする例を次に示します。

try {
  // JMSパッケージのメソッド呼び出し
} catch (JMSException e) {
  // 例外処理
}

java.jms.JMSException例外をスロー宣言する例を次に示します。

public class void main(String args[]) throws JMSException {
  // JMSパッケージのメソッド
}

メッセージ・リスナー

Publisher - Subscriber モデルでは、送信者はJMSサーバ内部に作成されるトピックに対してメッセージを発行します。トピック(話題)とは、メッセージを分類するオブジェクトです。

受信者側はあらかじめJMSサーバに対してそのトピックに送られたメッセージを購読 (Subscribe) することを伝えておきます。メッセージが発行されれば、購読を申し込んでいる全ての受信者にメッセージが送られます。全ての受信者がメッセージを受け取ったら、メッセージはサーバから削除されます。

TopicConnectionオブジェクトを作成するには、TopicConnectionFactory.createTopicConnection()メソッドを使用します。

TopicConnection connection = connectionFactory.createTopicConnection();

TopicSessionオブジェクトを作成するには、TopicConnection.createTopicSession()メソッドを使用します。

TopicSession session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);

トピックを作成するには、TopicSession.createTopic()メソッドを使用します。

String topicName = "SampleTopic";
Topic topic = session.createTopic(topicName);

サブスクライバは非永続的サブスクライバと永続的サブスクライバに分けられます。

非永続的サブスクライバを作成するには、TopicSession.createSubscriber()メソッドを使用します。

永続的サブスクライバを作成するには、TopicSession.createDurableSubscriber()メソッドを使用します。

TopicSubscriber subscriber = session.createSubscriber(topic);

メッセージ・リスナーは、javax.jms.MessageListenerインタフェースを実装 (implements) することで作成することができる。MessageListenerオブジェクトは、配達されたメッセージを非同期的に受信するのに使われる。

class MyListener implements MessageListener {
  public void onMessage(Message message) {
    ....
  }
}

メッセージ・ハンドラであるMessageListener.onMessage()メソッドにてMapMessageから値を取り出すには、次のメソッドを使用します。

int getInt(String name) throws JMSException
String getString(String name) throws JMSException

引数nameには、値とペアになっている名前を指定します。

メッセージ・リスナーを登録するには、TopicSubscriber.setMessageListener()メソッドを使用します。

MyListener myListener = new MyListener();
TopicSubscriber subscriber = session.createSubscriber(topic);
subscriber.setMessageListener(myListener);

非同期リスナー

キューからメッセージを読み出す際、アプリケーションはAPIの制御が戻るまで待たされることになる。例えば、5秒の待ち時間を指定してメッセージの読み出しを行った場合、5秒以内にメッセージがキューから読み出し可能になるか、または5秒が経過するまで、そのアプリケーションは待たされる。

JMSには非同期リスナーと呼ばれるメッセージの受信処理だけを別のclassとして実装し、main()の処理と別のスレッドで実行させる機能がある。この機能を使うと、main()は各種セットアップや終了処理などに専念させ、メッセージの受信処理を別のスレッドで行うことが可能になり、より効率的なメッセージ処理を実装することができる。

javax.jms パッケージ

javax.jms.Connection インタフェース

Session createSession(boolean transacted, int acknowledgeMode) throws JMSException
transacted
同期点処理するか否かを指定する。
acknowledgeMode
JMSクライアントがメッセージ受信した際に、ACKをどのように送信側へ戻るかを指定する。
Session.AUTO_ACKNOWLEDGE
Session.CLIENT_ACKNOWLEDGE
Session.DUPS_OK_ACKNOWLEDGE

javax.jms.MapMessage インタフェース

MapMessageには、名前と値のペアが格納されています。

メッセージ・ハンドラであるMessageListener.onMessage()メソッドにてMapMessageから値を取り出すには、次のメソッドを使用します。

int getInt(String name) throws JMSException
String getString(String name) throws JMSException

引数nameには、値とペアになっている名前を指定します。

javax.jms.MessageListener インタフェース

リスナーへメッセージを渡す。

void onMessage(Message message)
message
リスナーに渡されたメッセージ

javax.jms.TopicConnection インタフェース

TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException

javax.jms.TopicConnectionFactory インタフェース

TopicConnection createTopicConnection() throws JMSException

javax.jms.TopicSession インタフェース

Topic createTopic(String topicName) throws JMSException

引数topicNameにはトピック名を指定します。

TopicSubscriber createSubscriber(Topic topic) throws JMSException
TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException

javax.jms.TopicSubscriber インタフェース

void setMessageListener(MessageListener listener) throws JMSException