Singleton Session Bean в Джакарта EE

1. Общ преглед

Винаги, когато се изисква единичен екземпляр на сеанс Bean за даден случай на употреба, можем да използваме Singleton сеанс Bean.

В този урок ще разгледаме това чрез пример, с приложение Джакарта EE.

2. Мейвън

На първо място, трябва да дефинираме необходимите зависимости на Maven в pom.xml .

Нека дефинираме зависимостите за API на EJB и вграден контейнер EJB за разполагане на 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 и не се споделя с други клиенти.

Когато комуникацията между клиента и бина приключи, сесията Bean също се прекратява.

3.2. Фасул за сесия без гражданство

Bean на сесия без гражданство не поддържа състояние на разговор с клиента. Бобът съдържа състоянието, специфично за клиента, само до продължителността на извикването на метода.

Последователните извиквания на методи са независими, за разлика от Stateful Session Bean.

Контейнерът поддържа пул зърна без гражданство и тези случаи могат да бъдат споделяни между множество клиенти.

3.3. Сингъл сеанс боб

Singleton Session Bean поддържа състоянието на боб за целия жизнен цикъл на приложението.

Singleton Session Bean са подобни на Sessions Beans без гражданство, но само един екземпляр на Singleton Session Bean е създаден в цялото приложение и не прекратява, докато приложението не бъде изключено.

Единичният екземпляр на боб се споделя между множество клиенти и може да бъде достъпен едновременно.

4. Създаване на Singleton сесия Bean

Нека започнем със създаването на интерфейс за него.

За този пример нека използваме анотацията javax.ejb.Local, за да дефинираме интерфейса:

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

Използването на @Local означава, че зърното е достъпно в същото приложение. Също така имаме опцията да използваме javax.ejb.Remote анотация, която ни позволява да извикаме EJB дистанционно.

Сега ще дефинираме класа на изпълнение на EJB. Маркираме класа като Singleton Session Bean, като използваме анотацията javax .ejb.Singleton .

В допълнение, нека също маркираме компонента с анотацията javax .ejb.Startup, за да информираме контейнера EJB да инициализира компонента при стартиране:

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

Това се нарича нетърпелива инициализация. Ако не използваме @Startup , контейнерът EJB определя кога да се инициализира бинът .

Също така можем да дефинираме множество сесионни компоненти, за да инициализираме данните и да заредим зърната в конкретния ред. Затова ще използваме анотацията javax.ejb.DependsOn, за да дефинираме зависимостта на нашия боб от други бобчета на сесията.

Стойността за анотацията @DependsOn е масив от имената на имената на клас Bean, от които зависи нашият Bean:

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

Ще дефинираме метод 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 Bean: управляван от контейнер паралел и управляван от бин паралел.

Анотацията javax.ejb.ConcurrencyManagement дефинира политиката на паралелност за метод. По подразбиране контейнерът EJB използва управлявана от контейнера паралелност.

В @ConcurrencyManagement анотацията отнема javax.ejb.ConcurrencyManagementType стойност. Опциите са:

  • ConcurrencyManagementType.CONTAINER за управлявана от контейнери паралелност.
  • ConcurrencyManagementType.BEAN за управлявана от боб паралелност.

5.1. Управляван от контейнера паралел

Най-просто казано, в едновременно управлявано от контейнера контейнер контролира как достъпът на клиентите до методи.

Нека използваме анотацията @ConcurrencyManagement със стойност javax.ejb.ConcurrencyManagementType.CONTAINER :

@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.