Seamでは、Webリクエストに対して非同期に処理を行うことが非常に簡単にできます。 Java EEでの非同期性といえば、多くの人はJMSを思い浮かべるでしょう。 これは確かにSeamでこの問題にアプローチするひとつの方法です。そして厳密で明確なサービス品質の要件があるならば、これは適切な方法です。 Seamでは、Seamコンポーネントを使って簡単にJMSメッセージを送受信できます。
しかし多くのユースケースではJMSは過剰です。 Seamはシンプルな非同期メソッドとイベント機関をEJB 3.0タイマーサービス上にレイヤ化します。
非同期イベントとメソッド呼び出しは、コンテナのEJBタイマーサービスと同程度のサービス品質を期待できます。 タイマーサービスに親しんでいなくても心配は不要です。Seamで非同期メソッドを使うなら、タイマーサービスを直接触る必要はありません。
非同期メソッドとイベントを使うには、components.xmlに以下の行を追加する必要があります:
<core:dispatcher/>
EJB 3.0をサポートしない環境では、この機能は使えないことに注意して下さい。
最も単純なかたちでは、非同期呼び出しは、メソッド呼び出しを呼び出し側に対して非同期に (異なるスレッドで) 処理させるだけです。 我々は通常、クライアントに即座にレスポンスを返し、重い仕事をバックグラウンドで処理させたい場合に、非同期呼び出しを使います。 このパターンは、クライアントが処理結果をサーバへ自動的にポーリングできるような、AJAXを使用するアプリケーションでとても効果的です。
EJBコンポーネントでは、ローカルインターフェースをアノテートしてメソッドが非同期に処理されるよう指定します。
@Local public interface PaymentHandler { @Asynchronous public void processPayment(Payment payment); }
(JavaBean コンポーネントでは、 望むならコンポーネントの実装クラスをアノテートすることができます)
非同期性の使用はbeanクラスに透過的です。
@Stateless @Name("paymentHandler") public class PaymentHandlerBean implements PaymentHandler { public void processPayment(Payment payment) { //do some work! } }
そしてクライアントに対しても透過的です。
@Stateful @Name("paymentAction") public class CreatePaymentAction { @In(create=true) PaymentHandler paymentHandler; @In Bill bill; public String pay() { paymentHandler.processPayment( new Payment(bill) ); return "success"; } }
非同期メソッドは完全に新規のイベントコンテキストで処理され、 呼び出し側のセッションまたは対話コンテキストの状態にはアクセスできません。 しかしビジネスプロセスコンテキストは伝播されます。
非同期メソッド呼び出しは@Duration、@Expiration、 @IntervalDurationアノテーションを使って、 後の実行のためにスケジューリングできます。
@Local public interface PaymentHandler { @Asynchronous public void processScheduledPayment(Payment payment, @Expiration Date date); @Asynchronous public void processRecurringPayment(Payment payment, @Expiration Date date, @IntervalDuration Long interval)' }
@Stateful @Name("paymentAction") public class CreatePaymentAction { @In(create=true) PaymentHandler paymentHandler; @In Bill bill; public String schedulePayment() { paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() ); return "success"; } public String scheduleRecurringPayment() { paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(), ONE_MONTH ); return "success"; } }
クライアントとサーバの両者は、呼び出しに関連付けられたTimerオブジェクトに アクセスできます。
@Local public interface PaymentHandler { @Asynchronous public Timer processScheduledPayment(Payment payment, @Expiration Date date); }
@Stateless @Name("paymentHandler") public class PaymentHandlerBean implements PaymentHandler { @In Timer timer; public Timer processScheduledPayment(Payment payment, @Expiration Date date) { //do some work! return timer; //note that return value is completely ignored } }
@Stateful @Name("paymentAction") public class CreatePaymentAction { @In(create=true) PaymentHandler paymentHandler; @In Bill bill; public String schedulePayment() { Timer timer = paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() ); return "success"; } }
非同期メソッドは呼び出し側に他のどんな値も返すことができません。
Seam は Seam コンポーネントの JMS メッセージの送受信を容易にしています。
JMS メッセージ送信のための Seam インフラストラクチャの設定として、 Seam に対しメッセージ送信する Topic または、Queue を指示する必要があります。 また、 QueueConnectionFactory あるいは、 TopicConnectionFactory の場所を Seam に指示する必要もあります。
Seam はデフォルトで、JBossMQ 用の通常のコネクションファクトリ UIL2ConnectionFactory を使用します。 もし、その他の JMS プロバイダを使用する場合には、 seam.properties、web.xml あるいは、components.xml 中の queueConnection.queueConnectionFactoryJndiName、 topicConnection.topicConnectionFactoryJndiName の一方あるいは両方を設定する必要があります。
Seam 管理の TopicPublisher または、 QueueSender をインストールするために、 components.xml 中に topic または queue を記入する必要もあります。
<jms:managed-topic-publisher name="stockTickerPublisher" auto-create="true" topic-jndi-name="topic/stockTickerTopic"/> <jms:managed-queue-sender name="paymentQueueSender" auto-create="true" queue-jndi-name="queue/paymentQueue"/>
JMS TopicPublisher や、 TopicSession をコンポーネントにインジェクトすることが可能です。
@In private TopicPublisher stockTickerPublisher; @In private TopicSession topicSession; public void publish(StockPrice price) { try { topicPublisher.publish( topicSession.createObjectMessage(price) ); } catch (Exception ex) { throw new RuntimeException(ex); } }
あるいは、Queue 連携することも可能です。
@In private QueueSender paymentQueueSender; @In private QueueSession queueSession; public void publish(Payment payment) { try { paymentQueueSender.send( queueSession.createObjectMessage(payment) ); } catch (Exception ex) { throw new RuntimeException(ex); } }
EJB3 メッセージ駆動型 Bean を利用してメッセージ処理が可能です。 メッセージ駆動型 Bean は Seam コンポーネントとすることも可能です。 この場合、イベントまたはアプリケーションスコープの Seam コンポーネントのインジェクトが可能です。