EJB 3.0(Public Draft)入門記 Simplified API Chapter 9

すでに今日は入門記書きましたがサボっていた分をとりもどすということでもうちょと書きます。

chapte 9です。この章は「Compatibility and Migration」ということでEJB 3.0 とそれ以前のコンポーネントやクライアントとの互換性と移行の問題について言及するそうです。

9.1 Support for Exisiting Applications

既存のEJB 2.1以前のアプリケーションはEJB 3.0のコンテナで変更することなく動かなければいけない(must)そうです。さらにすべてのEJB 3.0の実装はEJB 1.1、 EJB 2.0、 EJB 2.1、デプロイメント記述をサポートしなければいけない(must)そうです。

9.2 Interoperability of EJB 3.0 and Earlier Components

9.2.1 Clients written to the EJB 2.x APIs

EJB 2.1以前のAPIに対して書かれたエンタープライズBeanはコンパイルや書き直しすることなくEJB 3.0コンポーネントのクライアントにもなれるそうです。同一トランザクションEJB 3.0コンポーネントとそれ以前のコンポーネントへのアクセスを含めても問題なしだそうです。詳しいメカニズムは9.3節で述べられるようですが、RemoteHomeアノテーションやLocalHomeアノテーションを使うみたいです。

9.2.2 Clients written to the new EJB 3.0 API

EJB 3.0APIに対して書かれたクライアントはEJB 2.1以前のクライアントにもなれるらしいです。同一トランザクションEJB 3.0コンポーネントとそれ以前のコンポーネントへのアクセスを含めても問題なしだそうです。で、そのときホームインタフェースはEJBアノテーションでDIできるとか。

9.2.3 Combined use of EJB 2.x and EJB 3.0 persistence APIs

別々のトランザクションでも同一のトランザクション内でも、EJBのクライアントは 3.0のエンティティやEntity ManagerとEJB 2.xのエンティティビーンを一緒に使えるそうでです。

9.2.4 Other Combinations of EJB 3.0 and Earlier APIs

単一のコンポーネントクラス内で新しいEJB 3.0APIがどのように既存のEJBAPIと一緒に使われるかが「EJB Core Contracts and Requirements」で示されているそうです。

9.3 Adapting EJB 3.0 Session Beans to Earlier Client Views

EJB 3.0のセッションBeanはEJB 2.1以前のクライアントから見たインタフェースに適合できるそうで、それにはEJBHomeアノテーションとEJBLocalHomeアノテーションを使えばいいみたいです。(アノテーションと同等の機能のデプロイメント記述もあるようです。)

9.3.1 Stateless Session Beans

ホームインタフェースのcreate()メソッドは対応するリモートもしくはローカルのコンポーネントインタフェースを返さなければいけない。コンテナの実装によるがこのときインスタンスの生成が行われるかもしれないし行われないかもしれない。たとえば、コンテナは(プーリング戦略で)事前にBeanインスタンスをアロケートしたり、最初のビジネスメソッドの呼び出しまでBeanインスタンスの生成を遅らせるかもしれない。Beanインスタンスが生成されるとき、コンテナは(もし存在すれば)setSessionContextメソッドを呼び出し、Dependency Injectionを実行し、(もし存在すれば)PostConstructコールバックメソッドを呼び出す。
インスタンス生成同様にEJBHomeのremove(Handle)メソッドやEJBObjectやEJBLocalObjectのremove()メソッドの呼び出しがBeanインスタンスを除去することについても実装依存だ。Beanインスタンスが除去されるとき、(もし存在すれば)PreDestroyコールバックメソッドが呼び出される。
...といったようなことが書かれています。

9.3.2 Stateful Session Beans

create()の呼び出しによって次のことが起こります。Beanインスタンスの生成、(もし存在すれば)PostConstructコールバックメソッドの呼び出し、Initメソッドが呼び出し、対応するリモートもしくはローカルのコンポーネントインタフェースのreturn。これらのメソッドの呼び出しはクライアントのcreateメソッドの実行と同じトランザクションコンテキスト、セキュリティコンテキスト内で行われる。
EJBHomeのremove(Handle)メソッドやEJBObjectやEJBLocalObjectのremove()メソッドの呼び出しによって(もし存在すれば)PreDestroyコールバックメソッドが呼び出され、Beanインスタンスが除去される。
Initアノテーションが使われEJBHomeやEJBLocalHomeインタフェースのcreateメソッドの対応としてBeanクラスのメソッドを指定することができる。Initメソッドの戻り値はvoidでパラメータの型はcreateメソッドとまったく同じでなければいけない。
...といったようなことが書かれています。

9.4 Combined Use of EJB 3.0 and EJB 2.1 APIs in a Bean Class

アノテーションとjavax.ejb.EnterpriseBeanインタフェースの実装をいっしょに使うことが認められていてEJB 3.0のシンプルになったプログラミングモデルへの移行に役立てられるらしいです。

セッションBeanはEJB 2.1の仕様に則ってEJBHome、EJBLocalHome、EJBObject、EJBLocalObjectなどを定義できるそうです。

EJB 3.0とそれ以前のEJBの組み合わせの要件は例のごとく「EJB Core Contracts and Requirements」を参照しろということです。


では実験コーナー〜。今回はこんなことをやってみます。

  • EJB 3.0のステートレスセッションBeanとステートフルセッションBeanをEJB 2.1以前の呼び出し方で実行する。
  • そのためにRemoteHomeアノテーションを使う。(実はJBossでjavax.ejb.RemoteHomeがjboss-ejb3x.jarの中に見当たらなかったのです。まだ用意されてないのかなぁ。その代わりorg.jboss.annotation.ejb.RemoteHomeがありました。何か同じ機能のような気がする...。ということでorg.jboss.annotation.ejb.RemoteHomeを使いました。)
  • ステートフルセッションBeanに@Initアノテーションを使う。

まず、ステートレスセッションBean。
ホームインタフェース

public interface CalculatorHome extends EJBHome {
  public Calculator create() throws RemoteException, CreateException;
}

ビジネスインタフェース

public interface Calculator {
  public int add(int x, int y);
  public int subtract(int x, int y);
}

Beanクラス:@RemoteHomeを使ってます。

@Stateless
@Remote( { Calculator.class })
@RemoteHome(CalculatorHome.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です。
ホームインタフェース

public interface ShoppingCartHome {
  public ShoppingCart create(String arg) throws RemoteException, CreateException;
}

ビジネスインタフェース

public interface ShoppingCart {
  public void buy(String product, int quantity);
  public HashMap getCartContents();
}

Beanクラス:@RemoteHomeと@Initを使っています。

@Stateful
@Remote( { ShoppingCart.class })
@RemoteHome(ShoppingCartHome.class)
public class ShoppingCartBean implements ShoppingCart, Serializable {

  private HashMap cart = new HashMap();

  public void buy(String product, int quantity) {
    if (cart.containsKey(product)) {
      cart.put(product, cart.get(product) + quantity);
    } else {
      cart.put(product, quantity);
    }
  }
  
  public HashMap getCartContents() {
    return cart;
  }
  
  @Init
  public void init(String arg) {
    System.out.println(arg);
  }
}


次に上記のステートレス/ステートフルセッションBeanをEJB 2.1の呼び出し方で呼び出すクライアントです。
クライアント

public class Client {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    
    // stateless session bean
    Object calcRef = ctx.lookup(Calculator.class.getName());
    CalculatorHome calcHome = (CalculatorHome) PortableRemoteObject.narrow(calcRef, CalculatorHome.class);
    Calculator calc = calcHome.create();
    System.out.println("1 + 2 = " + calc.add(1,2));
    
    // stateful session bean
    Object shopCartRef = ctx.lookup(ShoppingCart.class.getName());
    ShoppingCartHome shopCartHome = (ShoppingCartHome) PortableRemoteObject.narrow(shopCartRef, ShoppingCartHome.class);
    ShoppingCart shopCart = (ShoppingCart) shopCartHome.create("create ShppingCart Bean");
    shopCart.buy("プリン", 1);
    shopCart.buy("プリン", 2);
    System.out.println(shopCart.getCartContents());
  }
}


JBossコンソール出力結果

23:49:26,067 INFO  [STDOUT] create ShppingCart Bean

デプロイしてクライアントを実行してみると、Initアノテーションをつけたメソッドが呼び出されていることがわかります。クライアント側の標準出力は書く必要なさそうですが一応こうなります。

1 + 2 = 3
{プリン=3}

@RemoteHomeの使い方がいまいちわからなかったのですがちゃんと動いたみたいなのでOK。でも使ったのはjavax.ejb.RemoteHomeではなくてあくまでもJBossのRemoteHomeアノテーションですが。


EJB 3.0って既存のEJBとの互換性にこだわってますねー。既存のEJBとの互換性を残すことが負の遺産ってことになったりはしないんでしょうか。
chapter 9 終わり。4ページでした。

EJB 3.0(Public Draft)入門記 Simplified API Chapter 8

お盆休みを理由に入門記サボっていました。いけない、いけない。

サボり癖がついてしまいましたが、なんとか復活。Chapter 8 Enterprise Bean Context and Environment に進みます。


エンタープライズBeanのコンテキストは次のものを含んでいるそうです。

  • コンテナのコンテキスト
  • リソース
  • 環境のコンテキスト

コンテキスト内のリソースや環境エントリへの参照はコンテナが提供するとあります。そして、インスタンス変数やセッターメソッドがDependency Injectionのターゲットとしてアノテートされるそうです。
アノテーションを使ったDependency Injectionの代わりに、javax.ejb.EJBContextインタフェースに追加されたlookupメソッドを使うこともできるそうです。
どちらの方法をとるにしてもコンテキストの依存を表すためにメタデータアノテーションが使われる、とあります。

環境エントリ(environment entries)ってJNDI ENCでアクセスできるEJBのことを指していると思ったのですが、デプロイメント記述のであらわされる値のことかも。

8.1 Annotation of Context Dependencies

Beanはリソースや環境コンテキスト内に登録されている他のオブジェクトへの依存をDependency Annotationを使って宣言します。
Dependency AnnotationはBeanが依存するオブジェクトやリソースの型、特徴、名前を指定するそうです。

いままで意識したことなかったですが「Dependency Annotation」という用語を使うのですね。ふーん。

例が載っています。

@EJB(name="mySessionBean", beanInterface="MySessionIF.class")
@Resource(name="myDB", type="javax.sql.DataSource.class")

アノテーションには@EJBと@Resourceって2種類あるんですね。別に一種類だけ@Injectアノテーションがあれば事足りるように思ったんですがだめなんでしょうか。ちなみに@InjectはEarly DraftまではEJB 3.0の仕様にありましたが、Public Draftではなくなっています。

Dependency AnnotationはBeanクラスかインスタンス変数かメソッドに対して使えるそうです。アノテーションをBeanクラスに指定できるというのはちょっと意外でした。

Dependency Annotationで指定する必要がある情報の量は使用しているコンテキストとコンテキストからどれくらい情報を推測できるかにかかっているようです。???

8.1.1 Annotation of Instance Variables

インスタンス変数をアノテートしてリソースや他のオブジェクトへの依存を示すことができます。コンテナがアノテートされたインスタンス変数を自動的に初期化します。この初期化はBeanのEJBContextがセットされた後かつビジネスメソッドが呼び出される前に行われるようです。

例が載っています。必要そうなとこだけ抜粋します。

@Resource(name = "myDB") // type is inferred from variable
public DataSource customerDB;

@EJB // reference name and type is inferred from variable
public AddressHome addressHome;

ルールが2つ載っています。

  • リソースの型が変数の型で決められる場合、オブジェクトの型はアノテーションに含まれる必要がない。
  • Bean環境内のリソースの参照に対する名前が変数名と同じである場合、名前はアノテーションで示される必要がない。

8.1.2 Setter Injection

セッターインジェクションはインスタンス変数インジェクションのalternativeだそうです。
EJB 3.0 ではインスタンス変数インジェクションがデフォルト扱い?
セッターインジェクションを使う場合はセッターメソッドにアノテートします、とあります。

例をコピペします。なぜか@EJBを使った例がないです。

@Resource(name=”customerDB”)
public void setDataSource(DataSource myDB) {
  this.ds = myDB;
}

@Resource // reference name is inferred from the property name
public void setCustomerDB(DataSource myDB) {
  this.customerDB = myDB;
}

@Resource
public void setSessionContext(SessionContext ctx) {
  this.ctx = ctx;
}

ルールが2つ載っています。

  • リソースの型がパラメータの型で決められる場合、オブジェクトの型はアノテーションに含まれる必要がない。
  • リソースの名前がセッターメソッドに対応するプロパティの名前と同じ場合、名前は明示的にアノテーションで示される必要がない。

型はパラメータを見て、名前はプロパティを見るということらしいですが、型も名前もプロパティを見るというように統一したほうがすっきりするような気がするんですが駄目なんでしょうか?ちょっと疑問です。

Dependency AnnotationをつけられたセッターメソッドはBeanのEJBContextがセットされた後かつビジネスメソッドが呼び出される前に行われる、とあります。

8.1.3 Injectioin and Lookup

リソース、コンポーネントへの参照、JNDIからlookupできる他のオブジェクトのインジェクトは上述したインジェクションメカニズムにより行われるそうです。
インジェクトされるオブジェクトへの参照のlookupは名前(インスタンス変数インジェクションではインスタンス変数の名前、セッターインジェクションではプロパティの名前)と型(インスタンス変数インジェクションではインスタンス変数の型、セッターインジェクションではパラメータの型)によって行われるということです。
これらのlookupはBeanのjava:/comp/envネームスペース内で行われるそうです。

そういえば、EJB 3.0の場合、DIコンテナにあるコンストラクタインジェクションがないですね。EJBだし仕方ないのか。

8.1.4 EJB Context

javax.ejb.EJBContext インタフェースに次のメソッドが追加されたそうです。

Object lookup(String name)

このメソッドはJNDIからリソースやオブジェクトをlookupするために使われます。


それでは、実際にDIが行われるコードを動かしてみます。やってみたことはこんなこと。

  • Dependency Annotationですべての情報を指定しなくてもコンテナがコンテキストから情報を推測してDIしてくれるみたいなので、このあたりを試してみる。
  • インスタンス変数インジェクションをつかってみる。(セッターインジェクションよりもこっちがdefaultっぽいので)
  • javax.ejb.EJBContextを拡張しているSessionContextのlookupメソッドを使ってみる。

ビジネスインタフェースEcho

public interface Echo {
  String echo(String arg);
}

Echoを実装したステートレスセッションBean:ローカルステートレスセッションBean。@Statelessにnameを指定している。

@Stateless(name="echo")
public class EchoBean implements Echo {
  public String echo(String arg) {
    return arg;
  }
}

ビジネスインタフェースHoge

public interface Hoge {
  void hoge();
}

Hogeを実装したリモートセッションBean:Echoにアクセス

@Stateless
@Remote(Hoge.class)
public class HogeBean implements Hoge {

  @EJB // 型と変数名でinject
  private Echo echo;
  
  @EJB // 型でinject
  private Echo echo2;
  
  @EJB(businessInterface=Echo.class) // 指定された型でinject
  private Object echo3;

  @EJB(beanName="echo") // 指定された名前でinject
  private Object echo4;
  
  @Resource
  private SessionContext ctx;
    
  public void hoge() {
    System.out.println(echo.echo("1"));
    System.out.println(echo2.echo("2"));
    System.out.println(((Echo)echo3).echo("3"));
    System.out.println(((Echo)echo4).echo("4"));
    
    Echo echo5 = (Echo) ctx.lookup(Echo.class.getName());
    System.out.println(echo5.echo("5"));
  }
}

クライアント

public class Client {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    Hoge hoge = (Hoge) ctx.lookup(Hoge.class.getName());
    hoge.hoge();
  }
}

実行結果:デプロイしてクライアントを実行するとこんなカンジに出力されます。

14:51:35,729 INFO  [STDOUT] 1
14:51:35,739 INFO  [STDOUT] 2
14:51:35,739 INFO  [STDOUT] 3
14:51:35,739 INFO  [STDOUT] 4
14:51:35,739 INFO  [STDOUT] 5

出力結果をみてみると、DIやlookupがうまくいっていることがわかります。2番目のパターン(インスタンス変数の型でinject)でもOKだとは思っていなかったです。この場合、自動バインディングされていると言っても大丈夫?でも、「コンテキストから情報を推測」とか言っているけど、どのように情報を取得するのかって明文化されているんでしょうか。そうじゃないとコンテナ依存になってしまうような気がします。


この章ではとにかく、「EJB Core Contracts and Requirements」のchpter 15 を参照しろと何度も言ってましたが、あまりしっかり参照してませんです。DIするにあたってどのようにコンテキストから情報を推測するのか気になるし、ちゃんと読んでみないといけないなぁ。

あと@EJBや@Resourceをクラスに指定できるというので、試してみたのですが、うまくいきませんでした。この章では、クラスに指定できるとは書いてあっても例がないんですよね...。


とりあえず chapter 8 終了。4ページでした。