Перейти к основному содержимому

Singleton Session Bean в Jakarta EE

· 7 мин. чтения

1. Обзор

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

В этом руководстве мы рассмотрим это на примере приложения Jakarta EE.

2. Мавен

Прежде всего, нам нужно определить необходимые зависимости Maven в pom.xml .

Давайте определим зависимости для API-интерфейсов EJB и встроенного контейнера EJB для развертывания EJB:

<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>tomee-embedded</artifactId>
<version>1.7.5</version>
</dependency>

Последние версии можно найти на Maven Central по адресу JavaEE API и tomEE .

3. Типы сеансовых компонентов

Существует три типа сеансовых компонентов. Прежде чем мы рассмотрим Singleton Session Bean, давайте посмотрим, в чем разница между жизненными циклами трех типов.

3.1. Сессионные компоненты с отслеживанием состояния

Stateful Session Bean поддерживает состояние диалога с клиентом, с которым он общается.

Каждый клиент создает новый экземпляр Stateful Bean и не используется совместно с другими клиентами.

Когда связь между клиентом и компонентом завершается, сессионный компонент также завершает работу.

3.2. Сессионные компоненты без сохранения состояния

Сессионный компонент без сохранения состояния не поддерживает состояние диалога с клиентом. Компонент содержит состояние, специфичное для клиента, только до момента вызова метода.

Последовательные вызовы методов независимы, в отличие от Stateful Session Bean.

Контейнер поддерживает пул компонентов без сохранения состояния, и эти экземпляры могут совместно использоваться несколькими клиентами.

3.3. Одиночные сеансовые компоненты

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

Одиночные сеансовые компоненты аналогичны сеансовым компонентам без сохранения состояния, но только один экземпляр одноэлементного сеансового компонента создается во всем приложении и не завершается до тех пор, пока приложение не будет закрыто.

Один экземпляр bean-компонента совместно используется несколькими клиентами и может быть доступен одновременно.

4. Создание Singleton Session Bean

Начнем с создания интерфейса для него.

В этом примере давайте используем аннотацию javax.ejb.Local для определения интерфейса:

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

Использование @Local означает, что доступ к bean-компоненту осуществляется в том же приложении. У нас также есть возможность использовать аннотацию javax.ejb.Remote , которая позволяет нам удаленно вызывать EJB.

Теперь мы определим класс компонента реализации EJB. Мы помечаем класс как Singleton Session Bean с помощью аннотации javax .ejb.Singleton .

Кроме того, давайте также пометим bean-компонент аннотацией javax .ejb.Startup , чтобы сообщить EJB-контейнеру о необходимости инициализировать bean-компонент при запуске:

@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<String> states = new ArrayList<String>();
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: параллелизм, управляемый контейнером, и параллелизм, управляемый компонентом.

Аннотация 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 {
...
}

Чтобы указать уровень доступа к каждому из бизнес-методов синглтона, мы будем использовать аннотацию javax.ejb.Lock . javax.ejb.LockType содержит значения для аннотации @Lock . javax.ejb.LockType определяет два значения:

  • LockType.WRITE — это значение обеспечивает эксклюзивную блокировку для вызывающего клиента и предотвращает доступ всех других клиентов ко всем методам компонента. Используйте это для методов, которые изменяют состояние одноэлементного компонента.

  • LockType.READ — это значение предоставляет одновременные блокировки нескольким клиентам для доступа к методу.

    Используйте это для методов, которые только считывают данные из bean-компонента.

Имея это в виду, мы определим метод setStates() с аннотацией @Lock(LockType.WRITE) , чтобы предотвратить одновременное обновление состояния клиентами.

Чтобы клиенты могли читать данные одновременно, мы аннотируем getStates() с помощью @Lock(LockType.READ) :

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

private final Map<String, List<String> countryStatesMap = new HashMap<>();

@Lock(LockType.READ)
public List<String> getStates(String country) {
return countryStatesMap.get(country);
}

@Lock(LockType.WRITE)
public void setStates(String country, List<String> states) {
countryStatesMap.put(country, states);
}
}

Чтобы остановить выполнение методов в течение длительного времени и заблокировать других клиентов на неопределенный срок, мы будем использовать аннотацию javax.ejb.AccessTimeout для тайм-аута долго ожидающих вызовов.

Используйте аннотацию @AccessTimeout , чтобы определить время ожидания метода в миллисекундах. По истечении времени ожидания контейнер генерирует исключение javax.ejb.ConcurrentAccessTimeoutException , и выполнение метода прекращается.

5.2. Параллелизм, управляемый компонентом

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

Если параллелизм не реализован разработчиком, все методы доступны для всех клиентов одновременно. Java предоставляет примитивы синхронизации и volatile для реализации параллелизма.

Чтобы узнать больше о параллелизме, прочитайте о java.util.concurrent здесь и Atomic Variables здесь .

Для параллелизма, управляемого bean-компонентом, давайте определим аннотацию @ConcurrencyManagement со значением javax.ejb.ConcurrencyManagementType.BEAN для класса Singleton Session Bean:

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

Далее мы напишем метод setStates() , который изменяет состояние компонента с помощью ключевого слова synchronized :

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

Синхронизированное ключевое слово делает метод доступным только для одного потока за раз.

Метод getStates() не изменяет состояние Bean-компонента, поэтому ему не нужно использовать ключевое слово synchronized .

6. Клиент

Теперь мы можем написать клиент для доступа к нашему Singleton Session Bean.

Мы можем развернуть Session Bean на серверах контейнеров приложений, таких как JBoss, Glassfish и т. д. Для простоты мы будем использовать класс javax .ejb.embedded.EJBContainer . EJBContainer работает на той же JVM, что и клиент, и предоставляет большинство сервисов корпоративного контейнера компонентов.

Сначала мы создадим экземпляр 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<String> 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<String> actualStates = countryStateBean.getStates("UnitedStates");
assertNotNull(actualStates);
assertArrayEquals(expectedStates, actualStates.toArray());
}

Точно так же мы можем использовать экземпляр Context , чтобы получить ссылку на Bean-Managed Singleton 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<String> 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<String> actualStates = countryStateBean.getStates("UnitedStates");
assertNotNull(actualStates);
assertArrayEquals(expectedStates, actualStates.toArray());
}

Завершите наши тесты, закрыв EJBContainer в методе close() :

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

7. Заключение

Singleton Session Bean столь же гибок и мощен, как и любой стандартный Session Bean, но позволяет нам применять шаблон Singleton для обмена состоянием между клиентами нашего приложения.

Параллельное управление Singleton Bean можно легко реализовать с помощью Container-Managed Concurrency, где контейнер обеспечивает одновременный доступ нескольких клиентов, или вы также можете реализовать свое собственное управление параллелизмом с помощью Bean-Managed Concurrency.

Исходный код этого руководства можно найти на GitHub .