金曜日 7 07, 2006
金曜日 7 07, 2006
前回のエントリの最後にふれたように、セッション・ビーンをリモートからもローカルからも呼び出せるようにするためには、リモート用とローカル用の2つのビジネス・インタフェースを用意する必要があります。
@Stateless
@Remote(CalcRemote.class) @Local(CalcLocal.class)
public class CalcImpl implements CalcRemote, CalcLocal {...}
ローカル用の一部のメソッドをリモート用には公開したくない場合は別ですが、ほとんどの場合、リモート用とローカル用には同じビジネス・メソッドのセットを定義したいと考えるでしょう。上記のように、同じメソッドのセットをもつ2つのインタフェースを用意しなければならないとすると、以下のようなデメリットが生じます。
ローカルとリモートでインタフェース・クラスを共有できない仕様は、J2EE 1.4(EJB 2.1)のときも仕様上不可避の問題でしたが、J2EE 1.4でのEJB開発ではほとんどの場合XDocletが使用されていたと思います。XDocletはEJB実装からEJBインタフェースを自動生成できるため、EJB実装とEJBインタフェースの同期はそれほど問題にはなりませんでした。EJB 3.0ではビジネス・インタフェースがせっかくPOJI(Plain Old Java Interface)になったのですから、なんとか1つにまとめたいものです。
ローカルとリモートで1つのインタフェースを使用できないのは、EJBクライアント側でルックアップする(あるいは、インジェクションする)EJBのメタ情報を宣言するためのアノーテーション@EJBの仕様上の問題です。@EJBはデプロイメント記述子のリモート用<ejb-ref>とローカル用<ejb-local-ref>の両方にマッピングされるように仕様化されました。@EJBアノーテーションそのものには、ローカルかリモートかを明示するメンバがなく、@EJB.beanInterface要素のインタフェースクラスが@Remoteと@Localのどちらでアノーテーションされているかの情報に頼らざるをえなくなっています(@EJB.beanInterface要素が省略された場合、アノーテーションされている変数の型から決定します)。もし、EJB参照のためのアノーテーションが@EJBRefと@EJBLocalRefの2つが用意されていたか、@EJBにローカル/リモートが明示できるメンバ要素が含まれていれば、おそらく1つのビジネス・インタフェースをローカル/リモートで共通化することが可能だったのではないかと思います。
しかし、現状のEJB 3.0仕様のままでもこの問題を解決する方法が、A Java Programmer's Blog: Remote or Local interface? に紹介されています。これはなかなか良いアイデアです。つまり、リモート用とローカル用にはインタフェースクラスを用意するものの、それらで定義するメソッドは両方の親インタフェースに全て定義してしまえば良いというものです。そのままでは、インタフェース・クラスが3つに増えてしまいますが、インナー・クラス表現を使ってJavaのクラスファイルとしては1つにまとめてしまえるわけです。
package com.example;
public interface Calc {
public int add(int i, int j);
@javax.ejb.Remote
public interface Remote extends Calc {
}
@javax.ejb.Local
public interface Local extends Calc {
}
}
package com.example;
import javax.ejb.Stateless;
@Stateless
public class CalcImpl implements Calc.Local, Calc.Remote {
public int add(int i, int j) {
return i + j;
}
}
クライアント側でローカル/リモートのどちらのEJB参照を要求するかを明示するには、Calcのサブインタフェースを明示すれば良いわけです。@EJBでインジェクションする場合は以下のように記述すればよいことになります。
@EJB(beanInterface=Calc.Remote.class) Calc calc1; // リモート参照 @EJB(beanInterface=Calc.Local.class) Calc calc2; // ローカル参照
ローカル/リモートどちらを選んでも、EJB参照の型はCalcに統一でき、ソースコードも増えません。ローカル呼出しからリモート呼出しに切替える場合であっても、変更はアノーテーションの部分だけで、ソースのロジックを見直す必要はありません。また、ローカルだけに公開したいメソッドを追加したい場合でもCalc.Localインタフェースにそのメソッドを追加すれば対応できます。
[1] 例えば、NetBeans 5.xでは、ソースコードのエディタ・ペインで実装クラスのメソッド名で右クリックし、コンテキストメニューからRefactor > Rename...またはRefactor > Change Method Parameters...を選ぶことで、複数インタフェースでメソッドが重複している場合でも同期的に変更が適用できます。
月曜日 7 03, 2006
今回は、Java EE 5仕様の中でも最も重要なEJB 3.0仕様に基づいたセッション・ビーンの実装方法とその利用方についての紹介です。
EJBは主にエンタープライズ・アプリケーションにおけるビジネスロジックをカプセル化したモジュールとして利用され、同期的なメソッド呼出しが可能なセッション・ビーンとJMSメッセージによる非同期呼出しが可能なメッセージ・ドリブン・ビーン(MDB)に分類されます。J2EE 1.4の頃は、データベース・テーブルをマッピングしたエンティティ・ビーンがEJBの1つの種類として利用されていましたが、これはスタンドアロン環境でも利用可能なJava Persistence API(JPA)に置き換わりました。JPAに基づくエンティティ・クラスはもはやEJBとは呼びません。JPAに関しては別途紹介したいと思います。
セッション・ビーンには、状態を持たないステートレス・セッション・ビーン(SLSB)と複数のメソッド呼出しにまたがって状態を保持するステートフル・セッション・ビーン(SFSB)があります。EJBのステート管理は、サーバのクラッシュにも耐え得るように仕様化されているため、SFSBはSLSBに較べメソッド呼出しのオーバヘッドが大きくなります。従って、ビジネスロジックをセッション・ビーンで表現する際は、なるべくステートレスなSLSBで定義することが望ましいとされています。Java EE 5では、ビジネスロジックをSLSBとして実装するのに、javax.ejb.SessionBeanインタフェースをimplementsして、ejbXXX()なライフサイクルメソッドを実装する必要はなくなりました。Java EE 5チュートリアル:Webサービス編(2)でも触れましたが、SLSBの場合は、@StatelessアノーテーションをPOJOなビジネスロジックのクラスに付与します。
package com.example;
import javax.ejb.Stateless;
@Stateless
public class Calc {
public int add(int i, int j) {
return i + j;
}
}
上記のクラスは、SJS AppServer 9 (glassfish)にそのままデプロイすることができますが、実際にはこのままでは、クライアントから呼び出すことができません。実は、Webサービスモジュールの場合とは異なり、EJBの場合はビジネスメソッドを定義したインタフェース(ビジネス・インタフェース)を定義することが必須となっています(EJB 3.0 Simplified API (JSR-220), 3.2節 Business Interfaces, pp.16参照)。参考に、JSR-220仕様の該当箇所を以下に引用します。
Under the EJB 3.0 API, the business interface of an enterprise bean is a plain Java interface, not an
EJBObject or EJBLocalObject interface. [2]
Session beans and message-driven beans require a business interface. The business interface of a message-
driven bean is typically defined by the messaging type used (e.g., javax.jms.MessageListener
in the case of JMS). Business interfaces in the sense of this chapter are not defined for entity
beans.
ビジネス・インタフェースはEJB 2.xのEJB(リモート/ローカル)インタフェースに相当するものですが、従来のようにjavax.ejb.EJBObject/javax.ejb.EJBLocalObjectインタフェースを実装する必要はなく、ただのJavaインタフェース(POJI: Plain Old Java Interface)で構いません。
Calcクラスを正しく動作するEJBとするため、実装クラス名をCalcImplとし、ビジネス・インタフェースとして、Calcインタフェースを定義します。
package com.example;
import javax.ejb.Stateless;
/** EJB本体のクラス:バリエーション1 */
@Stateless
public class CalcImpl implements Calc {
public int add(int i, int j) {
return i + j;
}
}
package com.example;
/** ビジネスインタフェース */
public interface Calc {
public int add(int i, int j);
}
ここで、もう一つ重要な注意があります。EJB 3.0仕様は、EJB 2.1との互換性を確保するため、非常に注意深く仕様が決定されました。そのため、EJB 2.x仕様で定義されたEJBローカル・インタフェースおよびEJBリモート・インタフェースという概念とそれらに対する互換性が保証されています。上記のサンプルコードにおいて、Calcインタフェースはリモート・インタフェースでしょうか?ローカル・インタフェースでしょうか?
厳密には、ビジネス・インタフェースがリモート呼出し用であれば@Remoteアノーテーションを、ローカル呼出し用であれば@Localアノーテーションを明示することができます。上記の例のようにEJBが実装しているインタフェースが1つしかなく、@Localも@Remoteも省略されている場合は、そのインタフェースはローカル・ビジネス・インタフェースであると解釈されます。従って、上記のサンプルは、以下のように@Localアノーテーションを明示したのと同等です。
package com.example;
import javax.ejb.Stateless;
import javax.ejb.Local;
/** EJB本体のクラス:バリエーション2 */
@Stateless
@Local
public class CalcImpl implements Calc {
public int add(int i, int j) {
return i + j;
}
}
@Localは、Calcインタフェース側に定義することもできます。
package com.example;
import javax.ejb.Stateless;
/** EJB本体のクラス:バリエーション3 */
@Stateless
public class CalcImpl implements Calc {
public int add(int i, int j) {
return i + j;
}
}
package com.example;
import javax.ejb.Local;
/** ビジネスインタフェース */
@Local
public interface Calc {
public int add(int i, int j);
}
今までの例は、CalcImplクラスがCalcインタフェースを実装している例でしたが、EJBの実装クラスがそのインタフェースを必ずしもimplementsしている必要はありません(EJB 3.0 Simplified API (JSR-220), 3.2節 Business Interfacesの注釈[3], pp.16参照)。
[3] While it is expected that the bean class will typically implement its business interface(s), if the bean class uses annotations on the bean class or the deployment descriptor to designate its business interface(s), it is not required that the bean class also be specified as implementing the interface(s). See the document EJB Core Contracts and Requirements [1].
ただし、その場合は、そのセッション・ビーンがどのインタフェースと関連付けられているかを@Remoteまたは@Localアノーテーションの要素に明示的に示してあげる必要があります。
package com.example;
import javax.ejb.Stateless;
import javax.ejb.Local;
/** EJB本体のクラス:バリエーション4 */
@Stateless
@Local(Calc.class)
public class CalcImpl {
public int add(int i, int j) {
return i + j;
}
}
同じ構成のEJBについて、アノーテーションの付け方にいろいろなバリエーションがありますが、以下の点が明確になっているかどうかを確認できれば迷うことはありません。
セッション・ビーンがビジネス・インタフェースをimplementsしていれば、1.の関係はコンテナから観測できるため、@Localにインタフェース・クラスを明示する必要がないわけです。実際のアプリケーション開発のプラクティスとしては、明示的にビジネス・インタフェースのimplementsを明示し、さらに@Localまたは@Remoteにそのインタフェース・クラスも明示してあげるのがよいでしょう。
package com.example;
import javax.ejb.Stateless;
import javax.ejb.Local;
/** EJB本体のクラス:バリエーション5(推奨) */
@Stateless
@Local(Calc.class)
public class CalcImpl implements Calc {
public int add(int i, int j) {
return i + j;
}
}
EJB本体とインタフェースはそのメソッド仕様が一致していなければなりませんが、implementsを使用していれば、コンパイラがこれをチェックしてくれます。これは、Webサービスの場合にも言えることです。Webサービス本体もエンドポイント・インタフェースをimplementsする必要はありませんが、同様の理由でインタフェースを切り出し、明示するのが好ましいです。
一般に同一のコンテナ(Webコンテナからの呼出しを含む)内でのEJB呼出しは、ローカル呼出しを使用するのが鉄則です。リモート呼出しは、引数と戻り値のシリアライゼーション処理を含むため、メソッド呼出しのオーバーヘッドがそれだけ大きくなります。しかし、物理的に離れたホストからリモートにメソッドを呼び出す必要があれば、当然のことながら@Remoteアノーテーションを付与します。
package com.example;
import javax.ejb.Stateless;
import javax.ejb.Remote;
@Stateless
@Remote(Calc.class)
public class CalcImpl implements Calc {
public int add(int i, int j) {
return i + j;
}
}
ここで1つ注意です。EJB 3.0の仕様では、1つのビジネス・インタフェースをリモート用とローカル用の両方で共用することができません。必ず別のインタフェース・クラスとして用意する必要があります。例えば、以下のように定義することはできません。
@Stateless
@Remote(Calc.class) @Local(Calc.class) // 間違い
public class CalcImpl implements Calc {...}
EJB 3.0ではビジネス・インタフェースがPOJIになったため、できればリモート/ローカルで共通のインタフェースを使用できればよかったのですが、EJB 2.xとの互換性を重視した結果でしょうか、実現されませんでした。この問題の解決方法については、別のエントリで紹介したいと思います。