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のメソッドを考慮した場合の呼び出しの順番は

  1. afterBegin
  2. AroundInvoke
  3. 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 HashMap getCartContents();
	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 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 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 完了です。