EJB 3.0(Public Draft)入門記 Simplified API Chapter 5
Chapter 5 のStateless Session Beanにすすみます。
ステートレスな設計に注目が集まっている?昨今ですが、ステートフルセッションBeanは日の目をみるんでしょーか。
5.1 Requirements for Stateful Session Beans
5.1.1 Business Interfaces
セッションビーンのビジネスインターフェースは通常のJavaインタフェース。EJBObjectでもEJBLocalObjectでもない。5.1.2 Home Interfaces
ホームインタフェースは不要。5.1.3 Bean Classes
ステートフルセッションBeanはStatefulアノテーションでアノテートされるかデプロイメント記述でステートフルセッションBeanであると示されなければいけない。Beanクラスはjavax.ejb.SessionBeanやjava.io.Serializableを実装する必要はない。コンテナはSerializableが実装されていなくてもBeanインスタンスのパッシベーションを扱うことができなければいけないそうです。でも、僕の環境だとSerializableを実装していないステートフルBeanはパッシベート時にException投げるんですけど...。
ステートフルセッションBeanはjavax.ejb.SessionSynchronizationというインタフェースを実装できるそうです。「EJB Core Contracts and Requrements」を参照しろとあって詳しく書かれていませんがちょっと見てみたいと思います。定義はこんな感じ。
public interface SessionSynchronization { public void afterBegin() throws EJBException, RemoteException; public void beforeCompletion() throws EJBException, RemoteException; public void afterCompletion(boolean flag) throws EJBException, RemoteException; }
トランザクションと同期をとるためのメソッドが用意されています。bean-managedなトランザクション境界をもつセッションBeanには必要ないらしいです。
5.1.4 Callbacks for Stateful Session Bean
ステートレスセッションBeanがサポートするコールバックは次のとおりです。- PostConstract
- PreDestroy
- PostActive
- PrePassivate
5.1.4.1 Semantics of the Life Cycle Callback Methods for Stateful Session Bean
PostConstractメソッドは新しく生成されたインスタンスに対して呼び出されます。これはDependency Injectionが行われ、ビジネスメソッドが呼ばれる前に行われます。JBossの場合はClientがlookupしてインスタンスの参照を取得した時点ではまだPostConstractは呼び出されなくてビジネスメソッドを実行したときにビジネスメソッドよりも前に呼び出されるみたい。
PreDestroyメソッドはRemoveアノテーションをもつメソッドの実行が完了してから呼び出されます。
PostConstractメソッドもPreDestroyメソッドも特定のトランザクションコンテキストやセキュリティコンテキストでは実行されない。
PostActiveとPrePassivateの意味はEJB 2.1のejbActiveとejbPassiveコールバックメソッドと同じだそうです。
5.1.5 Dependency Injection
Dependency Inejectionについてはchapter 8で述べるそうです。Dependency Inejectionはビジネスメソッドの実行やコールバックメソッドの実行より前に行われると書いてあります。5.1.6 Interceptors for Stateful Session Bean
AroundInvokeメソッドがサポートされるとあります。SessionSynchronizationのメソッドを考慮した場合の呼び出しの順番は- afterBegin
- AroundInvoke
- beforeCompletion
となります。
5.1.7 Example
例が載っているんですが、わかりにくいんで特にコピペしません。5.1.8 Client View
JNDIからのlookupやDependency Injectionが行われるときにステートフルセッションBeanのインスタンスが新しく生成されるけれども、クライアントが明示的に"create"メソッドを呼ぶわけではないので、クライアントから見ればステートフルセッションBeanのインスタンスは初期化されていない。クライアントは一般的にビジネスインタフェースのメソッドを通してステートフルセッションBeanを初期化するんだそうです。5.1.9 Stateful Session Bean Removal
Removeアノテーションを使ってコンテナにステートフルセッションBeanのインスタンスを破棄させることができるそうです。5.2 Other Requirements
詳しくは「EJB Core Contracts and Requrements」を見てねといったカンジです。では、コールバック、インターセプタ、SessionSynchronizationのメソッドがどういうときにどういう順番で呼ばれるかを確かめるようなコードを実行させてみようと思います。前も使ったShoppingCartですがこれを少し変更して使ってみたいと思います。主な特徴は次のとおり
- Beanクラスはステートフル
- Beanクラスはインターセプタクラスをもつ(TraceInterceptorは以前使ったものと同じ)
- SessionSynchronizationの挙動を見るためTransactionManagementアノテーションを使用してBEAN管理のトランザクション管理とする
- JBossのステートフルセッションBeanはdefaultで数分ほどでパッシベートされるようなのでクライアントは6分sleepする
- クライアントでUserTransactionを使いトランザクション外からの呼び出しとトランザクション内での呼び出し両方を行ってみる
- すべてのコールバックアノテーションを使用
- Removeアノテーションを使用
ビジネスインタフェース
public interface ShoppingCart { public void buy(String product, int quantity); public void pay(); public HashMapgetCartContents(); public void remove(); }
Beanクラス:TransactionManagementアノテーションを使ってます。SerializableがないとパッシベートがうまくいかなかったのでSerializableをimplementしました。
@Stateful @Remote( { ShoppingCart.class }) @Interceptor(TraceInterceptor.class) @TransactionManagement(value=TransactionManagementType.BEAN) public class ShoppingCartBean implements ShoppingCart, SessionSynchronization, Serializable { private HashMapcart = 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 void pay() { System.out.println("pay for " + getCartContents()); } public HashMap getCartContents() { return cart; } @Remove public void remove() { System.out.println("remove()"); } @PostConstruct public void postConstruct() { System.out.println("postConstruct()"); } @PreDestroy public void preDestroy() { System.out.println("preDestroy()"); } @PostActivate public void postActive() { System.out.println("postActive()"); } @PrePassivate public void prePassivate() { System.out.println("prePassivate()"); } public void afterBegin() throws EJBException, RemoteException { System.out.println("afterBegin()"); } public void beforeCompletion() throws EJBException, RemoteException { System.out.println("beforeCompletion()"); } public void afterCompletion(boolean flag) throws EJBException, RemoteException { System.out.println("afterCompletion(" + flag + ")"); } }
クライアント:sleepしたりUserTransaction使ったりしてます。
public class ShoppingCartClient { public static void main(String[] args) throws Exception { InitialContext ctx = new InitialContext(); ShoppingCart cart = (ShoppingCart) ctx.lookup(ShoppingCart.class .getName()); // トランザクション内でbuy()を実行する cart.buy("おにぎり", 3); // 6分停止 Thread.sleep(360000); // トランザクション内でpay()を実行する UserTransaction tx = (UserTransaction) ctx.lookup("UserTransaction"); tx.begin(); cart.pay(); tx.commit(); cart.remove(); } }
JBossコンソールへの出力内容:適当にフォーマットしてます。
00:39:04,401 INFO [STDOUT] postConstruct() 00:39:04,472 INFO [STDOUT] BEGIN study.ejb.ShoppingCartBean#buy(おにぎり, 3) 00:39:04,472 INFO [STDOUT] END study.ejb.ShoppingCartBean#buy(おにぎり, 3) : null Executed Interceptor(s):1 00:41:25,534 INFO [STDOUT] prePassivate() 00:45:05,120 INFO [STDOUT] postActive() 00:45:05,120 INFO [STDOUT] postActive() 00:45:05,130 INFO [STDOUT] afterBegin() 00:45:05,130 INFO [STDOUT] BEGIN study.ejb.ShoppingCartBean#pay() 00:45:05,130 INFO [STDOUT] pay for {おにぎり=3} 00:45:05,130 INFO [STDOUT] END study.ejb.ShoppingCartBean#pay() : null Executed Interceptor(s):1 00:45:05,140 INFO [STDOUT] beforeCompletion() 00:45:05,140 INFO [STDOUT] afterCompletion(true) 00:45:05,150 INFO [STDOUT] BEGIN study.ejb.ShoppingCartBean#remove() 00:45:05,150 INFO [STDOUT] remove() 00:45:05,150 INFO [STDOUT] END study.ejb.ShoppingCartBean#remove() : null Executed Interceptor(s):1 00:45:05,150 INFO [STDOUT] preDestroy()
ブロックごとに間単にコメントしてみます。
- 最初にPostConstructアノテーションをもったメソッドが呼ばれてます。トランザクション外での呼び出しのときはSessionSynchronizationのメソッドは呼び出されません。
- 時間がたって(41分ころ)パッシベートされてます。
- 45分ころアクティベートされます。なぜか2回呼び出されてるんですが…。トランザクション内ということでafterBeginがAroundInvokeの前に実行されてます。そしてAroundInvoke後にbeforeCompletionやafterCompletionが呼ばれています。
- Removeアノテーションをもったメソッドが呼ばれてからPreDestroyアノテーションをもったメソッドが呼ばれています。
PostActiveメソッドが2回呼ばれているのが気になりますが、とりあえずChapter 5 完了です。
EJB 3.0(Public Draft)入門記 JBoss EJB 3.0 RC1のインストール
JBossの EJB 3.0 RC1 がリリースされたそうなので手順に従ってインストールしてみます。(前回はJSR-181のアノテーションを使ってみるということでJBoss-5.0.0alphaを使ったのですが、今回からはまたJBoss-4.0.3RC1に戻ります。)。Relese Noteをみると「Support interceptor injection」とありました。動かないなーと思っていたのですが単にJBossがサポートしてなかったんですね。EJB 3.0(Public Draft)入門記 Simplified API Chapter3 その4でインターセプタクラスにDataSourceをセッターインジェクションするコードを書いて期待通り動かなかったのですが、いま動かしてみたらOKでした。