Синглтон сессионный компонент в 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 для реализации параллелизма.
Для параллелизма, управляемого компонентом, давайте определим аннотацию@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.