EJB 3.0(Public Draft)入門記 Simplified API Chapter3 その3

Chapter 3 のその3です。コールバックメソッドとコールバックリスナークラスの節に進みます。Interceptorとホームインタフェースの節はChapter 3 のその4で扱おうと思います。細切れだと若干見にくい気がしますが、まぁいいか。

3.4 Callbacks and Callback Listener Classes

メソッドをコールバックメソッドとして指定し、セッションBeanやメッセージ駆動型Beanに対するライフサイクルのイベントの通知を受け取るようにすることができます。コールバックメソッドはコールバックアノテーションで示すことができます。例をコピペ。

@Stateful public class ShoppingCartBean implements ShoppingCart {
  private float total;
  private Vector productCodes;
  public int someShoppingMethod(){...};
  ...
  @PreDestroy endShoppingCart() {...};
}

コールバックメソッドを直接Beanクラスに定義する代わりにコールバックリスナークラスを使うことができます。コールバックリスナークラスは関連付けるBeanクラスにCallbackListnerアノテーションを使って示します。

コールバックリスナークラスの特徴が文章で書かれていますが抜き出して箇条書きしてみます。

  • コールバックリスナーはステートレス。
  • コールバックリスナーのライフサイクルは定義されていない。
  • リスナメソッドやコールバックリスナーはアノテーションやデプロイメント記述によって静的に設定される。
  • コールバックリスナーは引数なしのpublicなコンストラクタをもたねばいけない。
  • Beanクラス上のコールバックメソッドに対して使用されるアノテーションとコールバックリスナークラス上のコールバックメソッドに対して使用されるアノテーションは同一。だが、メソッドのシグネチャが異なる。
  • ひとつのメソッドに複数の異なったコールバックアノテーションをつけることができる。
  • 同じコールバックをBeanクラスとコールバックリスナークラスの両方に指定することはできない。また、同じコールバックをBeanクラスとコールバックリスナークラスのどちらか一方に複数指定することもできない。

コールバックに適用されるルールに次のものがあるそうです。。

  • コールバックメソッドは実行時例外をスローできる。トランザクション内で実行中のコールバックメソッドが実行時例外をスローするとトランザクションロールバックされる。
  • コールバックメソッドはアプリケーション例外をスローしてはいけない。
  • コールバックリスナークラスに対する依存性注入(Dependency Injection)は定義されていない。
  • コールバックリスナーはBeanの環境に登録されているオブジェクト(entries in the bean's environment)にアクセスできる。
  • エンタープライズBeanコンポーネントに適用されるプログラミング上の制限がコールバックリスナーにも同様に適用される。

3.4.1 Method Signatures

上記のコールバックリスナークラスの特徴の箇所でも言われていますが、Beanクラス上に定義されるコールバックメソッドとコールバックリスナークラス上に定義されるコールバックメソッドでシグネチャが異なります。Beanクラス上に定義されるコールバックメソッドの場合

public void ()

となり、コールバックリスナークラス上に定義されるコールバックメソッドの場合は

public void (Object)

となります。ただし、Objectは実際のBeanの型として定義できるそうです。特定のBean用のコールバックリスナークラスの場合はBeanの型にして、汎用的なコールバックリスナークラスの場合はObject型で受けろということかな。

以下、簡単なコードを動かしてみます。まずBeanクラスにコールバックメソッドを定義した場合はこんな感じになります。

public interface Calculator {
  public int add(int x, int y);
  public int subtract(int x, int y);
}
@Stateless
@Remote({Calculator.class})
public class CalculatorBean implements Calculator {
  public int add(int a, int b) {
    return a + b;
  }
  public int subtract(int a, int b) {
    return a - b;
  }
  
  @PostConstruct
  public void init() {
    System.out.println("@PostConstruct was executed at " + this.getClass().getName());
  }
}
public class Client {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    Calculator calc = (Calculator) ctx.lookup(Calculator.class
        .getName());
    System.out.println(calc.add(100, 400));
  }
}

デプロイしてClientを実行するとJBossのコンソールに次のように出力されます。@PostConstructはコールバックアノテーションなのですが、CalculatorBeanのinitメソッドが実行されていることがわかります。

02:28:49,118 INFO  [STDOUT] @PostConstruct was executed at study.ejb.CalculatorBean


次におんなじようなことをコールバックリスナークラスを使用して行う場合はこんな感じ。

public class CalculatorCallbackListener {

  public CalculatorCallbackListener() {
  }

  @PostConstruct
  public void init(CalculatorBean arg) {
    System.out.println("@PostConstruct was executed at "
        + this.getClass().getName() + ".");
    System.out.println("Argument type is " + arg.getClass().getName());
  }
}
@Stateless
@Remote({Calculator.class})
@CallbackListener(CalculatorCallbackListener.class)
public class CalculatorBean implements Calculator {
  public int add(int a, int b) {
    return a + b;
  }
  public int subtract(int a, int b) {
    return a - b;
  }
}

コールバックリスナークラスを作成してコールバックメソッドを定義します。そしてBeanクラスにCallbackListenerアノテーションをつけます。ビジネスインタフェースとClientは変更しません。デプロイしてClientを実行するとJBossのコンソールに次のように出力されます。コールバックリスナークラスのinitメソッドが呼ばれていることがわかります。

02:41:57,231 INFO  [STDOUT] @PostConstruct was executed at study.ejb.CalculatorCallbackListener.
02:41:57,231 INFO  [STDOUT] Argument type is study.ejb.CalculatorBean

OK。
コールバックの有効的な使い方が思い浮かばないので、とりあえず文字列を出力してみました。

EJB 3.0(Public Draft)入門記 Simplified API Chapter3 その2

前回うまくいかなかったローカルインタフェースを使用したEJBへのアクセスですが、根本的に勘違いしてたのでもういちどやり直します。

  • 登場人物はClientとShoppingCartBeanとCalculatorBean。
  • ShoppingCartBeanはリモートインタフェースを持っているステートフルセッションBean。
  • CalculatorBeanはローカルインタフェースを持っているステートレスセッションBean。(CalculatorにもCalculatorBeanにも@Localや@Remoteが指定されていませんが、Beanクラスが単一のインタフェースを実装している場合はそのインタフェースはデフォルトでローカルインタフェースになります。「3.2 Business Interfaces」より。)
  • Client → ShoppingCartBean → CalculatorBeanと呼び出します。(前回はClient → CalculatorBeanとしていて、EJBコンテナ外からローカルインタフェースを使用したEJBアクセスを行っていたためうまくいかないのでした。)

ソースコードはこんなカンジ。ほぼJBossのサンプルのパクリだったりします。

public interface Calculator {
  public int add(int x, int y);
  public int subtract(int x, int y);
}
@Stateless
public class CalculatorBean implements Calculator {
  public int add(int a, int b) {
    return a + b;
  }
  public int subtract(int a, int b) {
    return a - b;
  }
}
public interface ShoppingCart {
     void buy(String product, int quantity);
     HashMap getCartContents();
}
@Stateful
@Remote({ShoppingCart.class})
public class ShoppingCartBean implements ShoppingCart {

  private HashMap cart = new HashMap();

  public void buy(String product, int quantity) {
    if (cart.containsKey(product)) {
      int currq = cart.get(product);
      cart.put(product, getCalculator().add(currq, quantity));
    } else {
      cart.put(product, quantity);
    }
  }

  public HashMap getCartContents() {
    return cart;
  }

  private Calculator getCalculator() {
    try {
      InitialContext ctx = new InitialContext();
      return (Calculator) ctx.lookup(Calculator.class.getName());
    } catch (NamingException e) {
      throw new RuntimeException(e);
    }
  }
}
public class Client {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    ShoppingCart cart = (ShoppingCart) ctx.lookup(ShoppingCart.class.getName());
    cart.buy("おにぎり", 3);
    cart.buy("お茶", 2);
    cart.buy("おにぎり", 1);
    System.out.println(cart.getCartContents());
  }
}

ちなみに、ShoppingCartBeanがCalculatorを知るには@EJBアノテーションが使えるはずなのですが、とりあえず自分でlookupしてみました。

前回のbuild.xmlでデプロイしてClientを実行すると結果はこうなります。

{おにぎり=4, お茶=2}

成功です! あと、サーブレットからもCalculatorBeanが呼び出せました。

追記
コードが間違っていたので直しました。実はShoppingCartBeanがShoppingCartをimplementsしていなかったのです。でもこれはこれで動いていました。どうやら必ずしもインタフェースを実装していなくても@Remote({ShoppingCart.class})というアノテーションでビジネスインタフェースが示せれば動くようです、少なくともJBossでは。既存のEJBっぽいといえるのかな?