Javaを紐解くための重点キーワード

伊藤敬
日本BEAシステムズ
2001/11/7


JMS(Java Message Service)


JMSとは?

 JMSは通称「MOM」(メッセージ指向ミドルウェア)と呼ばれるエンタープライズメッセージングシステムにアクセスするための標準APIです。つまり、メッセージングシステムそのものの実装ではなく、MOMのサービスを利用するためのJavaベースの標準インターフェイスであるということです。JMSで定義されている仕様はベンダ・ニュートラルであり、どのMOMにアクセスする場合でも、一部の属性のみに個別の情報を定義するだけで、JMSアプリケーション自身は共通の手順と構成でシステム構築を進めることができます。

 また、JMSを利用することで複数のアプリケーションがメッセージの交換を通じて通信することができるようになります。そのメッセージング形態はピア・ツー・ピア型であり、JMSアプリケーションは通常「クライアント」に位置付けられます。JMSはJ2EEのほかのサービス(JTA/JTS、JNDI、JDBCなど)と連携し、Servlet/JSP、EJBのアプリケーションに組み込むことができます。また、EJB 2.0で定義されたメッセージドリブンBeanでは、JMSとの連携によって非同期型のEJBアプリケーションを作成できます。

 JMSの特徴をまとめると以下のようになります。

  • Javaアプリケーション間で既存のMOMを共有し、メッセージを送受信できる
  • メッセージを作成、送受信するための標準インターフェイスが提供されることによりメッセージ・アプリケーションの開発が容易になる
  • 既存のMOM製品群を横断的にカバーするアーキテクチャの提供により、メッセージング・アプリケーションのポータビリティが向上する

JMSのメッセージングモデル

 MOM製品の主要なメッセージングモデルにポイント・ツー・ポイント(PTP)およびパブリッシュ/サブスクライブ(Pub/Sub)があります。PTPはメッセージキューのコンセプトに基づいており、1つのあて先に対して1つの送信元からメッセージが送信されます。Pub/Subは階層化されたノードに定義された複数のクライアントに1つの送信元からメッセージを配信します。JMSはこの2つのメッセージングモデルをサポートしており、システム構築に必要なクライアントインターフェイスを提供しています。

PTPメッセージングモデル

 PTPメッセージングモデルでは、アプリケーションが別の1つのアプリケーションにメッセージを送信できます。PTPアプリケーションでは、名前付きキューを使用してメッセージが送受信されます。キューセンダ(プロデューサ)は、特定のキューに対してメッセージを送信し、以下の図のように、キューレシーバ(コンシューマ)は、特定のキューからメッセージを受信します。

PTPメッセージングモデル

 複数のキューセンダおよびキューレシーバを1つのキューに関連付けることができますが、個々のメッセージは1つのキューレシーバにしか配信できません。

Pub/Subメッセージングモデル

 Pub/Subメッセージングモデルでは、アプリケーションが複数のアプリケーションにメッセージを送信できます。Pub/Subアプリケーションでは、トピックをサブスクライブすることでメッセージが送受信されます。以下の図のように、トピックパブリッシャ(プロデューサ)では、特定のトピックに対してメッセージが送信されます。トピック・サブスクライバ(コンシューマ)では、特定のトピックからメッセージが受信されます。

Pub/Subメッセージングモデル

 PTPメッセージングモデルの場合と違って、Pub/Subメッセージングモデルでは複数のトピックサブスクライバが同じメッセージを受信できます。メッセージは、すべてのトピックサブスクライバが受信するまで維持されます。

JMSのアーキテクチャ

 JMSアプリケーションの基本構成は以下のとおりです。

JMSクライアント メッセージを送受信する個々のJavaプログラム。メッセージを送信する側をメッセージ・プロデューサ、受信側をメッセージ・コンシューマと呼ぶ
JMSプロバイダ JMSインターフェイスを実装し、かつ管理機能なども装備したメッセージングシステムそのもの、もしくはMOMのサービスをJ2EE側に提供する実装
JMSメッセージ JMSクライアント間での情報の受け渡しを行うために個々のアプリケーションで定義されるリクエスト、レポート、もしくはイベント

JMSアプリケーションの基本構成

 さらに、前述のとおり、JMSでは異なるMOMのサービスを共通化して利用するためにメッセージ通信に関する属性定義をカプセル化しています。そのために以下のオブジェクトが用意されています。

管理オブジェクト
 JNDIに登録されるオブジェクトであり、JMSクライアントがルックアップすることでJMSのサービスを利用できるようにします。管理オブジェクトは、JMSクライアントがJMSプロバイダに接続するために必要な属性情報を管理します。JMSプロバイダは通常、管理オブジェクトに属性を定義するためのツールを提供します。

 さらに、JMSにはConnectionFactoryとDestinationと呼ばれる管理オブジェクトが定義されています。

ConnectionFactoryオブジェクト
 ConnectionFactoryオブジェクトはJMS管理者によって定義された属性情報(JMSプロバイダが存在するホスト名、ポート番号、接続するクライアントIDなど)の集合をカプセル化します。JMSクライアントはConnectionFactoryオブジェクトを利用してJMSプロバイダとの接続を確立します。ConnectionFactoryインターフェイスは、JMSクライアントがConnectionを作成できるようにします。

Destinationオブジェクト
 JMSには既存のMOM製品が持つ固有の属性をカプセル化したDestinationオブジェクトが定義されています。これによって属性を単一のものにブリッジし、JMSクライアントのメッセージを送受信します。javax.jms.Destinationインターフェイスのサブクラスとしてjavax.jms.Queueとjavax.jms.Topicが定義されています(ほかにもありますがここでは割愛します)。これらのサブクラスにPTPメッセージングモデルとPub/Subメッセージングモデルに使用するメソッド群が定義されています。DestinationオブジェクトがJMSプロバイダとしてメッセージを送受信するQueueやTopicの実体です。QueueおよびTopicにはそれぞれのメッセージングモデルにのっとったルールが組み込まれており、提供されるメソッドを利用することによってそのルールに沿ったメッセージングを実現できます。

Connectionオブジェクト
 Connectionオブジェクトは、JMSアプリケーションとメッセージングシステムの間の通信チャネルを表し、メッセージを生成し送受信するためのSessionオブジェクトを作成するために使用します。Connectionオブジェクトでは、JMSアプリケーションとJMSの間のメッセージングを管理するサーバサイドとクライアントサイドのオブジェクトが作成されます。Connectionオブジェクトは、PTPとPub/Subに対応するインターフェイスを持っており、それぞれjavax.jms.QueueConnection、javax.jms.TopicConnectionで定義され、コネクションの状態をコントロールするメソッドなどを含めたJMSクライアントとJMSプロバイダの接続状態を管理します。

Sessionオブジェクト
 Sessionオブジェクトでは、生成および送受信されるメッセージの順序を定義し、複数のMessageProducerとMessageConsumerを作成できます。

MessageProducer/MessageConsumerオブジェクト
 MessageProducerオブジェクトでは、メッセージがQueueまたはTopicに送信されます。MessageConsumerオブジェクトでは、メッセージがQueueまたはTopicから受信されます。2つのオブジェクトは互いに独立して機能します。MessageConsumerが作成されてメッセージを待っているかどうかに関係なく、MessageProducerではメッセージが生成および送信されます。この逆も同様です。

 メッセージセンダオブジェクトとメッセージレシーバオブジェクトは、MessageProducer クラスおよびMessageConsumerクラスのサブクラスとして作成されます。次の表は、MessageProducerとMessageConsumerのサブクラスを説明しています。

サブクラス メッセージングモデル 機能
QueueSender PTP JMS PTPプロバイダのメッセージを送信する
QueueReceiver PTP JMS PTPプロバイダのメッセージを受信し、メッセージの作成されたJMS接続が閉じるまで存続する
TopicPublisher Pub/Sub JMS Pub/Subプロバイダのメッセージを送信する
TopicSubscriber Pub/Sub JMS Pub/Subプロバイダのメッセージを受信し、メッセージの作成されたJMS接続が閉じるまで存続する。メッセージの送り先は、適切なJNDIインターフェイスを使用して明示的にバインドしなければならない

 これまでに紹介したJMSアプリケーションを構成するオブジェクトやサービスを以下の図にまとめます。

JTA/JTSとそれに関連するサービス

JMSのメッセージ

 JMSアプリケーションで送受信されるのはMessageオブジェクトです。このオブジェクトはアプリケーション間で交換される情報がカプセル化されており、標準のメッセージヘッダフィールド、アプリケーション固有のプロパティ、およびメッセージ本文という3つの要素で構成されます。

メッセージヘッダフィールド
 すべての JMSメッセージには、デフォルトで挿入され、MessageConsumerで利用できる標準のヘッダフィールドがあります。一部のフィールドは、MessageProducerで設定できます。

メッセージプロパティフィールド
 メッセージのプロパティフィールドには、メッセージプロデューサによって追加されたヘッダフィールドが格納されます。プロパティは、標準的なJavaの名前と値の組み合わせです。有効な値は、boolean、byte、double、float、int、long、およびstring です。

メッセージ本文
メッセージ本文は、プロデューサからコンシューマに配信される内容です。メッセージ本文が扱えるのは、テキスト、Javaオブジェクト、ストリームデータなどです。

JMSアプリケーションの開発
 これまで説明した内容を踏まえて、JMSアプリケーションの開発は以下のような手順で行う必要があります。

  1. 必要なパッケージのインポート
  2. JMSアプリケーションの設定
  3. メッセージの送信、受信、および確認応答
  4. リソースのクローズとリリース

JMSアプリケーションの設定
 メッセージを送受信するには、あらかじめ JMSアプリケーションを設定しておく必要があります。以下に、JMSアプリケーションの設定に必要な手順を示します。

(例 : Pub/Subアプリケーション)
 以下のサンプルは、JMSアプリケーションのトピックセッションをどのように設定して開始するかを示すものです。

/* 必要な変数(JNDIコンテキストなど)、JMS接続ファクトリ、およびトピック静的変数を定義 */
public final static String JNDI_FACTORY = "javax.jndi.InitialContextFactory";
public final static String JMS_FACTORY = "javax.jms.TopicConnectionFactory";
public final static String TOPIC = "javax.jms.Topic";

protected TopicConnectionFactory tconFactory;
protected TopicConnection tcon;
protected TopicSession tsession;
protected TopicPublisher tpublisher;
protected Topic topic;
protected TextMessage msg;


InitialContext ic = getInitialContext(args[0]); /* JNDI 初期コンテキストを設定 */
.
.
.
private static InitialContext getInitialContext(
String url
) throws NamingException
{
  Hashtable env = new Hashtable();
  env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
  env.put(Context.PROVIDER_URL, url);
  return new InitialContext(env);
}


/* JMSトピックにメッセージを送信するために必要なすべてのオブジェクトを作成 */
public void init(
Context ctx, /* JNDI 初期コンテキスト */
String topicName
) throws NamingException, JMSException
{
  tconFactory = (TopicConnectionFactory) ctx.lookup(JMS_FACTORY); /* JNDIを使用してConnectionFactoryをルックアップ */
  tcon = tconFactory.createTopicConnection(); /*   ConnectionFactoryを使用してConnectionを作成 */
  tsession = tcon.createTopicSession(false,   Session.AUTO_ACKNOWLEDGE); /* Connectionを使用してSession(この場合は非トランザクション)を作成 */


  topic = (Topic) ctx.lookup(topicName); /* JNDIを使用してDestination(Topic)をルックアップ */
  tpublisher = tsession.createPublisher(topic); /*Sessionと  Destination(Topic)を使用してメッセージセンダオブジェクト(TopicPublisher)への参照を作成 */
  msg = tsession.createTextMessage(); /* メッセージオブジェクトを作成 */
  tcon.start(); /* 接続を開始 */
}

メッセージの送信

 メッセージをDestinationオブジェクトに送信するには、メッセージセンダオブジェクト(QueueSender(PTP)またはTopicPublisher(Pub/Sub))を使用します。

(例 : Pub/Subアプリケーションにおけるメッセージの送信)
 以下の例では、TextMessageオブジェクトを作成し、テキストベースのメッセージを設定した後、Topicに送信するために必要なコードを示してあります。

msg = tsession.createTextMessage();
.
.
.
public void send(
String message
) throws JMSException
{
  msg.setText(message);
  tpublisher.publish(msg); /* トピックパブリシャ・オブジェクトのPublishメソッドで送信 */
}

メッセージの受信

 メッセージを受信するには、メッセージレシーバオブジェクトを作成し、メッセージを同期受信するか非同期受信するかを指定する必要があります。

メッセージの非同期受信
 非同期受信を行う場合、JMSアプリケーションの設定時のメッセージプロデューサとメッセージの作成を以下のコードに変更します。

tpublisher = tsession.createPublisher(topic);
msg = tsession.createTextMessage();

       

tsubscriber = tsession.createSubscriber(topic); /* トピックサブスクライバを作成 */
tsubscriber.setMessageListener(this); /* 非同期メッセージリスナを登録 */

 メッセージがトピックセッションに届くと、そのメッセージはonMessageメソッドに渡されます。以下はonMessageメソッドでの処理の1例です。

public void onMessage(Message msg)
{
  try {
    String msgText;
    if (msg instanceof TextMessage) {
      msgText = ((TextMessage)msg).getText();
    }else { // TextMessageではない場合
      msgText = msg.toString();
    }
      System.out.println("Message Received:"+ msgText );
      if (msgText.equalsIgnoreCase("quit")) {
        synchronized(this) {
          quit = true;
          this.notifyAll(); // メインスレッドに終了するよう通知する
        }
      }
    } catch (JMSException jmse) {
    jmse.printStackTrace();
  }
}

メッセージの同期受信
 メッセージを同期的に受信するには、recieveメソッドを使用します。receiveメソッドを引数なしで呼び出した場合、その呼び出しはメッセージが生成されるか、またはアプリケーションが閉じられるまで無期限にブロックされます。代わりに、タイムアウト値を渡してメッセージの待ち時間を指定することもできます。

(例 : Pub/Subアプリケーション内でのメッセージの同期受信)
 以下の例ではreceiveメソッドを呼び出してメッセージ受信まで無期限に待機します。

tsubscriber = tsession.createSubscriber(topic); /* Topicに対するトピックサブスクライバの作成 */
Message msg = tsubscriber.receive(); /* receiveメソッドの実行とメッセージ待機 */
msg.acknowledge();

 

Javaを紐解くための重点キーワード

 

 



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間