Синглтон сессионный компонент в Java EE

Синглтон сессионный компонент в Java EE

1. обзор

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

В этом руководстве мы собираемся изучить это на примере с приложением Java EE.

2. специалист

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

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


    javax
    javaee-api
    8.0
    provided



    org.apache.openejb
    tomee-embedded
    1.7.5

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

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

Существует три типа сессионных бобов. Прежде чем изучать синглтон-сессионные бины, давайте посмотрим, в чем разница между жизненными циклами трех типов.

3.1. Stateful Session Beans

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

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

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

3.2. Сессионные бобы без гражданства

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

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

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

3.3. Синглтон сессионные бобы

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

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

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

4. Создание одноэлементного сеансового компонента

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

В этом примере давайте воспользуемся аннотацией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.

Кроме того, давайте также отметим 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.

С помощью этой аннотации он будет вызываться контейнером при создании экземпляра 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. совпадение

Затем мы спроектируем управление параллелизмом для Singleton Session Bean. EJB предоставляет два метода для реализации одновременного доступа к сессионному компоненту Singleton: параллелизм, управляемый контейнером, и параллелизм, управляемый компонентом.

Аннотация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* – Это значение обеспечивает одновременную блокировку нескольких клиентов для доступа к методу. Используйте это для методов, которые только читают данные из компонента.

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

Чтобы клиенты могли читать данные одновременно, мы аннотируем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. Параллелизм, управляемый компонентами

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

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

Чтобы узнать больше о параллелизме, прочтите оjava.util.concurrenthere и атомарных переменныхhere.

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

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

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

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

Ключевое словоsynchronized делает метод доступным одновременно только для одного потока.

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

6. клиент

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

Мы можем развернуть 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 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, чтобы получить ссылку на одиночный компонент, управляемый компонентом, и вызвать соответствующие методы:

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

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

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

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

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

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

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