こんにちは、本来ならば前回「EJB vs. POJO ~準備編~」の続きをやるところですが、Oracleのパーシステンステクノロジの中で大きなトピックありましたの予定を変更してお送りします。
その大きなトピックとは、「Oracle Application Server EJB 3.0 Preview」の登場です。JCP(Java Comminity Process)で策定が進められているEJB 3.0仕様はEarly Draft Review2(以下EDR2)が2005/2/7に発表されましたが、そのEDR2にほぼ準拠した、EJBコンテナであるOracle Application Server EJB 3.0 Previewが2005/3/23に発表されました。
そこで今回から数回にわたり現状のEJB3.0 EDR2の基本を取り上げていきたいと思います。
EJB 3.0には上記のようなゴールが設定されています。今回はこれら事項からEJB3.0一般的な事項とSession Bean/Message Driven-Beanに関わることについて触れていきたいと思います。
では、まず初めに単純なStateless Session BeanのサンプルをもとにEJB 2.1からEJB 3.0に変わるとどのように変化するかを確認したいと思います。サンプルはクライアントからHelloWorldBeanへはリモートアクセスで、HelloWorldBeanからMessageBeanへはローカルアクセスで呼び出すサンプルです。
![]() |
EJB 2.1 Stateless Session Beanの例 |
EJB 2.1ではHomeインタフェース/コンポーネント・インタフェース/Beanクラス/デプロイメント・ディスクリプタといったファイルをEJB 2.1仕様に従い、実装する必要がありました。では、簡単にHelloWorldBeanとMessageBeanとクライアントの実装例を見ていきましょう。
Home インタフェース(HelloWorldBeanの場合)
HelloWorldBeanは、クライアントからのリモートアクセスを想定しているので、javax.ejb.EJBHomeインタフェースを継承してHomeインタフェースを作成します。
【コード】HelloWorldHome.java
// HelloWorldHome.java
package oracle.ejb21;
import java.rmi.*;
import javax.ejb.*;
public interface HelloWorldHome extends EJBHome {
HelloWorld create() throws RemoteException, CreateException;
}
|
上記の様に、EJB 2.1以前では、クライアントからインスタンスを生成することのできるcreate()メソッドを実装する必要がありました。実際にインスタンスを生成するのはコンテナの役割なので、本当にコンテナ上で新規のインスタンスが生成されるか、コンテナにプールされているインスタンスが渡されるかは、クライアント側では意識する必要はありません。また、Stateless Session Beanの場合、引数を持たないcreate()メソッドを作成する必要がある等、細かいEJBの規則を覚えておく必要がありました。
コンポーネント・インタフェース(HelloWorldBeanの場合)
HelloWorldBeanはリモートアクセスを想定しているのでjavax.ejb.EJBObjectを継承してリモートインタフェースとして実装します。
【コード】HelloWorld.java
// HelloWorld.java
package oracle.ejb21;
import java.rmi.*;
import javax.ejb.*;
public interface HelloWorld extends EJBObject {
String sayHello(String name) throws RemoteException, CreateException;
}
|
コンポーネント・インタフェースでは、クライアントに公開するメソッドの定義を行う必要がありました。このHelloWorld.javaの例では、sayHello()メソッドという文字列を引数にとり、新たな文字列を返す単純なメソッドを定義しています。また、リモートインタフェースを利用する場合、RemoteExceptionを投げる様に実装する必要がありました。
Beanクラス(HelloWorldBeanの場合)
HelloWorldBeanのStateless Session Beanなのでjavax.ejb.SessionBeanを実装してBeanクラスを作成します。
【コード】HelloWorldBean.java
// HelloWorldBean.java
package oracle.ejb21;
import javax.ejb.*;
import javax.naming.*;
public class HelloWorldBean implements SessionBean {
private SessionContext _context;
private MessageLocalHome messageLocalHome;
public void ejbCreate() throws CreateException {
try {
InitialContext context = new InitialContext();
messageLocalHome = (MessageLocalHome) context.lookup(
"java:comp/env/ejb/Message");
} catch (NamingException ne) {
throw new CreateException(ne.getMessage());
}
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
public void setSessionContext(SessionContext ctx) {
_context = ctx;
}
public String sayHello(String name) throws CreateException {
MessageLocal messageLocal = messageLocalHome.create();
String message = messageLocal.getMessage();
return message + name;
}
}
|
HelloWorldBean内には継承をしていないので実装の必要がないようにみえるコンポーネント・インタフェースのsayHello()メソッドの実装を記述する必要があり、Homeインタフェースで定義したcreate()メソッドに対応したejbCreate()やその他SessionBeanインタフェースで定義されたコールバックメソッドであるejbXXX()メソッドを実装する必要がありました。コールバックメソッドであるejbXXX()メソッドは、EJBのライフサイクルを理解し、ライフサイクルがコンテナにより変更されたときに、実行したいロジックを実行する必要がありました。
この様にEJB 2.1までは、Homeインタフェースやコンポーネント・インタフェースの実装クラスを作成せず、それとは別にBeanクラスを作成し、かつそれぞれが関連しあうという判りにくい構成でコードを記述する必要がありました。
またsayHello()メソッド内では、ローカルインタフェースを用い、MessageBeanにアクセスをしています。そのHelloWorldBeanから呼び出されているMessageBeanの例も見ておきます。
Homeインタフェース(MessageBeanの場合)
MessageBeanはローカルアクセスを想定しているので、javax.ejb.EJBLocalHomeインタフェースを継承して実装します。
【コード】MessageLocalHome.java
// MessageLocalHome.java
package oracle.ejb21;
import javax.ejb.*;
public interface MessageLocalHome extends EJBLocalHome {
MessageLocal create() throws CreateException;
}
|
MessageBeanもHelloWorldBeanと同様のStateless Session Beanですので特に変わりはありません。
コンポーネント・インタフェース(MessageBeanの場合)
MessageBeanは、ローカルアクセスを想定しているので、javax.ejb.EJBLocalObjectインタフェースを継承して実装します。
【コード】MessageLocal.java
// MessageLocal.java
package oracle.ejb21;
import javax.ejb.*;
public interface MessageLocal extends EJBLocalObject {
String getMessage();
}
|
クライアントに公開する、ビジネスメソッドを定義します。MessageBeanはローカルインタフェースによるアクセスを想定しているので、ビジネスメソッドであるgetMessage()メソッドでRemoteExceptionは、投げられません。
Beanクラス(MessageBeanの場合)
MessageBeanクラスをStateless Session Beanなのでjavax.ejb.SessionBeanを継承して実装します。
【コード】MessageBean.java
// MessageBean.java
package oracle.ejb21;
import javax.ejb.*;
public class MessageBean implements SessionBean {
private SessionContext _context;
public void ejbCreate() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
public void setSessionContext(SessionContext ctx) {
_context = ctx;
}
public String getMessage() {
return "Hello ";
}
}
|
MessageBean内には単純な文字列を返すgetMessage()メソッドを用意しています。また、当然、javax.ejb.SessionBeanインタフェースで定義されている、コンテナがEJBインスタンスの状態を変更する時に呼び出されるejbXXX()メソッドを特に実行するロジックがなくとも実装する必要があります。
デプロイメント・ディスクリプタ
J2EE汎用のデプロイメント・ディスクリプタを作成します。EJB 2.1までは環境に依存する設定情報は、デプロイメント・ディスクリプタというXMLファイルに記述し、分離することによりコンポーネントとしての特性(ロジックと環境に依存した設定情報の分離)を実現していました。
【コード】ejb-jar.xml
<?xml version = '1.0' encoding = 'windows-31j'?>
<ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"
version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee">
<enterprise-beans>
<session>
<description>Session Bean (Stateless)</description>
<display-name>HelloWorld</display-name>
<ejb-name>HelloWorld</ejb-name>
<home>oracle.ejb21.HelloWorldHome</home>
<remote>oracle.ejb21.HelloWorld</remote>
<ejb-class>oracle.ejb21.HelloWorldBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<ejb-local-ref>
<ejb-ref-name>ejb/Message</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home>oracle.ejb21.MessageLocalHome</local-home>
<local>oracle.ejb21.MessageLocal</local>
<ejb-link>Message</ejb-link>
</ejb-local-ref>
</session>
<session>
<description>Session Bean (Stateless)</description>
<display-name>Message</display-name>
<ejb-name>Message</ejb-name>
<local-home>oracle.ejb21.MessageLocalHome</local-home>
<local>oracle.ejb21.MessageLocal</local>
<ejb-class>oracle.ejb21.MessageBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>HelloWorld</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>Message</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
|
EJBの論理的な名前/Homeインタフェース/コンポーネント・インタフェース/Beanクラス/トランザクション属性等のEJB仕様上で定めされた設定を行います。今回のサンプルで注目すべき点はHelloWorldBeanからMessageBeanはローカルインタフェースでアクセスするのでHelloWorldBeanの定義内でejb-local-ref要素を利用し定義している点です。
また、このejb-jar.xmlの他に、アプリケーションサーバ固有のデプロイメント・ディスクリプタを作成します。EJB仕様上網羅することのできていな各アプリケーションサーバ特有の設定情報を記述するデプロイメント・ディスクリプタです。OC4J 10g(10.1.3) Developers Preview 3を利用する場合、orion-ejb-jar.xmlというファイル名で作成します。
【コード】orion-ejb-jar.xml
<?xml version = '1.0' encoding = 'windows-31j'?>
<orion-ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation=
"http://xmlns.oracle.com/oracleas/schema/orion-ejb-jar-10_0.xsd"
schema-major-version="10" schema-minor-version="0">
<enterprise-beans>
<session-deployment name="HelloWorld" location="ejb/HelloWorld"/>
<session-deployment name="Message" location="ejb/Message"/>
</enterprise-beans>
</orion-ejb-jar
|
ここでは、アプリケーションサーバに依存する設定である、JNDI名等をしています。
EJBクライアント
HelloWorldBeanにアクセスするクライアントです。
【コード】Client.java
…
Context context = new InitialContext();
HelloWorldHome helloWorldHome = (HelloWorldHome)
PortableRemoteObject.narrow(context.lookup(
"HelloWorld"), HelloWorldHome.class);
HelloWorld helloWorld = helloWorldHome.create();
System.out.println(helloWorld.sayHello("Hiroyuki Sato"));
…
|
イニシャル・コンテキストを取得し、Homeオブジェクトを取得し、リモートオブジェクトを生成し、アクセスしています。EJBのコンポーネントを取得するためのファクトリとなるHomeオブジェクトを取得し、そこからリモートオブジェクトを取得するという手順です。
ここまででEJB 2.1のサンプルは終わりで、結構ものすごく簡単なロジックなのに結構な記述量になったかと思われます。
![]() |
EJB 3.0 Stateless Session Beanの例 |
では、EJB 2.1で作成したHelloWorldBeanとMessageBeanをEJB 3.0で記述するとどのようになるのかを見ていきましょう。
ビジネス・インタフェース(HelloWorldBeanの場合)
HellowWorldBeanのビジネス・インタフェースです。EJB 3.0の仕様内ではコンポーネント・インタフェースとは言わずビジネス・インタフェースと呼んでいます。
【コード】HelloWorld.java
// HelloWorld.java
package oracle.ejb30;
import javax.ejb.Remote;
@Remote
public interface HelloWorld {
public String sayHello(String name);
}
|
ビジネス・インタフェースには、EJB 2.1のコンポーネント・インタフェースと同様にクライアントに公開するビジネスロジックの定義を行います。EJB 2.1の様にjavax.ejb.EJBObjectの様なEJB仕様固有のインタフェースを実装する必要はなく、単純なPOJI(Plain Old Java Interface)として実装します。また、@Remoteという記述がありますが、これがアノテーションと呼ばれるものです。このRemoteアノテーションにより、リモートインタフェースであることを宣言しています。そして、このインタフェースにはクライアントに公開するメソッドの定義を行います。ここでは、単純なsayHello()メソッドを定義しています。
Beanクラス(HelloWorldBeanの場合)
HelloWorldBeanのBeanクラスです。
【コード】HelloWorldBean.java
// HelloWorldBean.java
package oracle.ejb30;
import javax.ejb.Stateless;
import javax.ejb.Inject;
@Stateless
public class HelloWorldBean implements HelloWorld {
@Inject
Message message;
public String sayHello(String name) {
return message.getMessage() + message;
}
}
|
javax.ejb.SessionBeanインタフェースEJB 2.1の様にを実装する必要はなく、単純なPOJO(Plain Old Java Object)として実装します。その代わりに先ほど作成したビジネス・インタフェース(HelloWorld)を実装し、sayHello()メソッドの実装を記述します。StatelessアノテーションによりStateless Session Beanであるのことの宣言をしています。Injectアノテーションにより、HelloWorldBeanから呼び出しているMessageBeanのDependency Injectionを行っています。Dependency Injectionにより依存が注入されたフィールドmessageはコード上では特に初期化等することなく利用する事ができます。
このようにEJB 3.0のStateless Sesison Beanの場合、ユーザに公開するメソッドを定義したビジネス・インタフェースを実装したBeanクラスを作成するという素直な実装方法になります。
このHelloWorldBeanから呼び出されているMessageBeanについても見ておきます。
ビジネス・インタフェース(MessageBeanの場合)
MessageBeanのビジネス・インタフェースです。
【コード】Message.java
// Message.java
package oracle.ejb30;
import javax.ejb.Local;
@Local
public interface Message {
public String getMessage();
}
|
こちらもHelloWorldBeanと同様にjavax.ejb.EJBLocalObjectの様なEJB仕様固有のインタフェースを実装する必要はなく、単純なPOJI(Plain Old Java Interface)として実装します。また、@Localという記述がありますが、これはLocalアノテーションで、ローカルインタフェースとしての定義です。
Beanクラス(MessageBeanの場合)
MessageBeanのBeanクラスです。
【コード】MessageBean.java
// MessageBean.java
package oracle.ejb30;
import javax.ejb.Stateless;
@Stateless
public class MessageBean implements Message {
public String getMessage() {
return "Hello ";
}
}
|
こちらもHelloWorldBeanと同様にjavax.ejb.SessionBeanインタフェースを実装する必要はなく、単純なPOJO(Plain Old Java Object)として実装します。そして先ほど作成したビジネス・インタフェース(Message)を実装しています。またStatelessアノテーションによりStateless Session Beanであるのことの宣言をしています。
このようにEJB 3.0では、ビジネス・インタフェースを作成し、その実装クラスであるBeanクラスを作成するという素直な実装方法になります。また、EJB固有のインタフェースを利用しなくてすみ、EJB固有のException等のハンドリングもありませんので、開発者にとって視認性の高いコードになります。
ここまででEJB 3.0仕様でのHelloWorldBean/MessageBeanはおわりです。デプロイメント・ディスクリプタは、アノテーションを利用する事により開発時に作成する必要はありません。EJB 3.0のEDR2時点では、デプロイメント・ディスクリプタを作成する事もできよう様に記述されており作成されていた場合、アノテーションの設定を上書きする様な事を想定している様です。
EJB クライアント
HelloWorldBeanのクライアントです。
【コード】Client.java
Context context = new InitialContext();
HelloWorld helloWorld =
(HelloWorld)context.lookup("java:comp/env/ejb/HelloWorld");
System.out.println(helloWorld.sayHello(args[0])
|
EJB 3.0では、イニシャルコンテキスを生成しルックアップするといきなりリモートオブジェクトを取得することができます。Homeオブジェクトを扱う必要がありません。
ここまで見てきたようにEJB 3.0ではアノテーションを利用する事により開発者が作成するコードが非常に簡略化されます。またこのサンプルではHelloWorldBeanからMessageBeanの呼び出しにDependency Injectionも利用されています。このアノテーションとDependency InjectionはEJB 3.0を利用する上では重要な概念ですので少しまとめておきましょう。
アノテーションとは、Javaプログラムに対し、動作上影響のあるメタデータを記述する事のできる機能です。前述のEJB 3.0のコードの@で始まるものがアノテーションになります。
EJB 3.0ではアノテーションを利用する事により、EJB 2.1まで必要であったが、開発者の負担となっていたEJB固有のインタフェース(javax.ejb.EJBObject/javax.ejb.SessionBean等)やデプロイメント・ディスクリプタ等の開発の負荷を排除することができます。
アノテーション自身は単なるメタデータであり、それを読み取り新たなコードを生成したり、ロジックを追加したりするのは、フレームワークやツール側で行うことになります。つまりEJB 3.0はアノテーションを解釈しEJB固有の振る舞い(ロジック)を与えるフレームワークといえるかと思います。
先ほどの@Statelessなどのアノテーションは、EJB 3.0仕様上定義されたものであり、開発者はそれを利用するだけです。アノテーション自身を開発者の方が定義することは少ないと思われますが、EJB 3.0を理解する上である程度は知っておいたほうが良いと思われるので、今回は簡単なアノテーションを作成してみたいと思います。
![]() |
簡単なアノテーションの作成 |
では、アノテーションとはどのようなものなのかを簡単なアノテーションのサンプルを作成しながらご紹介します。
まずは、アノテーション・タイプの定義を行います。アノテーション・タイプとはアノテーションを定義した型です。アノテーション・タイプとアノテーションの関係は、クラスとそこから生成されるオブジェクトの関係に似ていいます。ここでは独自のMyTaskアノテーションを作成して利用してみます。まずはMyTaskアノテーションのアノテーション・タイプの定義してみます。
【コード】MyTask.java
// MyTask.java
package sample;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTask {
TaskName name() default TaskName.COOK;
}
|
アノテーション・タイプの定義は、通常のJavaコードとして行われますが、@interfaceという特殊なインタフェースとして実装します。また、アノテーションに付加する事のできる属性をメンバと呼びます。例えば上記のアノテーション・タイプを利用する場合@MyTask(name=”TaskName.BOIL”)と指定する事ができます。メンバは、メソッドとして定義しデフォルト値を設定することができます。ここでは、メンバに対応したname()メソッドを用意し、列挙型のTaskNameの戻り値とし、デフォルト値はTaskName.COOKをデフォルト値としています。このデフォルト値を設定することによりEJB 3.0仕様のゴールで説明した“Configuration by Exceptionアプローチ”を実現しています。また、この戻り値は、アノテーションのメンバとして指定することのできる値を表し、今回の場合は列挙型のTaskNameを以下のように定義してみます。
【コード】TaskName.java
// TaskName.java
package sample;
public enum TaskName {
COOK,
BAKE,
BOIL
}
|
J2SE5.0から導入された列挙型としてCOOK、BAKE、BOILの3つの定数を定義しています。つまりMyTaskアノテーションのメンバとしてCOOK/BAKE/BOILの3種類を指定する事ができます。
また、このMyTaskアノテーション・タイプの定義であるMyTask.javaの中にRetentionアノテーションがありました、これはアノテーションをアノテートするためのアノテーションです?。つまりアノテーション定義のためにアノテーションを利用している(できる)と言うことです。
Retentionアノテーションは、アノテーションがどこで解釈されるのかをメンバとして指定します。メンバとして指定できる値は、列挙型のRetencionPolicyに3種類の定数が定義されています。
定数 |
概要 |
SOURCE |
アノテーションは、コンパイル時に無視をされる。つまり、ソースだけで有効。 |
CLASS |
アノテーションは、コンパイル時に解釈されクラスファイルに埋め込まれるが、JVMにクラスがロードされてから解釈はできない。(デフォルト) |
RUNTIME |
アノテーションは、コンパイル時に解釈されクラスファイルに埋め込まれ、JVMにクラスがロードされてから解釈できる。 |
では、EJB 3.0仕様で定義されているアノテーション・タイプのRetentionPolicyは上記のうちどれかというと、EDR2時点では、全てRUNTIMEとして定義されています。つまり、クラスをJVM上でロードして解釈可能であるアノテーション・タイプとして定義されています。
これでアノテーション・タイプの定義は終わりです。
では、このアノテーションを利用するサンプルを見ていきます。アノテーションは、パッケージ・クラス・メソッド・フィールド等に指定することができます。以下のサンプルはメソッドに対してMyTaskアノテーションを適用している例です。
【コード】Client.java
// Client.java
package sample;
public class Client {
public Client() {
}
@MyTask
public void foo() {
System.out.println("foo() called");
}
@MyTask(name = TaskName.BAKE)
public void bar() {
System.out.println("bar() called");
}
@MyTask(name = TaskName.BOIL)
public void hoge() {
System.out.println("hoge() called");
}
}
|
MyTaskアノテーションはRetentionPolicyとしてRUNTIMEを指定していましたので、このソースをコンパイルするとクラスファイル内にMyTaskアノテーションの情報が格納され、JVMにロードしてからアノテーションを参照することができます。
では、クラスをロードして格納されたアノテーションを表示するサンプルを作ってみましょう。
【コード】ReflectionAnnotation.java
// ReflectionAnnotation
package sample;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
public class ReflectMethodAnnotation {
public static void main(String args[]) {
try {
Class cls = Class.forName("sample.Client");
AnnotatedElement[] annotatedElements = cls.getDeclaredMethods();
for (AnnotatedElement annotatedElement : annotatedElements) {
System.out.print(annotatedElement +" は ");
Annotation[] annotations = annotatedElement.getAnnotations();
if (annotations.length != 0) {
System.out.print("アノテートされており、\n\tアノテーションは\n");
for (Annotation annotation : annotations) {
System.out.println("\t" + annotation);
}
System.out.println("\tです。");
} else {
System.out.println("アノテートされていません。\n");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
このサンプルでは、独自に定義してMyTaskアノテーションを利用しているClient.javaをコンパイルしたクラスファイルからアノテーションを取得するサンプルです。実行結果は以下の様になります。
public void sample.Client.bar() は アノテートされており、
アノテーションは
@sample.MyTask(name=BAKE)
です。
public void sample.Client.foo() は アノテートされており、
アノテーションは
@sample.MyTask()
です。
public void sample.Client.hoge() は アノテートされており、
アノテーションは
@sample.MyTask(name=BOIL)
です。
public void sample.Client.hogeFooBar() は アノテートされていません。
|
ちなみに、MyTask.javaのRetentionPolicyをデフォルトのCLASSにした場合、実行結果は以下のようになり、クラスをJVM上にロードしてからでは、解釈ができない事がわかります。
public void sample.Client.bar() は アノテートされていません。
public void sample.Client.foo() は アノテートされていません。
public void sample.Client.hoge() は アノテートされていません。
public void sample.Client.hogeFooBar() は アノテートされていません。
|
EJB 3.0の重要な概念としてDependency Injectionがあります。Dependency Injection(依存性注入)は、Inversion of Control(制御の逆転)とも言われます。コンポーネントで実行環境であるコンテナに登録されたコンポーネントを呼び出すのではなく、コンテナがコンポーネント内にコンテナに登録された他のコンポーネントの依存性を注人する為に、Dependency InjectionもしくはInversion of Controlと呼ばれています。
例えばAコンポーネントがBコンポーネントを利用している場合、AがBへの依存性を解決し、インスタンス変数に保持するのではなく、外部から依存性を注入する事により解決しようというものです。EJB 3.0でいえば典型的なものとして他のBeanクラスの呼び出しがあります。
前述の簡単なサンプルでもDependency Injectionが利用されていましたので、そのサンプルを見てみましょう。
Interceptorは、ビジネスメソッドの呼び出しをインターセプトし、追加のロジックを埋め込むことのできる機能です。Interceptorは、AroundInvodeアノテーションにより記述でき、Interceptor/Interceptorsアノテーションを利用して別クラスに記述することも可能です。
それではサンプルを見ていきます。
【コード】HelloWorldBean.java
// ProfilingIntercepter.java
package oracle.ejb30;
import javax.ejb.Stateless;
import javax.ejb.Interceptor;
import javax.ejb.Interceptors;
import javax.ejb.AroundInvoke;
import javax.ejb.InvocationContext;
@Stateless
@Interceptor(value="oracle.ejb30.ProfilingInterceptor")
//@Interceptors({@Interceptor(value="oracle.ejb30.ProfilingInterceptor"),
@Interceptor(value="oracle.ejb30.ProfilingInterceptor2")})
public class HelloWorldBean implements HelloWorld {
public String sayHello(String name) {
System.out.println("sayHello() called");
return "Hello " + name;
}
@AroundInvoke
public Object checkPermission(InvocationContext ctx) throws Exception {
System.out.println("checkPermission() called");
if (!ctx.getEJBContext().getCallerPrincipal().getName().equals("jazn.com/admin")) {
throw new SecurityException("Caller: '"
+ ctx.getEJBContext().getCallerPrincipal().getName() +
"' does not have permissions for method " + ctx.getMethod());
}
return ctx.proceed();
}
}
|
この例では、AroundInvokeアノテーションを利用して、ビジネス・メソッドであるsayHello()がクライアントにより呼び出されたときに、インターセプトしcheckPermission()メソッドを呼び出します。chackPermission()メソッドは、メソッド呼び出しのユーザを識別し、権限がないユーザで呼び出されたときにはSecurityExceptionを投げるという簡易的なセキュリティチェックの機能を提供しています。またcheckPermittion()メソッドの引数はInvocationContextが渡されます。このInvocationContextはメソッドの呼び出しの情報をカプセル化したオブジェクトであり、メソッド名やパラメータを取得することができます。上記の例では、InvocationContextからEJBContextを取得して、メソッドを呼び出したプリンシパルを取得してセキュリティチェックを行い、proceed()メソッドによりsayHello()メソッドの処理を継続させています。
また、Interceptorアノテーションを利用して、外部クラスにインターセプト時に実行するロジックを記述する事もできます。コメントアウトされているInterceptorsアノテーションを利用すれば複数の外部クラスを指定することもできます。
以下は、Intersepterアノテーションにより、指定されたインターセプタクラスです。
【コード】ProfilingInerceptor.java
// ProfilingInterceptor.java
package oracle.ejb30;
import javax.ejb.AroundInvoke;
import javax.ejb.InvocationContext;
public class ProfilingInterceptor {
@AroundInvoke
public Object profile(InvocationContext ctx) throws Exception {
System.out.println("profile() called");
long startTime = 0;
long endTime = 0;
try {
startTime = System.currentTimeMillis();
return ctx.proceed();
} finally {
endTime = System.currentTimeMillis();
System.out.println("Method Name: " + ctx.getMethod() +
" Execution Time : " + (endTime - startTime) + " ms");
}
}
}
|
sayHello()メソッドが呼び出されたときの実行結果は以下のようになります。
05/04/04 11:17:55 profile() called
05/04/04 11:17:55 checkPermission() called
05/04/04 11:17:55 sayHello() called
05/04/04 11:17:55 Method Name: public java.lang.String
oracle.ejb30.HelloWorldBean.sayHello(java.lang.String) Execution Time : 0 ms |
この様にインターセプタを利用するとメソッドの前後にAOP(Aspect Oriented Programing)的?にロジックを追加することが可能になります。
今回は、EJB3.0のStatelss/Stateful/Message Driven Beanを中心に見てみました。次回はEJB 3.0の大きな目玉である、CMP(Container Managed Persistence)のEntity Beanを中心に見ていきます。