このエントリは2020/08/12現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容との乖離が発生する可能性があります。(2021/01/22に更新)
Azure Service Bus(以下、Service Bus)がJMS over AMQPでのJMS 2.0 APIを使った接続をサポートするようになった。2021/01/21に一般提供を開始した。具体的には以下のドキュメントを参照。
Azure Service Bus Premium で Java Message Service 2.0 API を使用する / Use Java Message Service 2.0 API with Azure Service Bus Premium
https://docs.microsoft.com/azure/service-bus-messaging/how-to-use-java-message-service-20
なお、以下のような似ているドキュメントがあるが、こちらはJMS 1.1仕様のAPIでの接続の場合を説明している。こちらは機能に制限があるので、今後はお勧めできない。
Azure Service Bus と AMQP 1.0 で Java Message Service (JMS) を使用する / Use the Java Message Service (JMS) with Azure Service Bus and AMQP 1.0
https://docs.microsoft.com/azure/service-bus-messaging/service-bus-java-how-to-use-jms-api-amqp
このドキュメントに「今後はJMS 2.0 APIを使った接続を推奨する」とのメッセージがある。
Warning
Azure Service Bus と AMQP 1.0 で Java Message Service を使用する / Use the Java Message Service (JMS) with Azure Service Bus and AMQP 1.0
The below guide caters to limited support for Java Message Service (JMS) 1.1 API and exists for Azure Service Bus standard tier only.
Full support for the Java Message Service (JMS) 2.0 API is available only on the Azure Service Bus Premium tier in preview, which is highly recommended.
https://docs.microsoft.com/azure/service-bus-messaging/service-bus-java-how-to-use-jms-api-amqp
どのSKUで使えるのか
Premiumのみ。その他のSKUでは使えない。Standad、Basicなどで使うと以下のような例外が出る(例外メッセージでもPremiumでなければ使えない、と明記している)。

JMSのどの機能がサポートされているのか
ドキュメントに明記されている通り、分散トランザクションはサポートされないので注意が必要。
What JMS features are supported?
https://docs.microsoft.com/azure/service-bus-messaging/how-to-use-java-message-service-20#what-jms-features-are-supported
依存関係
2020/01/10現在では0.0.7が最新。詳細は以下。
azure-servicebus-jms – ServiceBus ConnectionFactory for JMS users
https://search.maven.org/artifact/com.microsoft.azure/azure-servicebus-jms
以下はMavenの例。
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-servicebus-jms</artifactId>
<version>0.0.7</version>
</dependency>
簡単に接続を試す
基本的にドキュメントに記載のとおりやれば問題ない。以下はJMSContextを使う例。
まずは接続ファクトリを作る。
import com.microsoft.azure.servicebus.jms.ServiceBusJmsConnectionFactory;
import com.microsoft.azure.servicebus.jms.ServiceBusJmsConnectionFactorySettings;
...
ServiceBusJmsConnectionFactorySettings connectionFactorySettings = new ServiceBusJmsConnectionFactorySettings();
connectionFactorySettings.setConnectionIdleTimeoutMS(20000);
ConnectionFactory factory = new ServiceBusJmsConnectionFactory("CONNECTION_STRING", connectionFactorySettings);
ここまでできれば、通常のJMS 2.0 APIと同じ。以下はQueueへの送信の例。
try (JMSContext jmsContext = factory.createContext() ) {
// Create the queue and topic
Queue queue = jmsContext.createQueue("QUEUE_NAME");
// Create the JMS message producer
JMSProducer producer = jmsContext.createProducer();
// Create textmessage
TextMessage msg = jmsContext.createTextMessage(String.format("message sent at %s", (new Date()).toString()));
// send the message to the queue
producer.send(queue, msg);
}
catch (JMSException e) {
e.printStackTrace();
}
続いて、Queueからの受信の例。
try (JMSContext jmsContext = factory.createContext() ) {
// Create the queue and topic
Queue queue = jmsContext.createQueue(”QUEUE_NAME");
// set Message Listener
JMSConsumer consumer = jmsContext.createConsumer(queue);
// Listener implements MessageListener.
consumer.setMessageListener(new Listener());
System.out.println("Receiver is ready, waiting for messages...");
System.out.println("press Ctrl+c to shutdown...");
while (true) {
Thread.sleep(1000);
}
} catch(InterruptedException e) {
e.printStackTrace();
}
Message Listenerはこんな感じ。動作確認用途なので非常にシンプルにしている。
public class Listener implements MessageListener {
public void onMessage(Message m) {
try {
TextMessage msg = (TextMessage) m;
// Show message
System.out.printf("[Dequeued message at %s] %s\n", (new Date()).toString(), msg.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
Topicのサブスクリプション
通常、Topicからのメッセージを受け取る時は事前にサブスクリプションを作成し、そのサブスクリプションをリスニングするが、まだサブスクリプションが存在しない場合、JMSConsumer オブジェクト作成時に自動的にサブスクリプションを作成する。また、JMSConsumerオブジェクトのクローズ時に作成したサブスクリプションは削除される。この自動生成されるサブスクリプション名は、2021/01/10現在以下のルールに従って作成しているようである(今後の変更の可能性あり)。
1. JMSContext::createConsumerを使う場合
createConsumerを使った場合のサブスクリプション名ルールは以下の通り。
<GUID>$$ND
GUIDを使うのは、createConsumerではサブスクリプション名を指定できないから。NDはNon durableの意味と思われる。Client IDの指定の有無にかかわらず、区切り文字$が2個連続する。以下はその例。

2. JMSContext::createDurableConsumerを使う場合
createDurableConsumerを使った場合のサブスクリプション名ルールは以下の通り。
<コード内で指定したsubscriptionName>$<コード内で指定したclientID>$D
$は区切り文字、最後のDはDurableを明示していると思われる。なお、DurableConsumerではClient IDの指定が必須で、指定しない場合はIllegalStateRuntimeExceptionがスローされる。以下はAzure Portalで見た例(コード内で指定したsubscriptionNameはsubscription1、clientIDはLogico-1)。

3. JMSContext::createSharedConsumerを使う場合
createSharedConsumerを使った場合のサブスクリプション名ルールは以下の通り。
<コード内で指定したサブスクリプション>$<コード内で指定したClient ID>$ND
$は区切り文字、NDはNon durableを意図していると思われる。Client IDを指定しない場合、$$が2個連続する。以下はAzure Portalで見た例。上図はClient IDを指定している場合、下図は指定していない場合(コード内で指定したsubscriptionNameはsubscription1、clientIDはLogico-1)。


4. JMSContext::createSharedDurableConsumerを使う場合
createSharedDurableConsumerを使った場合のサブスクリプション名ルールは以下の通り。
a. Client IDを指定している場合
createDurableConsumerと同じ(<コード内で指定したサブスクリプション>$<コード内で指定したClient ID>$D)。
b. Client IDを指定していない場合
<コード内で指定したサブスクリプション>
以下はClient IDを指定しなかった場合の例(コード内で指定したsubscriptionNameはsubscription1)。

ということで、下表にもまとめたが、コードで指定したsubscriptionNameとAzure Portalに現れるサブスクリプション名と一致するのは、4-bの場合のみ。
メソッド | (A) コードで指定したサブスクリプション名 | (B) Client ID | (C) Azure Portalでのサブスクリプション名 |
---|---|---|---|
createConsumer | × | △ | <GUID>$$ND |
createDurableConsumer | ○ | ○ | <A>$<B>$D |
createSharedConsumer | ○ | △ | <A>$<B>$ND |
createSharedDurableConsumer | ○ | △ | Client ID未指定時:<A> Client ID指定時:<A>$<B>$D |
Azure Service BusにJMS 2.0 APIでアクセスできると何がうれしいのか
これまで、Javaアプリケーションサーバー上で稼働するオンプレミスアプリケーションをクラウドに移行する場合、IaaSへのLiftが主流であった。その理由としては
アプリケーションの変更が大変なので…
(PaaSへの移行はやりたくない)
もう塩漬けにするので…
(手を入れたくない)
といったやや後ろ向きのものもあれば、
メッセージングにJMSを多用しているだけでなくアプリケーションサーバーの機能をたくさん使っているため、結果としてIaaSへのLiftのみを選択せざるを得ない
JMSの分散トランザクションが必須なので…
といったやむを得ないものもあった(本当に分散トランザクションが必要なユースケースはそれほど多くないはずだが、この場ではその議論はしない)。
しかし、例えば以下のような状況があてはまるのであれば、Managed message brokerとしてのAzure Service Busの利活用が選択肢に入る可能性がある。また、JMS 2.0 APIが使えるということで、これまでJava/Jakarta EEを使った開発になれている人にとっては学習に費やす時間が少なくてすむであろう。
- API呼び出しを使うものの、やはりメッセージングも必要で、順序保証かつ永続化が必須
- 既存アプリケーションでJMSを使っているが、アプリケーションサーバー固有のJMSの機能は使っていない
- 既存アプリケーションを最新の開発フレームワーク(Helidon、Quarkus、Micronautなど)ベースに書き換えてモダナイズを予定している。
- IaaSではなくPaaS(ここでいうPaaSは、アプリケーションをデプロイするだけでよいもの。IaaSに限りなく近いOSレベルの操作ができるものではない)への移行を目指している
- Kubernetesなどのコンテナオーケストレーションツールを使ったマイクロサービス化を目指している