第21章 非同期性とメッセージング


Seam では Web 要求からの作業を非同期で容易に行うことができます。 Java EE での非同期は通常 JMS とつながっています。 サービス要件が厳密で明確に定義されている場合にはこれは適切な方法です。 Seam コンポーネントからの JMS メッセージの送信は簡単です。
しかし、多くのユースケースで JMS は必要以上にパワフルです。 Seam は選択した ディスパッチャ に対してシンプルで非同期なメソッドとイベントの機能を階層化します。
  • java.util.concurrent.ScheduledThreadPoolExecutor (デフォルト)
  • EJB タイマーサービス (EJB 3.0 環境向け)
  • Quartz

21.1. 非同期性

非同期のイベントやメソッドの呼び出しは、基礎となるディスパッチャのメカニズムと同等のサービスが期待されます。 ScheduledThreadPoolExecutor をベースとするデフォルトのディスパッチャは効率的な働きをしますが、 永続非同期のタスクに対応していないためタスクが実際に実行されるかは保証されません。 EJB 3.0 に対応する環境で作業している場合は次の行を components.xml に追加して、 コンテナの EJB タイマーサービスによりタスクが非同期に処理されるようにします。
<async:timer-service-dispatcher/>
Copy to Clipboard Toggle word wrap
Seam で非同期メソッドを使用する場合、 タイマーサービスを直接操作する必要はありません。 ただし重要なことは、EJB3 実装には永続タイマーを使用するオプションがあるため、タスクが最終的には処理されることが保証されるという点です。
別の方法としては、 オープンソースの Quartz ライブラリを使って非同期メソッドを管理する方法です。 この場合、 EAR に Quartz ライブラリ JAR (lib ディレクトリ) を同梱してから application.xml で Java モジュールとして宣言します。 Quartz ディスパッチャはクラスパスに Quartz プロパティファイルを追加すると設定可能になります。このファイルは seam.quartz.properties という名前にしてください。 また、 Quartz ディスパッチャをインストールする場合は次の行を components.xml に追加する必要があります。
<async:quartz-dispatcher/>
Copy to Clipboard Toggle word wrap
Quartz Scheduler、 EJB3 Timer、 デフォルトの ScheduledThreadPoolExecutor の Seam API は非常によく似ているため、 components.xml に 1 行追加するだけで「プラグアンドプレイ」が可能です。

21.1.1. 非同期メソッド

非同期呼び出しにより、呼び出し側に対してメソッド呼び出しを非同期に (別のスレッドで) 処理することが可能です。通常クライアントに直ちに応答を返し、コスト高の作業をバックグラウンドで処理させたい場合には非同期呼び出しが使用されます。このパターンはクライアントが処理結果をサーバへ自動的にポーリングできるような AJAX を使用するアプリケーションでとても効果的です。
EJB コンポーネントの場合、Bean の実装にアノテーションを付与して、メソッドが非同期に処理されるよう指定します。JavaBean コンポーネントの場合は、コンポーネント実装クラスにアノテーションを付与します。
@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
  @Asynchronous
  public void processPayment(Payment payment) {
    //do some work!
  }
}
Copy to Clipboard Toggle word wrap
非同期性の使用は Bean クラスには透過的です。また、クライアントにも透過的です。
@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
  @In(create=true) PaymentHandler paymentHandler;
  @In Bill bill;
    
  public String pay() {
    paymentHandler.processPayment( new Payment(bill) );
    return "success";
  }
}
Copy to Clipboard Toggle word wrap
非同期メソッドは新しいイベントコンテキストで処理され、 呼び出し側のセッションまたは対話コンテキストの状態にはアクセスできません。 しかしビジネスプロセスコンテキストは 伝播されます
実行を遅らせるために @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);
}
Copy to Clipboard Toggle word wrap
@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";
  }
}
Copy to Clipboard Toggle word wrap
クライアントとサーバーはいずれも呼び出しに関連付けられた Timer オブジェクトにアクセスすることができます。 以下に示す Timer オブジェクトは EJB3 ディスパッチャで使用される EJB3 タイマーです。 デフォルトの ScheduledThreadPoolExecutor の場合、 タイマーは JDK から Future を返します。 Quartz ディスパッチャの場合は QuartzTriggerHandle を返します。 これについては次項で説明していきます。
@Local
public interface PaymentHandler
{
  @Asynchronous
  public Timer processScheduledPayment(Payment payment, 
                                       @Expiration Date date);
}
Copy to Clipboard Toggle word wrap
@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
  }
}
Copy to Clipboard Toggle word wrap
@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";
  }
}
Copy to Clipboard Toggle word wrap
非同期メソッドは呼び出し側に他のどんな値も返すことができません。

21.1.2. Quartz ディスパッチャを使った非同期メソッド

Quartz ディスパッチャでは上記のような @Asynchronous@Duration@Expiration@IntervalDuration のアノテーションが使用できます。 また、 他にもいくつかのアノテーションに対応しています。
@FinalExpiration アノテーションは反復タスクの終了日を指定します。 QuartzTriggerHandle をインジェクトできる点に注意してください。
@In QuartzTriggerHandle timer;
        
// Defines the method in the "processor" component
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                           @IntervalDuration Long interval,
                                           @FinalExpiration Date endDate, 
                                           Payment payment) { 
  // do the repeating or long running task until endDate
}
    
... ...
    
// Schedule the task in the business logic processing code
// Starts now, repeats every hour, and ends on May 10th, 2010
Calendar cal = Calendar.getInstance ();
cal.set (2010, Calendar.MAY, 10);
processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);
Copy to Clipboard Toggle word wrap
このメソッドは QuartzTriggerHandle オブジェクトを返し、 これを使用してスケジューラの停止、 一時停止、 再開を行うことができます。 QuartzTriggerHandle オブジェクトはシリアライズ可能であるため、 長期間に渡りこれを維持する必要がある場合はデータベースに保存することができます。
QuartzTriggerHandle handle=
    processor.schedulePayment(payment.getPaymentDate(), 
                              payment.getPaymentCron(), 
                              payment);
payment.setQuartzTriggerHandle( handle );
// Save payment to DB
        
// later ...
        
// Retrieve payment from DB
// Cancel the remaining scheduled tasks
payment.getQuartzTriggerHandle().cancel();
Copy to Clipboard Toggle word wrap
@IntervalCron アノテーションはタスクのスケジューリングに Unix cron ジョブ構文をサポートします。 たとえば、 次の非同期メソッドは 3 月の毎水曜日の 2:10pm と 2:44pm に実行されます。
// Define the method
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                           @IntervalCron String cron, 
                                           Payment payment) { 
  // do the repeating or long running task
}
    
... ...
    
// Schedule the task in the business logic processing code
QuartzTriggerHandle handle = 
     processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);
Copy to Clipboard Toggle word wrap
@IntervalBusinessDay アノテーションは「第X日営業日」というシナリオの呼び出しに対応します。 たとえば、 次の非同期メソッドは毎月第2営業日の 14:00 に実行されます。 デフォルトではすべての週末とアメリカ合衆国の祝日を営業日から除外します。
// Define the method
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalBusinessDay NthBusinessDay nth, 
                                 Payment payment) { 
  // do the repeating or long running task
}
    
... ...
    
// Schedule the task in the business logic processing code
QuartzTriggerHandle handle = 
    processor.schedulePayment(new Date(), 
                              new NthBusinessDay(2, "14:00", WEEKLY), 
                              payment);
Copy to Clipboard Toggle word wrap
NthBusinessDay オブジェクトには呼び出しトリガーの設定が含まれます。 additionalHolidays プロパティで祝日を追加指定することができます (会社固有の休み、 アメリカ合衆国の祝日以外など)。
public class NthBusinessDay implements Serializable {
  int n;
  String fireAtTime;
  List<Date> additionalHolidays;
  BusinessDayIntervalType interval;
  boolean excludeWeekends;
  boolean excludeUsFederalHolidays;

  public enum BusinessDayIntervalType { WEEKLY, MONTHLY, YEARLY }

  public NthBusinessDay () {
    n = 1;
    fireAtTime = "12:00";
    additionalHolidays = new ArrayList<Date> ();
    interval = BusinessDayIntervalType.WEEKLY;
    excludeWeekends = true;
    excludeUsFederalHolidays = true;
  }     
      ... ...
}
Copy to Clipboard Toggle word wrap
@IntervalDuration@IntervalCron@IntervalNthBusinessDay のアノテーションは互いに矛盾します。 同じメソッド内で使用しようとすると RuntimeException エラーの原因になります。

21.1.3. 非同期イベント

コンポーネント駆動のイベントも非同期にすることができます。 非同期処理するためにイベントを引き起こす場合は Events クラスの raiseAsynchronousEvent() メソッドを呼び出します。 指定時刻に起きるイベントをスケジュールするには、raiseTimedEvent() メソッドを呼び出し、スケジュールオブジェクトを渡します (デフォルトのディスパッチャまたはタイマーサービスのディスパッチャの場合は TimerSchedule を使用します)。 コンポーネントは通常通りに非同期のイベントを監視することができますが、 非同期スレッドに伝播されるのはビジネスプロセスコンテキストのみです。

21.1.4. 非同期の呼び出しによる例外処理

各非同期ディスパッチャは例外がそれを通じて伝播されるとそれぞれ異なった動作をします。 たとえば、 java.util.concurrent は繰り返す呼び出しの実行がこれ以上起きないよう一時停止し、EJB3 タイマーサービスがその例外を吸収します。 したがって、 非同期の呼び出しから伝播する例外がディスパッチャに到達する前に Seam によってその例外はキャッチされます。
デフォルトでは、 非同期実行から伝播する例外はすべてエラーレベルでキャッチされログ記録されます。 org.jboss.seam.async.asynchronousExceptionHandler コンポーネントを無効にするとこの動作をグローバルにカスタマイズすることができます。
@Scope(ScopeType.STATELESS)
@Name("org.jboss.seam.async.asynchronousExceptionHandler")
public class MyAsynchronousExceptionHandler 
             extends AsynchronousExceptionHandler { 
  @Logger Log log;
   
  @In Future timer;
   
  @Override
  public void handleException(Exception exception) {
    log.debug(exception);
    timer.cancel(false);
  }
   
}
Copy to Clipboard Toggle word wrap
上記は java.util.concurrent ディスパッチャを使ってその制御オブジェクトをインジェクトし、例外が送出された場合には今後の呼び出しをすべて取り消すようにしています。
また、 コンポーネント上にメソッド public void handleAsynchronousException(Exception exception); メソッドを実装すると個別のコンポーネントに対しこの動作を変更することができます。 たとえば、
   
public void handleAsynchronousException(Exception exception) { 
  log.fatal(exception); 
}
Copy to Clipboard Toggle word wrap
トップに戻る
Red Hat logoGithubredditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

Red Hat ドキュメントについて

Red Hat をお使いのお客様が、信頼できるコンテンツが含まれている製品やサービスを活用することで、イノベーションを行い、目標を達成できるようにします。 最新の更新を見る.

多様性を受け入れるオープンソースの強化

Red Hat では、コード、ドキュメント、Web プロパティーにおける配慮に欠ける用語の置き換えに取り組んでいます。このような変更は、段階的に実施される予定です。詳細情報: Red Hat ブログ.

会社概要

Red Hat は、企業がコアとなるデータセンターからネットワークエッジに至るまで、各種プラットフォームや環境全体で作業を簡素化できるように、強化されたソリューションを提供しています。

Theme

© 2025 Red Hat