Мы используем session bean для реализации бизнес-логики как дискретный, развертываемый элемент. Например, session bean может реализовать логику, требуемую для проверки истинности кредитной карты и, если вы разрабатываете приложение для электронной коммерции, вы можете просто включить этот компонент в ваше приложение, а не реализовывать проверку подлинности кредитной карты с чистого листа.

Session bean также часто используется для реализации распределенных façades (фасадов) (хотя не только этот шаблон удовлетворяет модели session bean). Façades является EJB, который доступен удаленному приложению и который прячет от клиента многие или все другие EJB, используемые приложением, как показано на приведенной ниже диаграмме.

Преимущества façade'а заключаются в упрощении клиентского представления удаленного приложения, так как он прячет большую часть внутренних сложностей. Он также может быть применен для снижения сетевого трафика, если он обеспечивает доступ большей части клиентских методов, которые передают массу структурированной информации в одном едином сетевом вызове, вместо того, чтобы заставить клиента взаимодействовать с группой различных объектов.

В приведенном ниже примере мы будем реализовывать session bean без состояний, который мы назовем ShowManager, и который будет предоставлять методы для редактирования программы показа - пока добавление и удаление фильмов. ShowManager также будет взаимодействовать с façade'ом, чтобы спрятать генерацию первичного ключа от клиента (помните, при каждом создании нового фильма мы должны предоставить уникальное значение первичного ключа).

Генерация уникального первичного ключа это сложная задача, которую я не буду полностью освещать здесь, но она исчерпывающе раскрыта во многих источниках, которые перечислены в конце этой главы. Для следующих примеров я хочу представить более надежное решение, чем то, что вы видели в предыдущем клиентском коде. Мы создадим новый CMP entity bean, называемый AutoCounter, который мы используем для генерации уникального значения первичного ключа. Принцип его работы очень прост: экземпляр AutoCounter'а содержит только одно поле типа int и имеет метод getNext( ), который увеличивает текущее значение поля и возвращает результат. Так как AutoCounter является entity bean, значение счетчика автоматически постоянно хранится в базе данных. Мы будем использовать тип String в качестве первичного ключа компонента, который также будет уникальным именем счетчика.

Ниже приведена реализация CMP bean'а AutoCounter, как это определено в каталоге c18/example06/src/ejb-tier:

//: c18:example06:src:ejb-tier:javatheater:ejb:implementation:AutoCounterBean.java
package javatheater.ejb.implementation;

import javax.ejb.*;

/**
* @ejb:bean name="AutoCounter" jndi-name="javatheater/AutoCounter" type="CMP"
*           primkey-field="name" reentrant="False"
* @ejb:pk class="java.lang.String"
* @ejb:home remote-class="javatheater.ejb.AutoCounterHome"
* @ejb:interface remote-class="javatheater.ejb.AutoCounter"
* @ejb:finder signature="java.util.Collection findAll()"
*/
public abstract class AutoCounterBean implements EntityBean {
  
/**
    * Проверяет первичный ключ и инициализирует поля bean.
    *
    * @ejb:create-method
    */
  
public String ejbCreate ( String name ) throws CreateException {
     
if ( name == null )
        
throw new CreateException ( "Counter name can not be null" ) ;
     
if ( name.length () == 0 )
        
throw new CreateException ( "Counter name can not be an empty string" ) ;
      setName
( name ) ;
      setValue
( 0 ) ;
     
return null ;
  
}
  
  
public void ejbPostCreate ( String name ) {
   }
  
  
/**
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public String getName () ;
  
  
/**
    * @ejb:persistent-field
    */
  
abstract public void setName ( String name ) ;
  
  
/**
    * @ejb:persistent-field
    */
  
abstract public int getValue () ;
  
  
/**
    * @ejb:persistent-field
    */
  
abstract public void setValue ( int value ) ;
  
  
/**
    * Возвращает следующее значение последовательности.
    *
    * @ejb:interface-method
    */
  
public int getNext () {
     
int value = getValue () ;
      value +=
1 ;
      setValue
( value ) ;
     
return value;
  
}
}
// /:~

Каждый entity bean в приложении JavaTheater будет использовать различные экземпляры AutoCounter, так что каждый из них будет использовать свой собственный набор значений первичного ключа. В настоящее время у нас есть только Movie entity bean, но позже мы реализуем Show entity bean. Так как каждый счетчик идентифицируется строкой (своим первичным ключом), а различные entity bean'ы будут использовать различные счетчики, мы должны предоставить для счетчиков Фильмов (Movie) и Показов (Show) свои собственные имена.

Я использую эту простую проблему для того, чтобы ввести еще одну особенность EJB 2.0: домашние методы EJB. Домашний метод реализуется в домашнем интерфейсе компонента, и поэтому он не вызывается ни в одном определенном экземпляре - нечто сродни статическим методам Java, но для распределенной среды. Домашние методы могут реализовывать произвольный код, не только код для создания, поиска и удаления EJB экземпляра, как вы это видели для фабрики.

В следующем примере, в директории c18/example07/src/ejb-tier, я добавил домашний метод getCounterName( ) в Movie entity bean:

/**
* Это реализация
<code> Movie </code> entity bean.
*
*
@see javatheater.ejb.MovieHome
* @ejb:bean name="Movie" jndi-name="javatheater/Movie" type="CMP"
*           primkey-field="id" reentrant="False"
* @ejb:pk class="java.lang.Integer"
* @ejb:home remote-class="javatheater.ejb.MovieHome"
* @ejb:interface remote-class="javatheater.ejb.Movie"
* @ejb:env-entry name="CounterName" value="MoviePKCounter"
*                type="java.lang.String"
*/
public abstract class MovieBean implements EntityBean {
  
// … Пропущенные методы
  
/**
    * Домашний метод, возвращающий имя счетчика (тип String) для генерации
    * уникального значения первичного ключа фильма. Реальное имя определяется,
    * как переменная окружения компонента Movie.
    *
    * @ejb:home-method
    */
  
public String ejbHomeGetCounterName () {
     
try {
        
Context initial = new InitialContext () ;
        
return ( String ) initial.lookup ( "java:comp/env/CounterName" ) ;
     
}
     
catch ( NamingException e ) {
        
throw new EJBException ( e ) ;
     
}
   }
  
// Пропущенные методы…
}

Метод носит название ejbHomeGetCounterName(). Префикс ejbHome требуется по правилу именования мета-интерфейсов EJB. Однако, в домашнем интерфейсе этот метод будет показан, как getCounterName( ). Реализация метода использует кое-что такое, чего вы до сих пор не видели: переменную окружения EJB. Концептуально, переменная окружения EJB не отличается от переменной окружения, которую вы определяете в оболочке операционной системы, исключение состоит в том, что она зависит от специфики EJB. Переменная окружения состоит просто из пары имя/значение, с двумя главными отличиями: 1) Значение может быть любым объектом, и 2) переменная определяется в специальном JNDI контексте, который называется контекст именования окружения (ENC), который всегда отображается во всех Контейнерах на java:comp/env. Значение определяется в дескрипторе развертывания и может быть получено с помощью поиска JNDI. Используя EJBDoclet, вы можете определить переменную окружения, используя тэг ejb:env-entry, как показано выше, а реальная переменная будет сгенерирована в дескрипторе развертывания. Преимущество от использования переменных окружения состоит в том, что их значения могут быть изменены Deployer'ом, что придаст большую гибкость в конфигурировании компонента.

Реализация session bean ShowManager (смотри ниже) должна сделать вещи понятнее:

//: c18:example07:src:ejb-tier:javatheater:ejb:implementation:ShowManager.java
package javatheater.ejb.implementation;

import javatheater.ejb.*;

import javax.ejb.*;

import javax.naming.*;

import java.rmi.RemoteException;

/**
* @ejb:bean type = "Stateless" name = "ShowManager" jndi-name =
*           "javatheater/ShowManager" reentrant = "false"
* @ejb:home remote-class="javatheater.ejb.ShowManagerHome"
* @ejb:interface remote-class="javatheater.ejb.ShowManager"
*/
public abstract class ShowManagerBean implements SessionBean {
  
AutoCounterHome autoCounterHome = null ;
   MovieHome movieHome =
null ;
   String movieCounterName =
null ;
  
  
/**
    * @ejb:create-method
    */
  
public void ejbCreate () throws CreateException {
     
try {
        
Context initial = new InitialContext () ;
        
// Получаем домашний интерфейс EJB, который мы будем использовать
         // позже.
        
autoCounterHome = ( AutoCounterHome ) initial
               .lookup
( "javatheater/AutoCounter" ) ;
         movieHome =
( MovieHome ) initial.lookup ( "javatheater/Movie" ) ;
         movieCounterName = movieHome.getCounterName
() ;
     
}
     
catch ( NamingException e ) {
        
throw new EJBException ( e ) ;
     
}
     
catch ( RemoteException e ) {
        
throw new EJBException ( "Error accessing the Movie home interface" ,
               e
) ;
     
}
   }
  
  
/**
    * Создаем новый фильм и возвращаем новое значение первичного ключа.
    *
    * @ejb:interface-method
    */
  
public Integer createMovie ( String movieName ) throws TheaterException {
     
try {
        
Integer pk = new Integer ( getCounter ( movieCounterName ) .getNext ()) ;
         movieHome.create
( pk, movieName ) ;
        
return pk;
     
}
     
catch ( CreateException e ) {
        
throw new TheaterException ( e ) ;
     
}
     
catch ( RemoteException e ) {
        
throw new TheaterException ( e ) ;
     
}
   }
  
  
/**
    * Удаляем фильм, передавая его первичный ключ.
    *
    * @ejb:interface-method
    */
  
public void deleteMovie ( Integer moviePk ) throws TheaterException {
     
try {
        
Movie movie = movieHome.findByPrimaryKey ( moviePk ) ;
         movie.remove
() ;
     
}
     
catch ( FinderException e ) {
        
throw new TheaterException ( e ) ;
     
}
     
catch ( RemoveException e ) {
        
throw new TheaterException ( e ) ;
     
}
     
catch ( RemoteException e ) {
        
throw new TheaterException ( e ) ;
     
}
   }
  
  
/**
    * Вспомогательный метод, чтобы получить генератор первичного ключа для
    * компонента Movie.
    */
  
AutoCounter getCounter ( String counterName ) throws RemoteException,
         CreateException
{
     
AutoCounter counter = null ;
     
try {
        
counter = autoCounterHome.findByPrimaryKey ( counterName ) ;
     
}
     
catch ( FinderException e ) {
      }
     
if ( counter == null )
        
counter = autoCounterHome.create ( counterName ) ;
     
return counter;
  
}
}
// /:~

Session bean без состояний ShowManager начинается с определения метода создания без аргументов (спецификация запрещает аргументы для метода создания session bean без состояний), в котором он находит с помощью JNDI домашний интерфейс компонента, который будет использоваться в других методах. Он также вызывает домашний метод getCounterName( ) для домашнего интерфейса Movie, чтобы получить имя счетчика, используемого для генерации уникального первичного ключа для фильмов.

Компонент также предоставляет метод createMovie( ), который может использовать клиентское приложение для создания нового объекта Movie в системе. Вы можете видеть, как этот метод прячет от клиента сложность генерации первичного ключа: в качестве информации, которую клиент должен предоставить, выступает только название фильма. Метод createMovie( ) использует AutoCounter, чтобы получить новый уникальный первичный ключ для Фильма, создает объект Movie и возвращает первичный ключ клиенту.

Если создание фильма закончится неудачей, клиенту будет послано TheaterException. Это исключение бизнес-уровня, которое мы определили, чтобы указать приложению на проблему. Его реализация достаточно проста, как вы можете видеть ниже. Пожалуйста, обратите внимание, что этот код просто демонстрирует вам, как легко определить и использовать ваше собственное исключение в спецификации методов enterprise компонент. В более реалистичной ситуации вы можете захотеть выполнить некое корректирующее действие или предоставить больше информации клиенту, используя различные исключения.

//: c18:example07:src:ejb-tier:javatheater:ejb:TheaterException.java
package javatheater.ejb;

public class TheaterException extends Exception {
  
public TheaterException () {
   }
  
  
public TheaterException ( String message ) {
     
super ( message ) ;
  
}
  
  
public TheaterException ( String message, Throwable cause ) {
     
super ( message, cause ) ;
  
}
  
  
public TheaterException ( Throwable cause ) {
     
super ( cause ) ;
  
}
}
// /:~

Session bean ShowManager также определяет метод deleteMovie( ), который клиенты используют для удаления фильма из программы. Его реализация проста, но польза заключена в том, что компонент теперь работает как façade, пряча сложность и предоставляя клиенту только те методы и классы, которые специфичны для бизнес области JavaTheater.

Дескриптор развертывания session bean ShowManager генерируется XDoclet'ом. Ниже вы можете увидеть часть, описывающую ShowManager session bean:

<session>
    <description><![CDATA[No Description.]]></description>
    <ejb-name>ShowManager</ejb-name>
    <home>javatheater.ejb.ShowManagerHome</home>
    <remote>javatheater.ejb.ShowManager</remote>
    <ejb-class>
        javatheater.ejb.implementation.ShowManagerSession
    </ejb-class>
    <session-type>Stateless</session-type>
    <transaction-type>Container</transaction-type>
</session>

Если вы сравните это описание с определением entity bean, вы увидите, что они очень похожи, и что определение session bean проще. Есть только два новых элемента - это <session-type>, который в данном случае устанавливает, что компонент ShowManager является session bean'ом без состояний, и <transaction-type>, который мы используем для указания того, что мы хотим, чтобы Контейнер позаботился о разграничении транзакций. Альтернативное значение, указание значения <Bean>, означает, что мы программно управляем жизненным циклом транзакции в коде реализации ShowManager (в этой главе мы не будем рассматривать программное разграничение транзакций).

Все классы, интерфейсы и XML дескрипторы для компонент будут собираться группой в наш ejb-jar (javatheater.jar) с помощью Ant скрипта, а все классы, необходимые клиентскому приложению, будут собираться в клиентский jar. Я подразумеваю, что вы выполните полную сборку этого примера с помощью Ant скрипта и проверите содержимое директория gen и двух сгенерированных jar файлов в директории dist.