EJB

Материал из wiki
Перейти к: навигация, поиск

Enterprise Java Beans - EJB

EJB - корпоративный java-компонент.

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

Все технологии, рассмотренные до этого не позволяли распределять бизнес-логику (кроме вебсервисов). EJB предназначено для Java и делает распределение бизнес-логики легче.

EJB предоставляет фреймворк, упрощающий разработку распределенных приложений.

Спецификация EJB определяет три типа распределенных компонентов

  1. sessions beans - сеансовые
  2. message-driven beans (MDB) - компоненты управляемые сообщениями
  3. entity beans - компоненты сущностей

sessions beans

sessions beans могут быть 3 видов:

  • stateless - без сохранения состояния, при обращении к методам которых состояние не сохраняется. Это позволяет использовать один и тот же экземпляр для обработки зарпосов от разных клиентов.
  • stateful - с состоянием. Это сеансовые компоненты, которые сохраняют диаологове состояние (состояние, которое должно быть сохранено между обращениями клиента). По сути можно считать такой компонент аналогом сессии в jsp
  • singleton. Создается единственный экземпляр в конейнере и все запросы обрабатываются им, при чем он должен обеспечить параллельный доступ.

stateless

Компоненты stateless по настоящему позволяют кое-что сохранять в своих полях, но оно не может иметь отношение для взаимодействия с конкретным клиентом. Сохранять такие компоненты stateless могут, например, подключение к БД. Но следует иметь в виду, что такие сохраненные переменные нельзя передавать клиенту в качестве результата выполнения какого-либо метода, потому-что контейнер, внутри которого работают все эти компоненты, может направить вызов обращения клиента к любому из имеющихся экземпляров компоненты stateless. Контейнер определяем к какому компоненту обратиться на основании того, какой компонент является наименее загруженным.

Важно, еще раз, все компоненты stateless должны быть идентичны и обращение клиента может быть направлено к любому из этих компонент.

Вообще, контейнер управляет всеми видами компонентов EJB.

Если существующих компонентов недостаточно для обработки всех запросов, то контейнер может сам создать необходимое количество или, наоборот, удалить ненужные.

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

Компоненты stateless не должны реализовывать javax.ejb.SessionSynchronization. Также на основе stateless компоненты можно реализовать веб-сервис.

stateful

Сеансовые компоненты stateful предназначены для кеширования информации между запросами клиента в течении клиентского сеанса. Для каждого клиента создается свой собственных экземпляр компонента stateful. Экзпмляр stateful уничтожается либо по запросу клиента, либо после определенного периода неактивности. Возможны ситуации, когда некоторые экземпляры простаивают и расходуют ресурсы, что неэффективно. Чтобы с этой ситуацией бороться, существует рабочий набор - компоненты, используемые в данный момент. Если компонент не используется некоторое время, то он может быть исключен из рабочего процесса (сериализован, сохранен в специальное хранилище и выгружен из ОЗУ). С этим могут быть проблемы, если компонент держит внутри себя соединения или открытые сокеты, поэтому, желательно, прежде чем компонент будет выгружен из рабочего набора, чтобы он эти ресурсы освободил.

Перевод из рабочего процесса называют passication (instance passivation), а обратный перевод называется активацией (activation).

Чтобы дать компоненту освободить ресурсы, перед переводом в пассивное состояние, контейнер вызывает метод, помеченный аннотацией @PrePassivate и реализовать этот функционал должен программист.

При переводе из пассивного состояния в активное, конейтенр вызовет метод @PostActivate, который должен восстановить компонент.

На перевод компонентов в пассивное состояние накладываются ограничения, например:

  • нельзя перевести компонент в пассивное состояние, если выполняется транзакция
  • если один из сущностных компонентов, принадлежащих сеансовуму компоненту, не сериализуем

В этих случаях контейнер просто не может перевести в пассивное состояние. Сеансовые компоненты без сохранения состояния не переводятся, а просто уничтожаются.


singleton

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

Экземпляр singleton разделяется между всеми клиентами, в отличии от двух предыдущих типов сеансовых компонентов.


JMS

Компоненты управляемыми сообщениями были введенеы в спецификацию EJB2. Они позволяют получать и обрабатывать jms-сообщения (Java Messaging Services). JMS, в отличие от сокетов, обеспечивает надежную передачу сообщения. Например, если клиент, для которого предназначено сообщение, не работает, то сообщение будет сохранено на сервере и будет передано клиенту когда он заработает. JMS поддерживает два типа сообщений:

  • очереди
  • темы

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

Тема - каждое сообщение отправляется всем подпищикам данной темы.

Компоненты, ориентированные на сообщения, работают в асинхронном режиме, выполняют какие-либо действия по получению сообщения, являются относительно короткоживущими (в отличие от entity-компонент), т.е. при завершении работы контейнера, при завершении компонента, он теряет свое состояние (как и ранее рассмотренные).

Также компоненты могут поддерживать транзакцию и не предоставляют напрямую работу с БД (работа с БД осуществляется с помощью entity-компонент).

Контейнер может поддерживать пул MDB-компонентов.

Любой MJB-компонент может быть удаленным или локальным. Удаленный расположен за пределами текущего EJB-контейнера (на другой машине). Локальный расположен в том-же контейнере, где и обращающийся к нему компонент. Такое разделение сделано, чтобы можно было экономить ресурсы при обращении к локальным компонентам.

Для того, чтобы объявить удаленный компонент, можно поступить 2 способами.

Способ 1: объявить Java-интерфейс и пометить его аннотацией @Remote из javax.ejb.Remote

Класс, реализующий этот интерфейс будет удаленным компонентом.

Способ 2: пометить сам класс аннотацией @Remote и в качестве параметра передать название интерфейса

Пример:

@Remote(InterfaceName)
public calss ...

Интерфейсы часто называют еще бизнес-интерфейсами, т.к. они используются для реализации бизнес-логики.

Объявление локального интерфейса осуществляется аналогично, только используется аннотация @Local, а не @Remote.

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

Один и тот же компонент может быть и удаленным и локальным, для этого надо будет реализовывать два интерфейса, т.к. один и тот же интерфейс не может быть и удаленным и локальным. (как правило так не делают).

В принципе, ejb-компонент без сохранения состояния может являться веб-сервисом, и обращаться к нему можно как к обычному веб-сервису, но для этого он должен быть помечен javax.jws.WebService.

Сеансовые компоненты могут быть трех разным типов и помечаются аннотациями из javax.ejb.Stateless javax.ejb.Statefull, javax.ejb.Singleton

В случае с компонентами управляемыми сообщениями, новый интерфейс реализовывать не обязательно. Сам класс должен быть помечен аннотацией javax.ejb.MessageDriven, но он должен реализовать существующий интерфейс javax.jms.MessageListener (это интерфейс должен реализовывать события, чаще всего это onMessage).


Требования, предъявляемые к компонентам

Класс, описывающий сеансовый компонент:

  1. должен быть public
  2. не может быть final
  3. должен быть public-конструктор без параметров
  4. не должен определяться метод finalize()

При этом надо иметь в виду, что поддержка аннотаций появилась только с EJB 3 версии. В более ранних версиях требовалось, чтобы компонент наследовался от EJBObject или EJBLocalObject.

Клиенты

Клиенты, работающие с EJB-компонентами, никогда не работают с этими компонентами напрямую. Они работают с заглушкой. Заглушка уже преобразует обращения в соответствии с протоколом передачи распределенных объектов по сети, там запросы демаршализируются и контейнером производится обращение к нужным EJB-компонентам.

Клиент не знает конкретную реализацию EJB компонента и можно даже поменять тип компонента с stateless на stateful без изменения кода переменных.

Как клиенты могут работать с объектами? (в качестве клиента могут быть и сервлеты, и jsp-страницы и javaSE и другой EJB-компонент).

В случае с stateless компонентом: он может быть представлен как веб-сервис и, тогда, работа с ним будет аналогично вебсервисам:

@WebServiceRef
public StockQuesteService stockServie ...
.. получается port,
работаем с port как обычно.

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

Способ 1:

Впрыскивание ресурсов (DI):

@EJB 
Cart cart;

При этом можно передавать имя компонента, но, если имя не передано, то конейнет выполнит поиск EJB-компонента соответствующему типа, перед которым стоит аннтация (у нас Cart) и ссылка будет присвоена переменной.

Cпособ 2:

используя JNDI

@Resource SessionContext ctx;
Cart cart = (Cart) ctx.lookup("cart");

После того, как получена ссылка, с ней можно обращаться как с POJO-объектом.

Пример:

 
@EJB Cart cart;
cart.startShopping();
card.addItem(633);

Возможно еще получать ссылку не на интерфейс EJB-объекта, а на класс EJB-объекта. (В предыдущем примере Cart определяло интерфейс, который реализован пусть будет CartBean).

Это можно сделать так:

@EJB
CartBean cart;

Это называют безинтерфейсным представлением объекта.

Пример:

@Remote
public interface MathOperationRemote{
    public double diviceNumbers(double numberFirst, double nubmeTwo);
    }
@Stateless
public class MathOperatonBean implements MathOperationRemote {
    ... здесь раилзация
    }

К бизнес-методам есть тоже ограничения

  1. не рекомендуется начинать название метода с ejb
  2. метод должен быть public
  3. не может быть final или static

Получение ссылки на EJB-объекты

Несколько подробнее про получение ссылок на EJB-объекты: DI можно использовать не всегда, эти зависимости должен кто-то инжектить. Как правило это делает контейнер (сервер приложений). Т.о. если клиентом является сервлет или другой EJB-объект, то DI использовать можно, т.к. он будет выполняться контейнером и он произведет впрыскивание. Если же клиентом является обычное JavaSE-приложение, то DI не сработает и, в этом случае, необходимо использовать JNDI. Кроме того, существует так называемый клиентский контейнер (ACC application client container). Этот клиентский контейнер может провести впрыскивание зависимостей в обычное JavaSE приложение. Вместо с glassfish поставляется клиентский контейнер appplitn и чтобы ее использовать можно использовать appclient -client EJBMathOperClient.jar, где в jar-файле находится сам клиент.

Не рекомендуется в одном архиве компоновать и клиентскую часть и разворачивание ее на сервере. Т.е не должно быть клиентской части и класса main от JavaSE.

В случае использования JNDI тоже не все так просто. Если приложение работает в конейнере, то никаких проблем.

Contex ctn = new InitialContext()
mops = (MathOperationRemote) ctx.lookup(jngiName);

Но если программа работает вне контейнера, нужно создать JNDI-контекст. Нужно сформировать параметры получения контекста, но они зависят от конкретного контейнера, предоставляющего JNDI.

Пример для JBOSS:

properties p new Properties();
p.put (Cntext.INITIAL_CONText_FACTORY, "org.jnp.interface.NamingContextFactory");
p.put( context.URL...
...

Пример клиента:

Context ctx = getInitialContext()
jndiName = MathOperationRemote.class.getName();
MathOperationRemote mops = (Math... ) ctx.lookup(jndiName);

Об именовании EJB-объектов:

К каджому EJB-объекту можно получить ссылку по нескольким именам. (через интерфейс и через название класса, реализующего интерфейс). Но есть еще способы:

1) Через глобальный контекст

java:global[/<app-name>]/<module-bame>/<bean-name>/...

где <app-name> испоьзуется если приложение было упакован в ear -архив (

2)

java:app/<module-name>/<bean-name>/[!<full-qulified-interface-name>]

поиск при этом проводится в контексте текущего приложения

3)

java:module/<bean-name>

это если ищем внутри текущего сервера

Пример: пусть есть fooejb.jar без дескрипотора как stan-alone модуль

java:global/fooejb/FooBean
java:global/fooejb/FooBean!com.acme.Foo
java:app/fooejb/FooBean
java:module/FooBean
java:module/FooBean!com.acme.Foo

В случае если fooejb.jar внутри fooapp.ear, то обращение

java:global/fooapp/fooejb/FooBean
java:app/fooejb/FooBean
java:module/FooBean