JBoss EJB 3.0と拡張機能

  汎用インタセプタ
はじめに

トランザクションやセキュリティサービスのようなランタイムサービスは、メソッド呼び出し時にBeanオブジェクトに対して適用されます。内部では、それらのサービスはしばしば、コンテナによって管理されるインタセプタメソッドとして実装されます。インタセプタメソッドはサービスアクションを行うため (たとえばトランザクションをコミットしたりセキュリティ例外を投げるため)、対象メソッドの呼び出し前後に実行されます。

EJB 3.0では開発者はEJBメソッドのためにカスタムインタセプタを書くことができます。これはコンテナが拡張可能であることを意味しています。あなたは独自のサービスを開発、再利用、出荷することができます。あるいはトランザクションやセキュリティサービスを再実装し、コンテナのデフォルトサービスが提供するものを上書きすることさえできます。このトレイルでは、EJB 3.0インタセプタがどのように働くかを論じていきます。

サンプルアプリケーション

このトレイルのサンプルアプリケーションはセッションベースの投資計算プログラムのために開発した2つのカスタムインタセプタを紹介します。ひとつ目のインタセプタは計算レコードの履歴数を4つに制限します。「計算」ボタンを複数回押したとき、計算フォームの下に表示される直近の計算レコードが4つまでしか表示されないことがわかるでしょう。もう一方のインタセプタは計算処理に含まれるBeanの各メソッドのトレースと処理時間を提供します。トレースはページの最下部に表示されます。

Beanクラス内のインタセプタ

カスタムインタセプタメソッドはBeanクラス内に置くことができます。メソッドは@AroundInvokeタグでアノテーションを付けられなければなりません。このメソッドはBeanオブジェクト内の他のビジネスメソッドが呼び出される前に、コンテナから呼ばれます。コンテナは現在の呼び出しコンテキストをctxオブジェクトとしてインタセプタメソッドに渡します。呼び出しコンテキストから、現在のBeanオブジェクトと、このインタセプタによってインタセプトされた対象メソッドが分かります。次の例では、インタセプタメソッドは、セッション内に4つを超えた計算記録がキャッシュされていないか検査します。もしそうであれば、インタセプタは最も古い記録を消し、新しい計算記録のための場所を空けます。メソッドの最後に書かれたctx.proceed()は再帰的に次のインタセプタに進むことを指示します。これ以上インタセプタがなければ、対象メソッドが実行されます。


@Stateful
public class InterceptorCalculator implements Calculator, Serializable {

  // ... ...
  
  @AroundInvoke
  public Object limitStateSize (InvocationContext ctx)
                                      throws Exception {

    // Remove the earliest entry when the history
    // list gets too long
    if (starts.size() > 4) {
      starts.remove (0);
      ends.remove (0);
      growthrates.remove (0);
      savings.remove (0);
      results.remove (0);
    }
    return ctx.proceed();
  }
}
インタセプタクラス

インタセプタを定義するもうひとつのやり方は、別のクラスにインタセプタメソッドを置き、@Interceptorタグで対象Beanクラスにアノテーションを付けることです。次のコードは、対象Beanオブジェクトの全メソッド呼び出しの呼び出しトレースと実行時間をログ出力するインタセプタクラスです。インタセプタ内のctx.proceed()は次のインタセプタと対象メソッドを再帰的に呼び出すことを思い出してください。なので、finally内のコードは、対象メソッドと下流のインタセプタがすべて返った後に呼ばれます。


public class Tracer {

  @AroundInvoke
  public Object log (InvocationContext ctx)
                            throws Exception {

    String className = ctx.getBean().getClass().getName();
    String methodName = ctx.getMethod().getName();
    String target = className + "." + methodName + "()";

    long start = System.currentTimeMillis();
    System.out.println ("Invoking " + target);
    try {
      return ctx.proceed();
    } catch(Exception e) {
      throw e;
    } finally {
      System.out.println("Exiting " + target);
      cal.setTrace(cal.getTrace() + "
" + "Exiting " + target); long time = System.currentTimeMillis() - start; System.out.println("This method takes " + time + "ms to execute"); } } }

次はBeanクラスの@Interceptorアノテーションです。


@Stateful
@Interceptors (Tracer.class)
public class InterceptorCalculator implements Calculator, Serializable {

  // ... ...
}

@Interceptorsアノテーションを使って、Beanに複数のインタセプタクラスを指定することもできます。それらのインタセプタはアノテーションの属性配列に現れる順に呼び出されます。Beanクラス内のインタセプタメソッドは最後に呼び出されます。

ソースコード参照

サーバ

  • Calculator.java: セッションBeanインターフェース
  • InterceptorCalculatorBean.java: 履歴のキャッシュサイズを制限するインタセプタメソッドを持ったセッションBean
  • Tracer.java: ロギング、トレーシングインタセプタのためのインタセプタクラス

クライアント

まとめ

このトレイルでは、コンテナサービスの領域を拡張するため、EJB 3.0インタセプタの使い方を学びました。ここまでで、EJB 3.0仕様とJBossの拡張APIの外観をほぼすべてカバーしたことになります。