Java EEのシングルトンセッションBean
1. 概要
特定のユースケースでセッションBeanの単一のインスタンスが必要な場合はいつでも、シングルトンセッションBeanを使用できます。
このチュートリアルでは、Java EEアプリケーションを使用して、例を通じてこれを調査します。
2. メーベン
まず、必要なMavenの依存関係をpom.xmlで定義する必要があります。
EJBをデプロイするためのEJBAPIと組み込みEJBコンテナの依存関係を定義しましょう。
javax
javaee-api
8.0
provided
org.apache.openejb
tomee-embedded
1.7.5
最新バージョンは、Maven CentralのJavaEE APIおよびtomEEにあります。
3. セッションBeanの種類
セッションBeanには3つのタイプがあります。 シングルトンセッションBeanについて説明する前に、3つのタイプのライフサイクルの違いを見てみましょう。
3.1. ステートフルセッションBean
ステートフルセッションBeanは、通信しているクライアントとの会話状態を維持します。
各クライアントはステートフルBeanの新しいインスタンスを作成し、他のクライアントと共有されません。
クライアントとBean間の通信が終了すると、セッションBeanも終了します。
3.2. ステートレスセッションBean
ステートレスセッションBeanは、クライアントとの会話状態を維持しません。 Beanには、メソッド呼び出しの期間までクライアントに固有の状態が含まれます。
ステートフルセッションBeanとは異なり、連続したメソッド呼び出しは独立しています。
コンテナはステートレスBeanのプールを維持し、これらのインスタンスは複数のクライアント間で共有できます。
3.3. シングルトンセッションBean
シングルトンセッションBeanは、アプリケーションのライフサイクル全体にわたってBeanの状態を維持します。
シングルトンセッションBeanはステートレスセッションBeanに似ていますが、アプリケーション全体でシングルトンセッションBeanのインスタンスが1つだけ作成され、アプリケーションがシャットダウンされるまで終了しません。
Beanの単一インスタンスは複数のクライアント間で共有され、同時にアクセスできます。
4. シングルトンセッションBeanの作成
そのためのインターフェースを作成することから始めましょう。
この例では、javax.ejb.Localアノテーションを使用してインターフェースを定義しましょう。
@Local
public interface CountryState {
List getStates(String country);
void setStates(String country, List states);
}
@Localを使用すると、同じアプリケーション内でBeanにアクセスできます。 また、javax.ejb.Remoteアノテーションを使用して、EJBをリモートで呼び出すこともできます。
次に、実装EJBBeanクラスを定義します。 アノテーションjavax.ejb.Singletonを使用して、クラスをシングルトンセッションBeanとしてマークします。
さらに、Beanにjavax.ejb.Startupアノテーションを付けて、起動時にBeanを初期化するようにEJBコンテナに通知しましょう。
@Singleton
@Startup
public class CountryStateContainerManagedBean implements CountryState {
...
}
これは、熱心な初期化と呼ばれます。 @Startupを使用しない場合、EJBコンテナはBeanを初期化するタイミングを決定します。
複数のセッションBeanを定義して、データを初期化し、特定の順序でBeanをロードすることもできます。 したがって、javax.ejb.DependsOnアノテーションを使用して、他のセッションBeanへのBeanの依存関係を定義します。
@DependsOnアノテーションの値は、Beanが依存するBeanクラス名の名前の配列です。
@Singleton
@Startup
@DependsOn({"DependentBean1", "DependentBean2"})
public class CountryStateCacheBean implements CountryState {
...
}
Beanを初期化し、javax.annotation.PostConstructアノテーションを使用してライフサイクルコールバックメソッドにするinitialize()メソッドを定義します。
このアノテーションを使用すると、Beanのインスタンス化時にコンテナによって呼び出されます。
@PostConstruct
public void initialize() {
List states = new ArrayList();
states.add("Texas");
states.add("Alabama");
states.add("Alaska");
states.add("Arizona");
states.add("Arkansas");
countryStatesMap.put("UnitedStates", states);
}
5. 並行性
次に、シングルトンセッションBeanの同時実行管理を設計します。 EJBには、シングルトンセッションBeanへの同時アクセスを実装するための2つのメソッドがあります。コンテナ管理の同時実行とBean管理の同時実行です。
アノテーションjavax.ejb.ConcurrencyManagementは、メソッドの並行性ポリシーを定義します。 デフォルトでは、EJBコンテナはコンテナ管理の同時実行性を使用します。
@ConcurrencyManagementアノテーションはjavax.ejb.ConcurrencyManagementType値を取ります。 オプションは以下のとおりです。
-
コンテナ管理の同時実行性の場合はConcurrencyManagementType.CONTAINER。
-
Bean管理の同時実行性の場合はConcurrencyManagementType.BEAN。
5.1. コンテナ管理の同時実行性
コンテナ管理による同時実行では、コンテナは、クライアントがメソッドにアクセスする方法を制御します。
値javax.ejb.ConcurrencyManagementType.CONTAINERで@ConcurrencyManagementアノテーションを使用しましょう:
@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class CountryStateContainerManagedBean implements CountryState {
...
}
シングルトンの各ビジネスメソッドへのアクセスレベルを指定するには、javax.ejb.Lockアノテーションを使用します。 javax.ejb.LockTypeには、@Lockアノテーションの値が含まれています。 javax.ejb.LockTypeは、次の2つの値を定義します。
-
LockType.WRITE –この値は、呼び出し元のクライアントに排他ロックを提供し、他のすべてのクライアントがBeanのすべてのメソッドにアクセスできないようにします。 これは、シングルトンBeanの状態を変更するメソッドに使用します。
-
*LockType.READ* –この値は、メソッドにアクセスするために複数のクライアントに同時ロックを提供します。 これは、Beanからデータのみを読み取るメソッドに使用します。
これを念頭に置いて、@Lock(LockType.WRITE)アノテーションを使用してsetStates()メソッドを定義し、クライアントによる状態の同時更新を防ぎます。
クライアントがデータを同時に読み取れるようにするために、getStates()に@Lock(LockType.READ)の注釈を付けます。
@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class CountryStateContainerManagedBean implements CountryState {
private final Map countryStatesMap = new HashMap<>();
@Lock(LockType.READ)
public List getStates(String country) {
return countryStatesMap.get(country);
}
@Lock(LockType.WRITE)
public void setStates(String country, List states) {
countryStatesMap.put(country, states);
}
}
メソッドの実行を長時間停止し、他のクライアントを無期限にブロックするために、javax.ejb.AccessTimeoutアノテーションを使用して待機中の呼び出しをタイムアウトします。
@AccessTimeoutアノテーションを使用して、メソッドのタイムアウトのミリ秒数を定義します。 タイムアウト後、コンテナはjavax.ejb.ConcurrentAccessTimeoutExceptionをスローし、メソッドの実行は終了します。
5.2. Bean管理の並行性
Bean管理の同時実行では、コンテナはクライアントによるシングルトンセッションBeanの同時アクセスを制御しません。 開発者は自分で並行性を実装する必要があります。
開発者が並行性を実装しない限り、すべてのメソッドはすべてのクライアントから同時にアクセスできます。 Javaは、並行性を実装するためのsynchronizationおよびvolatileプリミティブを提供します。
Beanで管理される同時実行性の場合、シングルトンセッションBeanクラスのjavax.ejb.ConcurrencyManagementType.BEAN値を使用して@ConcurrencyManagementアノテーションを定義しましょう。
@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class CountryStateBeanManagedBean implements CountryState {
...
}
次に、synchronizedキーワードを使用してBeanの状態を変更するsetStates()メソッドを記述します。
public synchronized void setStates(String country, List states) {
countryStatesMap.put(country, states);
}
synchronizedキーワードを使用すると、一度に1つのスレッドのみがメソッドにアクセスできます。
getStates()メソッドはBeanの状態を変更しないため、synchronizedキーワードを使用する必要はありません。
6. クライアント
これで、Singleton Session Beanにアクセスするクライアントを作成できます。
JBoss、GlassfishなどのアプリケーションコンテナサーバーにセッションBeanをデプロイできます。 簡単にするために、javax.ejb.embedded.EJBContainerクラスを使用します。 EJBContainerはクライアントと同じJVMで実行され、エンタープライズBeanコンテナのほとんどのサービスを提供します。
まず、EJBContainerのインスタンスを作成します。 このコンテナインスタンスは、クラスパスに存在するすべてのEJBモジュールを検索して初期化します。
public class CountryStateCacheBeanTest {
private EJBContainer ejbContainer = null;
private Context context = null;
@Before
public void init() {
ejbContainer = EJBContainer.createEJBContainer();
context = ejbContainer.getContext();
}
}
次に、初期化されたコンテナオブジェクトからjavax.naming.Contextオブジェクトを取得します。 Contextインスタンスを使用して、CountryStateContainerManagedBeanへの参照を取得し、メソッドを呼び出すことができます。
@Test
public void whenCallGetStatesFromContainerManagedBean_ReturnsStatesForCountry() throws Exception {
String[] expectedStates = {"Texas", "Alabama", "Alaska", "Arizona", "Arkansas"};
CountryState countryStateBean = (CountryState) context
.lookup("java:global/singleton-ejb-bean/CountryStateContainerManagedBean");
List actualStates = countryStateBean.getStates("UnitedStates");
assertNotNull(actualStates);
assertArrayEquals(expectedStates, actualStates.toArray());
}
@Test
public void whenCallSetStatesFromContainerManagedBean_SetsStatesForCountry() throws Exception {
String[] expectedStates = { "California", "Florida", "Hawaii", "Pennsylvania", "Michigan" };
CountryState countryStateBean = (CountryState) context
.lookup("java:global/singleton-ejb-bean/CountryStateContainerManagedBean");
countryStateBean.setStates(
"UnitedStates", Arrays.asList(expectedStates));
List actualStates = countryStateBean.getStates("UnitedStates");
assertNotNull(actualStates);
assertArrayEquals(expectedStates, actualStates.toArray());
}
同様に、Contextインスタンスを使用して、Bean管理のシングルトンBeanの参照を取得し、それぞれのメソッドを呼び出すことができます。
@Test
public void whenCallGetStatesFromBeanManagedBean_ReturnsStatesForCountry() throws Exception {
String[] expectedStates = { "Texas", "Alabama", "Alaska", "Arizona", "Arkansas" };
CountryState countryStateBean = (CountryState) context
.lookup("java:global/singleton-ejb-bean/CountryStateBeanManagedBean");
List actualStates = countryStateBean.getStates("UnitedStates");
assertNotNull(actualStates);
assertArrayEquals(expectedStates, actualStates.toArray());
}
@Test
public void whenCallSetStatesFromBeanManagedBean_SetsStatesForCountry() throws Exception {
String[] expectedStates = { "California", "Florida", "Hawaii", "Pennsylvania", "Michigan" };
CountryState countryStateBean = (CountryState) context
.lookup("java:global/singleton-ejb-bean/CountryStateBeanManagedBean");
countryStateBean.setStates("UnitedStates", Arrays.asList(expectedStates));
List actualStates = countryStateBean.getStates("UnitedStates");
assertNotNull(actualStates);
assertArrayEquals(expectedStates, actualStates.toArray());
}
close()メソッドのEJBContainerを閉じて、テストを終了します。
@After
public void close() {
if (ejbContainer != null) {
ejbContainer.close();
}
}
7. 結論
シングルトンセッションBeanは、標準のセッションBeanと同じように柔軟で強力ですが、シングルトンパターンを適用して、アプリケーションのクライアント間で状態を共有することができます。
シングルトンBeanの同時実行管理は、コンテナが複数のクライアントによる同時アクセスを処理するContainer-Managed Concurrencyを使用して簡単に実装できます。また、Bean-Managed Concurrencyを使用して独自のカスタム同時実行管理を実装することもできます。
このチュートリアルのソースコードはover on GitHubにあります。