アノテーション駆動型のリスナーエンドポイント

メッセージを非同期で受信する最も簡単な方法は、アノテーション付きのリスナーエンドポイントインフラストラクチャを使用することです。簡単に言えば、管理対象の Bean のメソッドを JMS リスナーエンドポイントとして公開できます。次の例は、その使用方法を示しています。

@Component
public class MyService {

	@JmsListener(destination = "myDestination")
	public void processOrder(String data) { ... }
}

前の例の考え方は、メッセージが jakarta.jms.DestinationmyDestination で使用可能であるときはいつでも、それに応じて processOrder メソッドが呼び出されるということです(この場合、JMS メッセージの内容で、MessageListenerAdapter が提供するものと同様です)。

アノテーション付きエンドポイントインフラストラクチャは、JmsListenerContainerFactory を使用して、アノテーション付きメソッドごとに背後でメッセージリスナーコンテナーを作成します。このようなコンテナーは、アプリケーションコンテキストに対して登録されていませんが、JmsListenerEndpointRegistry Bean を使用して管理目的で簡単に見つけることができます。

@JmsListener は Java 8 の繰り返し可能なアノテーションであるため、@JmsListener 宣言を追加することにより、複数の JMS 宛先を同じメソッドに関連付けることができます。

リスナーエンドポイントアノテーションを有効にする

@JmsListener アノテーションのサポートを有効にするには、次の例に示すように、@EnableJms を @Configuration クラスの 1 つに追加できます。

@Configuration
@EnableJms
public class AppConfig {

	@Bean
	public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
		DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
		factory.setConnectionFactory(connectionFactory());
		factory.setDestinationResolver(destinationResolver());
		factory.setSessionTransacted(true);
		factory.setConcurrency("3-10");
		return factory;
	}
}

デフォルトでは、インフラストラクチャは、メッセージリスナーコンテナーを作成するために使用するファクトリのソースとして、jmsListenerContainerFactory という名前の Bean を探します。この場合 (JMS インフラストラクチャの設定を無視して)、コアプールサイズを 3 スレッド、最大プールサイズを 10 スレッドにして、processOrder メソッドを呼び出すことができます。

リスナーコンテナーファクトリをカスタマイズして各アノテーションに使用するか、JmsListenerConfigurer インターフェースを実装して明示的なデフォルトを設定できます。デフォルトは、特定のコンテナーファクトリなしで少なくとも 1 つのエンドポイントが登録されている場合にのみ必要です。詳細と例については、JmsListenerConfigurer (Javadoc) を実装するクラスの javadoc を参照してください。

XML 構成を好む場合、次の例に示すように、<jms:annotation-driven> 要素を使用できます。

<jms:annotation-driven/>

<bean id="jmsListenerContainerFactory"
		class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="destinationResolver" ref="destinationResolver"/>
	<property name="sessionTransacted" value="true"/>
	<property name="concurrency" value="3-10"/>
</bean>

プログラムによるエンドポイント登録

JmsListenerEndpoint は、JMS エンドポイントのモデルを提供し、そのモデルのコンテナーの構成を担当します。このインフラストラクチャにより、JmsListener アノテーションによって検出されるエンドポイントに加えて、プログラムでエンドポイントを構成できます。次の例は、その方法を示しています。

@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

	@Override
	public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
		SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
		endpoint.setId("myJmsEndpoint");
		endpoint.setDestination("anotherQueue");
		endpoint.setMessageListener(message -> {
			// processing
		});
		registrar.registerEndpoint(endpoint);
	}
}

前の例では、実際の MessageListener を呼び出して提供する SimpleJmsListenerEndpoint を使用しました。ただし、独自のエンドポイントバリアントを作成して、カスタム呼び出しメカニズムを記述することもできます。

@JmsListener の使用を完全にスキップし、JmsListenerConfigurer を介してエンドポイントのみをプログラムで登録できることに注意してください。

アノテーション付きエンドポイントメソッドシグネチャー

これまで、エンドポイントに単純な String を注入していましたが、実際には非常に柔軟なメソッドシグネチャーを持つことができます。次の例では、Order にカスタムヘッダーを挿入するように書き換えます。

@Component
public class MyService {

	@JmsListener(destination = "myDestination")
	public void processOrder(Order order, @Header("order_type") String orderType) {
		...
	}
}

JMS リスナーエンドポイントに挿入できる主な要素は次のとおりです。

  • 生の jakarta.jms.Message またはそのサブクラス(受信メッセージ型と一致する場合)。

  • ネイティブ JMS API へのオプションのアクセス用の jakarta.jms.Session (たとえば、カスタム応答を送信するため)。

  • 受信 JMS メッセージを表す org.springframework.messaging.Message。このメッセージには、カスタムヘッダーと標準ヘッダー(JmsHeaders で定義されている)の両方が含まれていることに注意してください。

  • @Header - 標準の JMS ヘッダーを含む特定のヘッダー値を抽出するためのアノテーション付きメソッド引数。

  • すべてのヘッダーにアクセスするために java.util.Map にも割り当て可能である必要がある @Headers アノテーション付き引数。

  • サポートされている型(Message または Session)のいずれでもないアノテーションのない要素は、ペイロードと見なされます。パラメーターに @Payload アノテーションを付けることで、これを明示的にすることができます。@Valid を追加して、検証をオンにすることもできます。

Spring の Message 抽象化を挿入する機能は、トランスポート固有の API に依存することなく、トランスポート固有のメッセージに格納されているすべての情報を活用するのに特に役立ちます。次の例は、その方法を示しています。

@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }

メソッド引数の処理は DefaultMessageHandlerMethodFactory によって提供され、追加のメソッド引数をサポートするためにさらにカスタマイズできます。変換と検証のサポートもそこでカスタマイズできます。

たとえば、処理する前に Order が有効であることを確認したい場合、次の例に示すように、ペイロードに @Valid でアノテーションを付け、必要なバリデーターを構成できます。

@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

	@Override
	public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
		registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
	}

	@Bean
	public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
		DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
		factory.setValidator(myValidator());
		return factory;
	}
}

レスポンス管理

MessageListenerAdapter の既存のサポートにより、メソッドは void 以外の戻り値型をすでに持つことができます。その場合、呼び出しの結果は jakarta.jms.Message にカプセル化され、元のメッセージの JMSReplyTo ヘッダーで指定された宛先、またはリスナーに設定されたデフォルトの宛先のいずれかに送信されます。メッセージング抽象化の @SendTo アノテーションを使用して、デフォルトの宛先を設定できるようになりました。

processOrder メソッドが OrderStatus を返すようになったと仮定すると、次の例に示すように、自動的にレスポンスを送信するように記述できます。

@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
	// order processing
	return status;
}
複数の @JmsListener アノテーション付きメソッドがある場合は、クラスレベルで @SendTo アノテーションを配置して、デフォルトの応答先を共有することもできます。

トランスポートに依存しない方法で追加のヘッダーを設定する必要がある場合は、次のような方法で代わりに Message を返すことができます。

@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
	// order processing
	return MessageBuilder
			.withPayload(status)
			.setHeader("code", 1234)
			.build();
}

実行時にレスポンスの宛先を計算する必要がある場合は、実行時に使用する宛先も提供する JmsResponse インスタンスにレスポンスをカプセル化できます。前の例を次のように書き換えることができます。

@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
	// order processing
	Message<OrderStatus> response = MessageBuilder
			.withPayload(status)
			.setHeader("code", 1234)
			.build();
	return JmsResponse.forQueue(response, "status");
}

最後に、優先度や存続時間など、レスポンスにいくつかの QoS 値を指定する必要がある場合、次の例に示すように、それに応じて JmsListenerContainerFactory を構成できます。

@Configuration
@EnableJms
public class AppConfig {

	@Bean
	public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
		DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
		factory.setConnectionFactory(connectionFactory());
		QosSettings replyQosSettings = new QosSettings();
		replyQosSettings.setPriority(2);
		replyQosSettings.setTimeToLive(10000);
		factory.setReplyQosSettings(replyQosSettings);
		return factory;
	}
}