Jakarta EE의 Singleton Session Bean

1. 개요

주어진 사용 사례에 대해 세션 빈의 단일 인스턴스가 필요할 때마다 싱글 톤 세션 빈을 사용할 수 있습니다.

이 튜토리얼에서는 Jakarta EE 애플리케이션을 사용하여 예제를 통해이를 살펴볼 것입니다.

2. 메이븐

먼저 pom.xml 에 필요한 Maven 종속성을 정의해야합니다 .

EJB 배포를위한 EJB API 및 임베디드 EJB 컨테이너에 대한 종속성을 정의 해 보겠습니다.

 javax javaee-api 8.0 provided   org.apache.openejb tomee-embedded 1.7.5 

최신 버전은 Maven Central의 JavaEE API 및 tomEE에서 찾을 수 있습니다.

3. 세션 빈의 유형

세션 빈에는 세 가지 유형이 있습니다. Singleton Session Beans를 살펴보기 전에 세 가지 유형의 수명주기 간의 차이점을 살펴 보겠습니다.

3.1. 상태 저장 세션 빈

Stateful Session Bean은 통신중인 클라이언트와의 대화 상태를 유지합니다.

각 클라이언트는 Stateful Bean의 새 인스턴스를 생성하며 다른 클라이언트와 공유되지 않습니다.

클라이언트와 빈 간의 통신이 종료되면 세션 빈도 종료됩니다.

3.2. Stateless 세션 빈

Stateless Session Bean은 클라이언트와의 대화 상태를 유지하지 않습니다. Bean은 메소드 호출 기간까지만 클라이언트에 특정한 상태를 포함합니다.

연속적인 메소드 호출은 Stateful Session Bean과 달리 독립적입니다.

컨테이너는 Stateless Bean의 풀을 유지하며 이러한 인스턴스는 여러 클라이언트간에 공유 될 수 있습니다.

3.3. 싱글 톤 세션 빈

Singleton Session Bean은 애플리케이션의 전체 수명주기 동안 Bean의 상태를 유지합니다.

Singleton Session Bean은 Stateless Session Bean과 유사하지만 Singleton Session Bean의 한 인스턴스 만 전체 응용 프로그램에서 생성되며 응용 프로그램이 종료 될 때까지 종료되지 않습니다.

Bean의 단일 인스턴스는 여러 클라이언트간에 공유되며 동시에 액세스 할 수 있습니다.

4. 싱글 톤 세션 빈 생성

인터페이스를 만드는 것으로 시작하겠습니다.

이 예제에서는 javax.ejb.Local 주석을 사용하여 인터페이스를 정의 해 보겠습니다 .

@Local public interface CountryState { List getStates(String country); void setStates(String country, List states); }

@Local을 사용 하면 동일한 애플리케이션 내에서 Bean에 액세스 할 수 있습니다. 또한 EJB를 원격으로 호출 할 수있는 javax.ejb.Remote 주석 을 사용할 수있는 옵션도 있습니다 .

이제 구현 EJB 빈 클래스를 정의하겠습니다. javax .ejb.Singleton 주석을 사용하여 클래스를 Singleton Session Bean으로 표시합니다 .

또한 javax .ejb.Startup 주석으로 Bean을 표시하여 시작시 Bean을 초기화하도록 EJB 컨테이너에 알립니다.

@Singleton @Startup public class CountryStateContainerManagedBean implements CountryState { ... }

이를 즉시 초기화라고합니다. @Startup을 사용하지 않으면 EJB 컨테이너가 빈 초기화시기를 결정합니다.

또한 데이터를 초기화하고 특정 순서로 빈을로드하기 위해 여러 세션 빈을 정의 할 수 있습니다. 따라서 javax.ejb.DependsOn 주석을 사용하여 다른 Session Bean에 대한 Bean의 종속성을 정의합니다.

@DependsOn 어노테이션 의 값은 Bean이 의존하는 Bean 클래스 이름의 배열입니다.

@Singleton @Startup @DependsOn({"DependentBean1", "DependentBean2"}) public class CountryStateCacheBean implements CountryState { ... }

bean을 초기화 하는 initialize () 메소드를 정의하고 javax.annotation.PostConstruct 주석을 사용하여 라이프 사이클 콜백 메소드로 만듭니다 .

이 어노테이션을 사용하면 빈 인스턴스화시 컨테이너에서 호출됩니다.

@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. 동시성

다음으로 Singleton Session Bean의 동시성 관리를 설계합니다. EJB는 Singleton Session Bean에 대한 동시 액세스를 구현하는 두 가지 방법을 제공합니다. 컨테이너 관리 동시성 및 Bean 관리 동시성입니다.

주석 javax.ejb.ConcurrencyManagement 는 메소드에 대한 동시성 정책을 정의합니다. 기본적으로 EJB 컨테이너는 컨테이너 관리 동시성을 사용합니다.

@ConcurrencyManagement의 주석이 소요 javax.ejb.ConcurrencyManagementType의 값입니다. 옵션은 다음과 같습니다.

  • 컨테이너 관리 동시성을 위한 ConcurrencyManagementType.CONTAINER .
  • ConcurrencyManagementType.BEAN 은 빈 관리 동시성을위한 것입니다.

5.1. 컨테이너 관리 동시성

간단히 말해 컨테이너 관리 동시성에서 컨테이너는 클라이언트가 메서드에 액세스하는 방식을 제어합니다.

javax.ejb.ConcurrencyManagementType.CONTAINER 값이 있는 @ConcurrencyManagement 주석을 사용하겠습니다 .

@Singleton @Startup @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class CountryStateContainerManagedBean implements CountryState { ... }

To specify the access level to each of the singleton’s business methods, we'll use javax.ejb.Lock annotation. javax.ejb.LockType contains the values for the @Lock annotation. javax.ejb.LockType defines two values:

  • LockType.WRITE – This value provides an exclusive lock to the calling client and prevents all other clients from accessing all methods of the bean. Use this for methods that change the state of the singleton bean.
  • LockType.READThis value provides concurrent locks to multiple clients to access a method.

    Use this for methods which only read data from the bean.

With this in mind, we'll define the setStates() method with @Lock(LockType.WRITE) annotation, to prevent simultaneous update of the state by clients.

To allow clients to read the data concurrently, we'll annotate getStates() with @Lock(LockType.READ):

@Singleton @Startup @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class CountryStateContainerManagedBean implements CountryState { private final Map
    

To stop the methods execute for a long time and blocking the other clients indefinitely, we'll use the javax.ejb.AccessTimeout annotation to timeout long-waiting calls.

Use the @AccessTimeout annotation to define the number of milliseconds method times-out. After the timeout, the container throws a javax.ejb.ConcurrentAccessTimeoutException and the method execution terminates.

5.2. Bean-Managed Concurrency

In Bean managed concurrency, the container doesn't control simultaneous access of Singleton Session Bean by clients. The developer is required to implement concurrency by themselves.

Unless concurrency is implemented by the developer, all methods are accessible to all clients simultaneously. Java provides the synchronization and volatile primitives for implementing concurrency.

To find out more about concurrency read about java.util.concurrent here and Atomic Variables here.

For bean-managed concurrency, let’s define the @ConcurrencyManagement annotation with the javax.ejb.ConcurrencyManagementType.BEAN value for the Singleton Session Bean class:

@Singleton @Startup @ConcurrencyManagement(ConcurrencyManagementType.BEAN) public class CountryStateBeanManagedBean implements CountryState { ... }

Next, we'll write the setStates() method which changes the state of the bean using synchronized keyword:

public synchronized void setStates(String country, List states) { countryStatesMap.put(country, states); }

The synchronized keyword makes the method accessible by only one thread at a time.

The getStates() method doesn't change the state of the Bean and so it doesn't need to use the synchronized keyword.

6. Client

Now we can write the client to access our Singleton Session Bean.

We can deploy the Session Bean on application container servers like JBoss, Glassfish etc. To keep things simple, we will use the javax.ejb.embedded.EJBContainer class. EJBContainer runs in the same JVM as the client and provides most of the services of an enterprise bean container.

First, we'll create an instance of EJBContainer. This container instance will search and initialize all the EJB modules present in the classpath:

public class CountryStateCacheBeanTest { private EJBContainer ejbContainer = null; private Context context = null; @Before public void init() { ejbContainer = EJBContainer.createEJBContainer(); context = ejbContainer.getContext(); } }

Next, we'll get the javax.naming.Context object from the initialized container object. Using the Context instance, we can get the reference to CountryStateContainerManagedBean and call the methods:

@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()); }

Similarly, we can use the Context instance to get the reference for Bean-Managed Singleton Bean and call the respective methods:

@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()); }

End our tests by closing the EJBContainer in the close() method:

@After public void close() { if (ejbContainer != null) { ejbContainer.close(); } }

7. Conclusion

Singleton Session Beans are just as flexible and powerful as any standard Session Bean but allow us to apply a Singleton pattern to share state across our application's clients.

Concurrency management of the Singleton Bean could be easily implemented using Container-Managed Concurrency where the container takes care of concurrent access by multiple clients, or you could also implement your own custom concurrency management using Bean-Managed Concurrency.

The source code of this tutorial can be found over on GitHub.