タグ「JMS」が付けられているもの

この記事は GlassFish Advent Calendar 2014 の 13 日目です。昨日は「GlassFish 4.1で始めるWebサービス&MQ通信―番外編:WebSocket」です。本日は番外編その 2 として、GlassFish 4.1 がサポートする Server-Sent Events(SSE)について触れようと思います。

GlassFish 4.1 では、JAX-RS 実装 Jersey がいくつかの独自拡張機能を提供しています。Jersey 独自拡張機能としては MVC Framework(JAX-RS 2.0/2.1 での標準化を Bill Burke に拒否されたため、別仕様の JSR 371 として標準化が始まりました)、multipart メッセージ(ファイルアップロード等)対応などがあり、Jersey 2.3 からは SSE サポートが加わりました。Jersey 2.8 からは SSE サポートはデフォルトで有効となっています(GlassFish 4.1 はさらに新しい Jersey 2.10.4 をバンドルしているため、当然ながらデフォルトで SSE を扱うことができます)。

Jersey の SSE サポートについては、本日の第九回 #渋谷java にて発表しましたので、概要は発表資料を参照してください。

Jersey SSE サポートの詳細については、Jersey Users Guide, Chapter. 14 にサンプル付きで記載がありますので、こちらもご覧ください。

SSE 対応は JAX-RS 2.1 新機能として JSR 370 にて提案されています。しかし、残念ながら下記の理由からおそらく採用されることはないでしょう。

  • Java EE 8 の SSE サポートはほぼ確定とみられるが、どのコンポーネントに入れるべきかについて意見がまとまっていない。JAX-RS 2.1 の他にも、HTTP/2 の Server Push とあわせて Servlet 4.0(JSR 369)に入れる案もあり、その他 Web の API である JSF 2.3(JSR 372)、MVC 1.0(JSR 371)にも余地はあり得る(それが適切か否かは別として)。もっとも、JSP での SSE 対応はないと思われるが。
  • JSR 370 では、例によって Bill Burke が SSE サポートに難色を示している。MVC の時のような初めから提案を全否定し議論にすら応じないほどの拒絶は見られないが、最終的には Bill Burke がスペックリードの意見を押し切って廃案にする可能性が高い。

念のために書き添えておくと、Bill Burke は JBoss の JAX-RS 実装である RESTEasy のリードであり、JAX-RS の初期からエキスパート・グループの一員を務める JAX-RS の第一人者です。「JavaによるRESTfulシステム構築」(O'Reilly、2010)という JAX-RS に関する良著を執筆した人物でもあります。RESTEasy の独自拡張機能には彼の思想が色濃く反映されており、Jersey のそれとは全く異なるものになっています。余談ですが、筆者自身は JSR 370 における Bill Burke の態度が気に入らなくて(以下略)

SSE については以上です。では、また明日お目にかかりましょう。


補足:「JavaによるRESTfulシステム構築」は、JAX-RS を深く知るためには是非手元に置いておきたい一冊です。邦訳は JAX-RS 1.1 ベースですが、JAX-RS 2.0 を理解するための基礎知識を固める上で非常に有益です。Bill Burke に対する個人的な感情を抜きにして、筆者が自信を持ってお薦めできる参考書です。

この記事は Java EE Advent Calendar 2014 および GlassFish Advent Calendar 2014 の 8 日目です。昨日は @yamadamn さんの「スレッドダンプから見るWebLogic Serverの世界」(Java EE) および @backpaper0 さんの「よく使うasadminのコマンドを紹介する」(GlassFish)です。

本日は、「GlassFish 4.1で始めるWebサービス&MQ通信」の第 1 回で、JMS 2.0 と OpenMQ について取り上げます。このシリーズでは、Java EE 7 の文献ではあまり取り上げられないアプリケーションのデプロイとその前準備について取り上げていきます。第 2 回は明後日の予定で JAX-WS と Metro を取り上げます。第 3 回は 12 月 11 日で JAX-RS と Jersey を取り上げる予定です。

1. はじめに

今回の主役である JMS(Java Messaging Service)は、Java で MQ(Message Queue)通信を利用するための API です。MQ 通信とは、送信側と受信側の間にキューを設けて、メッセージを非同期に送受信する方式を言います。MQ 通信を実現するミドルウェアとしては IBM の WebSphere MQ が歴史・実績とも抜きんでており、世界中の大規模システムで利用されています。その他にも多数の製品があり、GlassFish には OpenMQ という製品がバンドルされています。こう言った製品を総称して MoM(Message-oriented Middleware)と言います(もっとも、現場ではこんな格好を付けた呼び方はあまりせず、単に MQ と呼ぶことが多いです)。

さて、MQ の何がそんなに嬉しいかというと、(キューが動作していれば)受信側の都合を考えずにメッセージを送信できることに尽きます。例えば、送信側が 24x7 稼働のオンラインシステムで、受信側が平日夜間しか動作しないバッチシステムの場合を考えてみましょう。MQ がなければ、オンラインシステム側がバッチシステムの稼働時間に合わせてメッセージ送信のタイミングを調整しなければなりません。しかし MQ があればオンラインシステムは任意のタイミングでメッセージを送信すれば良く、バッチシステムも自身の稼働時間中にメッセージを受信すれば良いのです。このように MQ は送受信双方の特性や性能が大きく違うシステム間連携で利用されます。

JMS を利用すると、Java (主に Java EE)アプリケーションで MQ を簡単に扱うことができます。また、利用する MQ 製品(WebSphere MQ、OpenMQ、Active MQ、HornetQ など)を問わず統一された API で MQ 通信を利用できます。

今回は GlassFish 4.1 における JMS と OpenMQ の基本的な使い方についてご紹介します。JMS には 2 種類の通信方式、Point-to-Point と Publish-Subscribe があり用途によって使い分けますが、ここでは基本的な MQ の使い方である Point-to-Point を取り上げます。

2. GlassFish と OpenMQ

GlassFish 4.1 は JMS の最新規格である JMS 2.0 をサポートし、MQ 製品である OpenMQ をバンドルしています(ただし、Web Profile には JMS と OpenMQ は含まれません)。

JMS ではキューを管理するサービスをブローカといいます。GlassFish を起動すると TCP ポート 7676 でブローカーが 1 つ起動します。このブローカは GlassFish 管理ブローカ(GlassFish-managed broker)と呼ばれる特殊な OpenMQ ブローカで、サービスの制御を GlassFish が管理します。GlassFish にバンドルされている OpenMQ はデフォルトで GlassFish 管理ブローカとして動作するようになっています。

まずは GlassFish を起動して、ブローカの動作を確認してみましょう。as-install-parent/mq/bin 以下に OpenMQ 一式が配置されています。今回はこの中から imqcmd コマンドを利用します。

imgcmd list bkr でブローカの一覧を表示します。ブローカが動作しているかどうかも確認できます。デフォルトのユーザー名とパスワードはともに admin です。

C:\glassfish4\mq\bin>imqcmd list bkr
Username: admin
Password:
Listing all the brokers in the cluster that the following broker is a member of:


-------------------------
Host         Primary Port
-------------------------
localhost    7676

Cluster ID
Cluster is Highly Available   false

-------------------------------
      Address           State
-------------------------------
192.168.xxx.xxx:7676  OPERATING

Successfully listed brokers in the cluster.

次にキューを作成します。キューの作成も imqcmd コマンドで行えます。myQueue という名前のキューを作成してみましょう。

C:\glassfish4\mq\bin>C:\glassfish4\mq\bin>imqcmd create dst -t q -n myQueue
Username: admin
Password:
Creating a destination with the following attributes:

Destination Name    myQueue
Destination Type    Queue

On the broker specified by:

-------------------------
Host         Primary Port
-------------------------
localhost    7676

Successfully created the destination.

imqcmd の create dst サブコマンドでキューを作成できます。オプション -n に名前(ここでは myQueue)を指定し、オプション -t にキューを示す q を指定します。

参考までに、Publish-Subscribe で使用するトピック(Topic)という特殊なキューを作成する場合はオプション -t にトピックを示す t を指定します。

なお、キューの削除には destroy dst サブコマンドを使用します。オプションは create dst と同様です。先に作成した myQueue を削除する場合は以下のようにします。

C:\glassfish4\mq\bin>imqcmd destroy dst -t q -n myQueue
Username: admin
Password:
Destroying the destination where:

------------------------------------
Destination Name    Destination Type
------------------------------------
myQueue             Queue

On the broker specified by:

-------------------------
Host         Primary Port
-------------------------
localhost    7676

Are you sure you want to destroy this destination? (y/n)[n] y

Successfully destroyed the destination.

キューの作成と削除は、imqadmin(Message Queue Administration Console)という GUI でも実行できます。今回は imqadmin の説明は割愛します。

キューが作成できたら、GlassFish の管理コンソールで設定を行いましょう。設定箇所は[リソース]>[JMS リソース]以下にある、[接続ファクトリ]と[宛先リソース]です。

まずは接続ファクトリから見てゆきましょう。

connection-factories.png

GlassFish には localhost のブローカに接続するための jms/__defaultConnectionFactory があらかじめ定義されており、特に指定しない場合この接続ファクトリが使用されます。独自の接続ファクトリを利用するケースについては、後の方でご紹介します。

次に宛先リソースを見てみましょう。

destination-resources.png

こちらは初期状態では何も定義されていません。ここに先ほど作成した myQueue を追加してみましょう。

create-destination.png

設定項目は、以下のようにします。

  • JNDI 名: jms/myQueue (アプリケーションから参照するための任意の名前)
  • 物理宛先名: myQueue (先に作成した myQueue を指定する
  • リソース・タイプ: javax.jms.Queue (キューであることを示す)

これで GlassFish 上のアプリケーションから myQueue に対してアクセスする準備が整いました。

実のところ、先にキューを作成しなくても、物理宛先名で指定された名前のキューが自動的に作成されます。しかし、JMS の背後にある OpenMQ を全く意識せずに使い続けると意図しないキューが作成される、あるいは意図したキューが作成されないなどのトラブルを引き起こしかねないため、敢えて OpenMQ の imqcmd コマンドでキューを作成・削除する方法をご紹介しました。

3. MQ の送受信を行うアプリケーションを作成する

ここからは、JMS を利用した非常に簡単なアプリケーションを作成します。

このアプリケーションは、文字列をキューに送信する機能と、キューから文字列を受信する機能だけを持ちます。今回、きちんとした UI は用意せず、JAX-RS のリソースに対し HTTP クライアントで直接アクセスする仕様とします。

最初に、JAX-RS のリソースクラスを示します。なお、ルート URI は /app、リソースの URI は /queue とし、キューへの送信には POST、キューからの受信には GET を使うものとします。

package jp.coppermine.samples.mq;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

@Path("/queue")
@RequestScoped
public class MqTestResource {
  
  @Inject
  private Sender sender;
  
  @Inject
  private Receiver receiver;
  
  @POST
  public void send(@FormParam("message") String message) {
    sender.send(message);
  }
  
  @GET
  public String receive() {
    return receiver.receive();
  }
}

キューへの送信は Sender クラス、キューからの受信は Receiver クラスが行います。この後ソースコードを示しますがいずれもステートレス・セッション Bean(EJB)です。

参考まで、リソースクラスに付加された @RequestScoped アノテーションは、このクラスのスコープを明示します。CDI 1.1 では beans.xml を省略できますが、その場合にはリソースクラスのスコープを明示しなければ @Inject が有効になりません(beans.xml の bean-discovery-mode が annotated と見なされる)。詳細については、上妻さん(@n_agetsu)のブログ記事「Java EE環境におけるCDIのデフォルト化」を参照してください。

続いて Sender クラスです。このクラスはキューへの送信を担当します。

package jp.coppermine.samples.mq;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.jms.Destination;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSContext;

@Stateless
public class Sender {
  
  @Inject
  private JMSContext context;
  
  @Resource(name = "jms/myQueue")
  private Destination myQueue;
  
  public void send(String message) {
    context.createProducer().send(myQueue, message);
  }
}

JMSContext context; に対して @Inject アノテーションを付加することで、デフォルトの接続ファクトリに基づいた JMSContext を取得することができます。Destination myQueue; に対して @Resource(name = "jms/myQueue) アノテーションを付加して JNDI 解決を行います。その上で JMSContext のオブジェクトから JMSProducer のオブジェクトを取得し、JMSProducer#send(Destination, String); でキューにメッセージを送信します。send メソッドはいくつかのオーバーロード・メソッドが存在し、メッセージのクラスを選択できます。

Sender と Receiver は、今回は EJB で作成しましたが、この規模であれば EJB である必要はありません。ただし、実際の MQ 通信では送受信とデータベースアクセスが連動するケースも多く見られるため、そのような場合は EJB を使用した方がトランザクション管理が完結になります。

以上で送信部は完了です。昔の JMS はいくつもの前処理と例外処理を必要とし、正直筆者も避けていたのですが、JMS 2.0 ではわずかな前処理があるだけです(例外は JPA 同様すべてランタイム例外になっている)。

最後に Receiver クラスを示します。このクラスはキューからメッセージを受信します。

package jp.coppermine.samples.mq;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.jms.Destination;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSContext;

@Stateless
public class Receiver {
  
  @Inject
  private JMSContext context;
  
  @Resource(name = "jms/myQueue")
  private Destination myQueue;
  
  public String receive() {
    return context.createConsumer(myQueue).receiveBody(String.class, 1000L);
  }
}

基本的な部分は Sender クラスと同じです。ただし、JMSContext のオブジェクトから JMSConsumer のオブジェクトを取得し、JMSConsumer#receiveBody(Class<T>, long); を呼び出しているところが異なります。receive 系メソッドも何種類かあります。ここで使用したのはメッセージのクラスとタイムアウト時間を指定する形式です。

では、実行してみましょう。まず、HTTP クライアントを用いてメッセージ "Hello, world" をキューに送信します。今回は HTTP クライアントに HTTP4e を使用しました。

send.png

実行後はキューにメッセージが 1 件入っているはずです。imqcmd でキューの状態を確認してみましょう。list dst サブコマンドを使用します。

C:\glassfish4\mq\bin>imqcmd list dst
Username: admin
Password:
Listing all the destinations on the broker specified by:

-------------------------
Host         Primary Port
-------------------------
localhost    7676

-----------------------------------------------------------------------------------------------------
   Name     Type    State      Producers        Consumers                      Msgs
                            Total  Wildcard  Total  Wildcard  Count  Remote  UnAck  InDelay  Avg Size
-----------------------------------------------------------------------------------------------------
mq.sys.dmq  Queue  RUNNING  0      -         0      -         0      0       0    0        0.0
myQueue     Queue  RUNNING  0      -         0      -         1      0       0    0        152.0

Successfully listed destinations.

myQueue の Count(件数)が 1 になっていることが確認できました。

次に HTTP クライアントを用いてキューからメッセージを取得します。

receive.png

先ほどと同様に imqcmd でキューの状態を確認しましょう。

C:\glassfish4\mq\bin>imqcmd list dst
Username: admin
Password:
Listing all the destinations on the broker specified by:

-------------------------
Host         Primary Port
-------------------------
localhost    7676

-----------------------------------------------------------------------------------------------------
   Name     Type    State      Producers        Consumers                      Msgs
                            Total  Wildcard  Total  Wildcard  Count  Remote  UnAck  InDelay  Avg Size
-----------------------------------------------------------------------------------------------------
mq.sys.dmq  Queue  RUNNING  0      -         0      -         0      0       0    0        0.0
myQueue     Queue  RUNNING  0      -         0      -         0      0       0    0        0.0

Successfully listed destinations.

myQueue の Count が 0 になっていることがわかります。

4. その他のトピックス

4.1. 他のホストからキューにアクセスする

前章で作成したアプリケーションは、あくまで同一ホスト上のアプリケーションによる MQ 通信でした。しかし現実には、異なるホスト上のアプリケーションが MQ で通信するケースがほとんどです。異なるホスト上のキューに接続する場合は、接続ファクトリを新たに定義する必要があります。

例として、GlassFish 4.1 をインストールした 2 つのホストを用意し、うち一方の OpenMQ ブローカにキューを作成して利用するケースを挙げます。ここではキューを作成するホストを lysithea、作成しないホストを himalia とします。

まず、lysithea と himalia の双方に lysithea のブローカにアクセスするための接続ファクトリを作成します。

create-connection-factory1.png

一般設定は以下のようにします。

  • JNDI 名: jms/myConnectionFactory (アプリケーションから参照するための任意の名前)
  • リソース・タイプ: javax.jms.ConnectionFactory

さらに、以下のプロパティを追加します。

create-connection-factory2.png

  • addressList = lysithea.voyager.coppermine.jp (lyshitea のホスト名、ここでは lysithea の FQDN が左記の通りであると仮定する

また、lyshitea のみキュー myQueue を作成し、lyshitea と himalia の双方の宛先リソースに myQueue を追加します。

さらにアプリケーションの JMSContext オブジェクト取得箇所を以下のように書き換えます。

@Inject
@JMSConnectionFactory("jms/myConnectionFactory")
JMSContext context;

このようにすることで、以降 jms/myConnectionFactory が指し示す先の myQueue、すなわち lysithea の myQueue にアクセスするようになります。

余談ですが、上記の設定を行った場合は himalia 側に myQueue が自動作成されることはありません。接続ファクトリを明示することで lysithea 側に作成された myQueue を左記に参照する(=himalia 側で myQueue を作成する必要はないと判断する)からです。

4.2. キュー間のメッセージ送受信

例えば、次のような通信は可能でしょうか?

lysithea の app → lysithea のキュー → himalia のキュー → himalia の app

JMS の文献ではこのような構成例はあまり見かけません。WebSphere MQ では良く採用されている構成なのですが。JMS で上記の構成を実現するには JMS ブリッジを使用します。JMS ブリッジでは例えば lysithea のブローカが自身のキューと himalia のキューをマッピングするようなことが可能です。OpenMQ には imqbridgemgr というブリッジ管理のコマンドが用意されています。JMS ブリッジについては今回は触れません。

5. まとめ

今回は JMS 2.0 の Point-to-Point 通信にターゲットを絞り、代わりに実行環境の GlassFish と OpenMQ の操作を交えながら、サンプルアプリケーションを作成してみました。JMS には Publish-Subscribe 通信という 1 対 N の通信方式を採ることもでき、EJB の Message-Driven Beans を用いるとメッセージの到着をトリガーにして何らかの処理を実行するアプリケーションを容易に実現できます。JAX-RS のような手軽さや汎用性はありませんが、システム間連携の要所で効果を発揮してくれる有力な API と言えるでしょう。

※今回作成したサンプルは GitHub https://github.com/btnrouge/mq-test にあります。

明日の Java EE Advent Calendar 2014 は hikaruworld@github さんです。GlassFish Advent Calendar 2014 は明日が「GlassFish 4.1でJAX-RS=CDI連携の単体テストを行う方法」、明後日が「GlassFish 4.1で始めるWebサービス&MQ通信―(2)JAX-WS」を予定しています。