Первый EJB, который мы будем реализовывать, является системой продажи билетов. Я выбрал этот тип объекта, поскольку он не содержит бизнес логики и имеет немного постоянной информации, так что это не будет отвлекать нас от реальной практики реализации EJB.
Так как фильмы содержат информацию (id и title), которая должна быть постоянно, мы будет реализовывать их как entity bean, используя Container Managed Persistence. Этот EJB будет называться, как это ни удивительно, Movie.
Пожалуйста, обратите внимание, что этот пример фундаментален, так как, хотя мы подходим к реализации специфического типа EJB (entity bean), большая часть информации, которую вы увидите, применима и к session bean, и к entity bean во всех их вариациях.
Для реализации компонента Movie, вам понадобиться определить два интерфейса, домашний интерфейс и компонентный интерфейс, а также один класс реализации. В зависимости от того, представление какого рода должен проявлять компонент, домашний и компонентный интерфейс могут быть реализованы как удаленный или локальный. В этом случае мы определим интерфейс Movie как удаленный.
Вам также понадобиться предоставить XML файл, называемый дескриптором развертки, который вы используете, чтобы сообщить Контейнеру о некоторых характеристиках вашего EJB, включая постоянство, транзакционные атрибуты и атрибуты безопасности. Наконец, так как реализуемый нами компонент является entity bean'ом, мы может пожелать снабдить его классом, являющимся первичным ключом компонента (или вы можете просто использовать некий предопределенный класс, что мы и сделаем).
Я буду объяснять каждый из пяти элементов, перечисленных выше (домашний и компонентный интерфейсы, класс реализации, дескриптор развертки и первичный ключ) в следующих разделах. Я также дам вам немного информации о внутренностях Контейнера и развертке Bean'а.
Домашний интерфейс
Начнем с того, что предоставим нашему компоненту Movie удаленное представление. Это означает, что когда клиент создает экземпляр EJB, реальный экземпляр создается в пространстве памяти процесса на сервере, а клиент получает ссылку на этот экземпляр.
Так как экземпляр EJB становится удаленным, клиентский код не может создавать экземпляр EJB используя оператор new Java, поскольку new всегда выделяет экземпляр в локальном адресном пространстве.
Теоретически, мы можем использовать оператор new для создания экземпляра компонента, который предоставляет локальное представление, но спецификация EJB скрывает различия удаленного/локального создания с помощью объекта фабрики. Фабрика предоставляет метод для создания экземпляра вашего компонента, а ее (фабрики) реализация, сгенерированная Контейнером, предоставляет логику для создания экземпляра объекта EJB в адресном пространстве Контейнера.
Таким образом, проблема просто сдвигается: как нам получить ссылку на объект фабрики? Ответ содержится в JNDI. Как вы помните, JNDI является нашим универсальным поисковым механизмом в EJB, а все EJB Контейнеры запускают внутреннюю Службу JNDI, чтобы клиенты могли получить доступ по сети.
Когда EJB становится доступным для Контейнера, ему также дается JNDI имя либо Поставщиком Компонент (Component Provider), либо Развертывателем (Deployer). Клиентское приложение использует это JNDI имя для нахождения фабрики объекта для этого EJB (в нашем случае это Movie), а затем использует фабрику для создания объектов Movie.
Фабрика предоставляет домашний интерфейс компонента, который вы вольны определить (не полностью произвольным образом, он должен соответствовать спецификации EJB). Тот факт, что вы сами определяете интерфейс, позволяет вам указать, какие аргументы передаются при вызове методов. Например, вы можете решить, какая информация требуется для создания экземпляра Movie. Реализация этого интерфейса будет автоматически синтезирована Контейнером, который получит данные об интерфейсе посредством Рефлексии (Reflection).
Если вы определяете домашний интерфейс для entity bean, есть дополнительные требования, которые необходимо выполнить для создания экземпляра. Вы должны найти существующий экземпляр. Это имеет смысл, если вы решили, что entity bean, по определению, постоянен в некотором хранилище данных; так что вы должны быть способны получить ссылку на один или несколько компонентов, созданных кем-либо некоторое время назад, вот что вы должны ожидать.
В домашнем интерфейсе методы, которые вы используете для создания новых EJB, называются методами создания, а методы, которые вы используете для получение экземпляров entity bean, называются методами поиска.
Ниже определен MovieHome, домашний интерфейс для Movie entity bean.
//: c18:example02:src:ejb-tier:javatheater:ejb:MovieHome.java
package
javatheater.ejb;
import
javax.ejb.*;
import
java.util.Collection;
import
java.rmi.RemoteException;
public interface
MovieHome
extends
EJBHome
{
public
Movie create
(
Integer id, String title
)
throws
RemoteException,
CreateException;
public
Movie findByPrimaryKey
(
Integer id
)
throws
RemoteException,
FinderException;
}
// /:~
Вы должны обратить внимание, что этот интерфейс наследуется от javax.ejb.EJBHome, который, в свою очередь, наследуется от java.rmi.Remote. Это сделано потому, что мы решили обеспечить наш компонент Movie удаленным представлением, и смысл здесь в том, что все методы нашего домашнего интерфейса будут соответствовать спецификации Java RMI. Практически, все методы будут иметь java.rmi.RemoteException в спецификации исключений.
Спецификация EJB описывает все синтаксические и семантические требования для методов создания и поиска. Если вы хотите узнать все детали, пожалуйста, обратитесь к спецификации EJB, которую вы можете загрузить на веб сайте Sun'а. Однако, в этой главе, в связи с ограниченностью места, я просто опишу смысл сигнатуры метода.
Первый метод интерфейса является методом создания, который клиент может использовать для создания нового экземпляра EJB Movie. Он возвращает ссылку на Movie. Movie - это компонентный интерфейс нашего компонента Movie, и я буду описывать его в следующем разделе. Первый аргумент для метода create( ) это то, что мы используем в качестве первичного ключа нашего компонента. В этом случае, мы решаем использовать Integer для уникальной идентификации экземпляра Movie. Первичный ключ будет описан более детально в одном из следующих разделов. В качестве второго аргумента мы передаем информацию, которая, в данном случае, представляет "состояние" Movie, это название фильма.
Наконец, исключения метода указывают, что метод create( ) может выбросить RemoteException и CreateException. RemoteException требуется RMI, и не должно удивлять вас. CreateException - это исключение уровня приложения, являющегося частью EJB API, и оно генерируется в том случае, если создание экземпляра провалилось внутри Контейнера. Особые причины, почему создание может закончиться неудачей, заключаются в том, если вы попробуете создать экземпляр компонента с одинаковым первичным ключом, что и у другого существующего entity bean того же типа. Этот особый случай представляется DuplicateKeyException, подклассом CreateException.
Второй метод интерфейса является методом поиска, который клиент использует для получения одного существующего экземпляра entity bean. Он называется findByPrimaryKey( ) и, как это не удивительно, он принимает Integer в качестве аргумента (Iteger является типом первичного ключа) и возвращает ссылку на Movie. Этот метод также выбрасывает исключение FinderException в том случае, если что-то прошло не так внутри Контейнера. Если возникнет специфическая проблема, когда не будет найдено значение первичного ключа, Контейнер сгенерирует ObjectNotFoundException, которое является производным классом от FinderException.
Все остальные поисковые методы, которые возвращают Java коллекцию, а не одиночную ссылку, не будут генерировать ObjectNotFoundException, а просто возвратят null. Необходимо добавить, что метод findByPrimaryKey( ) является обязательным для домашнего интерфейса entity bean.
Вы можете иметь столько поисковых методов, сколько вы хотите, для поиска постоянного экземпляра по любому критерию, который только можете придумать. Однако, все поисковые методы должны иметь имя, начинающееся с find&, должны возвращать коллекцию и должны выбрасывать FinderException. Только один поисковый метод возвращает единственный экземпляр - это findByPrimaryKey( ), так как только первичный ключ гарантирует уникальность результата - более подробно о поисковых методах я расскажу в следующей главе.
Также в домашнем интерфейсе может быть несколько методов создания и даже EJB бизнес методы (что-то похожее на статические методы Java, которые не оперируют над конкретным экземпляром). Мы поговорим об этих дополнительных методах позднее; в настоящее время давайте рассмотрим следующий интерфейс.
Компонентный интерфейс
Компонентный интерфейс содержит бизнес методы, которые клиент вызывает для определенного экземпляра EJB, например getTitle( ) для объекта Movie. По этой причине компонентный интерфейс произвольным образом определяется Bean Provader'ом, но он все равно должен удовлетворять спецификации EJB. Единственное требование такое: этот интерфейс наследуется от javax.ejb.EJBObject и все его методы выбрасывают RemoteException.
Ниже приведено определение компонентного интерфейса для entity bean Movie.
//: c18:example02:src:ejb-tier:javatheater:ejb:Movie.java
package
javatheater.ejb;
import
javax.ejb.*;
import
java.rmi.RemoteException;
/**
*
* Фильм с идентификатором(id) и названием (title).
*
*
*
* Заметьте, что в интерфейсте нет метода setId(),
*
* чтобы клиент не смог произвольно сменить
*
* первичный ключ фильма.
*
*/
public interface
Movie
extends
EJBObject
{
public
Integer getId
()
throws
RemoteException;
public
String getTitle
()
throws
RemoteException;
public
void
setTitle
(
String title
)
throws
RemoteException;
}
// /:~
Вы видите, что интерфейс просто предоставляет геттеры и сеттеры для атрибутов фильма (здесь исключительно бизнес логика для entity bean). Однако, здесь отсутствует сеттер для идентификатора (id) фильма. Причина этого состоит в том, что вам не нужно давать клиенту возможность изменять первичный ключ существующего постоянного объекта, так как при этом легко сломать ссылочную целостность вашей системы, если этот экземпляр участвует в каких-либо связях.
Первичный Ключ
На концептуальном уровне, первичный ключ в EJB - это то же самое, что и первичный ключ в реляционной базе данных. Это то, что вы используете для уникальной идентификации информационной группы: записи в базе данных или экземпляра entity bean в Контейнере EJB.
Однако, в EJB эта концепция намеренно более абстрактная, чем в реляционной базе данных, а причина этого в том, что EJB архитектура предназначена для работы со многими различными оконечными системами, включая, но не ограничиваясь, реляционную базу данных. Например, ваше оконечное постоянное хранилище может быть объектно-ориентированной базой данных, в которой нет первичного ключа, как в реляционной базе данных, а вместо этого используются объекты-идентификаторы.
Так что в EJB в качестве первичного ключа вы можете использовать объекты любого вида. Другими словами, вы можете использовать только классы, унаследованные от Object, но не примитивные типы. Вот почему мы использовали Integer вместо int в качестве первичного ключа Movie.
Первичный ключ EJB может содержать одно единственное значение или множество значений (получим составной первичный ключ). Он может также быть классом, определение которого отложено до завершения момента развертывания. Более подробно, включая требования к реализации, вы можете посмотреть в рекомендованной литературе в конце этой главы. В этой главе в качестве первичных ключей я просто использую простые стандартные классы.
Класс реализации
Теперь, когда мы определили наш домашний и компонентный интерфейсы, мы можем приступить к реализации компонента Movie. Вы можете ожидать, что мы определим класс, реализующий оба этих интерфейса, но, что достаточно интересно, вы не правы. Вместо этого вам необходимо определить класс, который реализует один из интерфейсов EntityBean, SessionBean или MessageDrivenBean, в зависимости от того, какого рода компонент вы реализуете.
Тогда кто будет реализовывать домашний и компонентный интерфейс, который вы определили? Конечно же, Контейнер. Приведенная внизу диаграмма должна помочь вам понять, как и почему Контейнер динамически синтезирует реализацию классов для вашего домашнего и компонентного интерфейса. На приведенной ниже диаграмме мы предполагаем, что клиентское приложение работает с использованием TCP/IP соединения, хотя я пренебрегаю этим на диаграмме.
Если вы внимательно взглянете на приведенную выше диаграмму, вы увидите, что вы реально не реализуете ни одного из определенных вами интерфейсов. Они все реализуются сгенерированными Контейнером классами: компонентами RMI, объектом Home и объектом EJB.
Есть очень хорошая причина для отказа Bean Provider'а, от реализации этих интерфейсов. Если вы изучите иерархию интерфейсов, вы обнаружите, что ваши интерфейсы содержат несколько методов, наследованных от базового интерфейса, которые не предназначены для ручной реализации, поскольку они являются частью инфраструктуры RMI и Movie.
Контейнер предоставляет реализацию домашнего интерфейса (объект Home), и реализацию компонентного интерфейса (объект EJB), так что все вызовы клиента будут перехвачены. Обычно Контейнер будет делегировать вызовы методов экземпляру Movie, но класс реализации Movie не реализует интерфейсы MovieHome и Movie.
Какой же интерфейс нашего экземпляра EJB предназначен для того, чтобы быть внешним, так чтобы EJB Объект мог делегировать ему клиентские вызовы? Например, метод Movie getTitle( ), определенный в интерфейсе Movie, который не реализован в экземпляре EJB Movie. Но как интерфейс определит семантический контакт между EJB объектом Movie и экземпляром Movie?
Вы должны запомнить, что реализация EJB Объекта синтезируется Контейнером. Эта реализация моделируется по определению компонентного интерфейса, который вы предоставили, и который Контейнер обнаруживает благодаря рефлексии.
Вы обязательно должны следовать конвенции имен, определенной в спецификации EJB, которая описывает, какие методы EJB объекта должны быть найдены в вашем экземпляре EJB (в нашем случае это Movie). Эта конвенция имен описывает семантические правила, которым вы должны следовать, чтобы Контейнер мог взаимодействовать с вашим экземпляром EJB. В этой главе я следую этим правилам для компонентного мета-интерфейса, не приводя их здесь. Это не Java интерфейс в строгом смысле этого слова, но это все еще набор методов, которые, на практике, определяют способ контакта между Контейнером (сгенерированным EJB объектом) и программистом (определенным EJB экземпляром).
Есть две категории методов в компонентном мета-интерфейсе: методы уведомления Контейнера и бизнес методы.
Методы уведомления Контейнера являются теми методами в классе реализации компонента, которые Контейнер вызывает в особых случаях. Их легко обнаружить, поскольку их названия все начинаются с префикса ejb<&> - например, ejbCreate( ). Исключениями из этого правила являются методы уведомления, связанные с Контекстом: setEntityContext( ), unsetEntityContext( ), setSessionContext( ) и setMessageDrivedContext( ). Я объясню каждый из методов уведомления Контейнера позднее, когда мы посмотрим исходный код класса реализации.
Бизнес методы суть просто методы, которые вы определили в интерфейсе для своего компонента. Все эти методы должны быть в вашем классе реализации EJB с абсолютно одинаковой сигнатурой, за исключением RemoteException, которое вы не должны включить в спецификацию исключений для методов.
Однако есть одна важная архитектурная вещь спецификации EJB 2.x, которая влияет на реальные бизнес-методы, которые вы реализуете. Начиная с EJB 2.0, архитектура предоставляет абстрактную модель программирования для CMP entity bean. Это значит, что реализация постоянных полей entity bean не подпадает под ответственность BenaProvider'а, а вместо этого делегируется Контейнеру. Мы ссылаемся на сгенерированные Контейнером постоянные поля, как на виртуальные поля.
Давайте возьмем, например, компонент Movie. Вы можете сказать, обнаружив геттеры и сеттеры компонентного интерфейса, что в Movie есть два свойства: id и title. Вы можете ожидать, что: a) вам нужно реализовать геттеры и сеттеры; b) что вам нужно объявить Integer и String в классе реализации для хранения значений id и title соответственно, и: c) состояние этих переменных будет синхронизироваться Контейнером с соответствующим состоянием в постоянном хранилище.
Вот что вы должны были бы ожидать в том случае, если бы вы кодировали для EJB 1.x. Однако в EJB 2.0 введена простая концепция, которая состоит в том, что Контейнер может делать работу гораздо лучше, чем разработчик, в месте реализации таких постоянных свойств. Причина такого предположения состоит в том, что Контейнер может иметь начальные знания о специфике хранения, которых нет у разработчика, так что он может лучше оптимизировать (например, если вы используете контейнер WebSphere с базой данных DB2, оба поставляются IBM).
Таким образом, в случае представления методов постоянных свойств entity bean, вы не предоставляете реализацию этих методов, поскольку Контейнер будет генерировать их за вас.
Если в вашем коде вы декларируете класс реализации вашего entity bean, как абстрактный, и он будет использоваться, как базовый класс для сгенерированного Контейнером класса (да, еще одного!), то он реализует постоянную логику. Вы просто должны в реализации вашего класса декларировать методы, представляющие постоянные свойства, как абстрактные.
Давайте посмотрим на MovieBean, класс реализации нашего Movie CMP entity bean.
//: c18:example02:src:ejb-tier:javatheater:ejb:implementation:MovieBean.java
package
javatheater.ejb.implementation;
import
javax.ejb.CreateException;
import
javax.ejb.EntityBean;
import
javax.ejb.EntityContext;
public abstract class
MovieBean
implements
EntityBean
{
// Методы уведомления Контейнера
public
Integer ejbCreate
(
Integer id, String title
)
throws
CreateException
{
if
(
id ==
null
)
throw new
CreateException
(
"Primary key cannot be null"
)
;
if
(
id.intValue
()
==
0
)
throw new
CreateException
(
"Primary key cannot be zero"
)
;
setId
(
id
)
;
setTitle
(
title
)
;
return null
;
}
public
void
ejbPostCreate
(
Integer id, String title
) {
}
public
void
ejbLoad
() {
}
public
void
ejbStore
() {
}
public
void
ejbRemove
() {
}
public
void
ejbActivate
() {
}
public
void
ejbPassivate
() {
}
public
void
setEntityContext
(
EntityContext ctx
) {
}
public
void
unsetEntityContext
() {
}
// Бизнес методы
public abstract
void
setId
(
Integer id
)
;
public abstract
String getTitle
()
;
public abstract
void
setTitle
(
String title
)
;
public abstract
Integer getId
()
;
}
// /:~
Обратите внимание, что класс является абстрактным, и что он реализует интерфейс EntityBean, но не реализует ни домашний, ни компонентный интерфейсы.
Относительно бизнес методов, последние четыре метода класса, больше нечего добавить. Они представляют постоянные свойства нашего компонента Movie CMP, так что вы просто декларируете их, как абстрактные, а контейнер предоставить их реализацию.
Методы уведомления Контейнера нуждаются в более подробном объяснении. Помните, что beanProvider определяет эти методы, но никогда не вызывает их. Все эти методы вызываются только Контейнером при разных условиях.
Вот краткое описание всех уведомляющих методов entity bean'а:
- ejbCreate( ): вызывается при создании экземпляра и назначении его клиенту, обычно при ответе на клиентский вызов метода создания в домашнем интерфейсе. Метод ejbCreate отвечает за проверку входных аргументов и инициализацию полей экземпляра посредством сеттеров. Он принимает те же аргументы, что и соответствующий метод создания в домашнем интерфейсе, но возвращаемым значением является тип первичного ключа компонента, а не компонентный интерфейс. Возвращаемое значение первичного ключа будет null, если entity bean использует CMP, если используется BMP, то будет возвращено реальное значение. Этот метод вызывается прежде, чем новый экземпляр будет сохранен в постоянном хранилище.
- ejbPostCreate( ): должен существовать соответствующий метод ejbPostCreate(&) для каждого метода ejbCreate( ) в классе. Возвращаемый тип всегда void. Этот метод вызывается после того, как новый экземпляр будет запомнен в постоянном хранилище, он используется для реализации кода, который предполагает, что entity bean'у было присвоено допустимое значение первичного ключа в постоянном хранилище - обычно для установления взаимоотношений с другими компонентами.
- ejbRemove( ): вызывается в ответ на вызов клиентом метода remove( ) домашнего или компонентного интерфейса. Выполняется до того, как entity bean будет удален из постоянного хранилища.
- ejbLoad( ): вызывается сразу после того, как состояние EJB объекта будет восстановлено из постоянного хранилища.
- ejbStore( ): вызывается прежде, чем состояние EJB объекта будет скопировано в постоянное хранилище.
- ejbActivate( ): вызывается сразу после того, как состояние EBJ объекта будет восстановлено из некоторого вторичного хранилища, обычно после того, как экземпляр был запрошен из пула.
- ejbPassivate( ): вызывается перед тем, как состояние EJB объекта будет записано в некоторое вторичное хранилище, обычно перед тем, как экземпляр будет возвращен в пул.
- ejbSetEntityContext( ): вызывается Контейнером, чтобы передать EJB объекту его Контекст. Объект Контекста предоставляет специфическую информацию времени выполнения для этого EJB объекта, например роль в безопасности, под которой будет вызываться метод. EJB объект предназначен для хранения ссылки Контекста в своем поле для последующего использования.
- unsetEntityContext( ): вызывается, чтобы проинформировать EJB объект, что переданный перед этим Контекст больше не действителен.
Повторюсь, я хочу, чтобы вы обратились к спецификации EJB или к одному из источников, перечисленных в конце этой главы, за более детальной информацией относительно методов уведомления и интерфейсе Контекста. В этом месте, той информации, которую я предоставил, должно быть достаточно для понимания того, что делает класс MovieBean.
Вы видите, что мы реализовали только один метод - ejbCreate( ), где мы проверяем id и title, и выбрасываем исключение, если они содержат недопустимые значения. После того, как аргументы будут проверены, мы используем сеттеры для инициализации значений id и title, постоянных полей нашего компонента. Все остальные методы уведомления Контейнера не нужны для выполнения каких-либо специальных действий нашего простого компонента Movie, так что мы просто предоставили методы с пустым телом.
Таким образом, выглядит обертка из Java кода, но это еще не исходный код. Нам все еще нужно снабдить наш компонент Movie дополнительными элементами.
Дескриптор Развертки
Дескриптор развертки является XML файлом, который содержит структурированную информацию относительно EJB. Эта информация включает, но не ограничивается: тип EJB; Java интерфейсы и классы, которые представляют домашний и компонентный интерфейсы; класс реализации и первичный ключ; логическое имя EJB, используемое в этом специфическом дескрипторе развертки; и постоянные поля entity bean.
Дескриптор развертки может также включать сборочную информацию относительно EJB, например логические имена, используемые в реализации компонента, которые необходимы для отображения на физические ресурсы.
Этот файл, который должен называться ejb-jar.xml, создается Bean Provider'ом, и может быть отредактирован Bean Developer'ом и/или Application Assembler'ом. Другими словами, дескриптор развертки содержит информацию конфигурирования компонента, которая может быть изменена для адаптации компонента к специфической операционной среде.
Ниже приведен дескриптор развертки для компонента Movie (я пропустил элементы xml и DOCTYPE):
<ejb-jar> <enterprise-beans> <entity> <ejb-name>Movie</ejb-name> <home>javatheater.ejb.MovieHome</home> <remote>javatheater.ejb.Movie</remote> <ejb-class> javatheater.ejb.implementation.MovieBean </ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>Movie</abstract-schema-name> <cmp-field><field-name>id</field-name>-</cmp-field> <cmp-field><field-name>title</field-name>-</cmp-field> <primkey-field>id</primkey-field> </entity> </enterprise-beans> </ejb-jar>
Структура дескриптора должна быть самообъясняющейся, так что я не буду вникать во все детали. Однако, следующие XML элементы требуют несколько слов:
- <entity>: определяет entity bean.
- <ejb-name>: Логическое имя, под которым на компонент можно будет ссылаться из других частей текущего дескриптора развертки. Не нужно путать его с JNDI именем (обсуждается далее).
- <home>: Полностью квалифицированное имя Java интерфейса, используемого в качетсве удаленного домашнего интерфейса компонента.
- <remote>: Полностью квалифицированное имя Java интерфейса, используемого в качетсве удаленного компонентного интерфейса компонента.
- <ejb-class>: Полностью квалифицированное имя Java класса, используемого в качестве класса реализации компонента.
- <persistence-type>: Контейнер для CMP, Bean для BMP.
- <prim-key-class>: Полностью квалифицированное имя Java класса, используемое в качестве первичного ключа entity bean.
- <primkey-field>: Имя поля в entity bean, которое представляет его первичный ключ.
- <cmp-field>: Имя поля, постоянство которого контролируется Контейнером. Entity bean может иметь несколько <cmp-field> элементов.
- <abstract-schema-name>: Имя, используемое для ссылки на этот entity bean в запросных выражениях в EJB Query Language (EJB-QL), который рассмотрим позднее.
Дескриптор развертывания может быть, и обычно бывает, богаче и сложнее, чем то, что вы видите в примере. Поэтому дескриптор создания на сегодняшний день редко создается вручную. Разработчик вместо этого полагается на инструмент разработки, который оказывает помощь в определении интерфейсов компонента, классах и дескрипторе развертки. Большинство из этих коммерческих инструментов предоставляют однотипную RAD среду, в которой "мастер" (wizard) предоставляет вам структурную информацию для генерации исходного кода. Как вы увидите позднее в этой главе, мы будем использовать полностью отличающийся и достаточно мощный инструмент для автоматической генерации кода.
Packaging
Теперь у вас есть все исходные файлы, которые составляют реализацию нашего компонента Movie, мы должны быть собраны в пакет для распространения.
Во-первых, вы должны скомпилировать код, так как мы должны распространять class-файлы, а не исходные файлы. Во-вторых, вы должны собрать в пакет все требуемые файлы в jar-файл, называемый ejb-jar для вашего компонента, который вы создаете с помощью утилиты jar, которая поставляется вместе с J2SE SDK.
Вы знаете, что jar-файлы могут содержать внутренние директории. Внутренняя структура файла ejb-jar должна удовлетворять следующей спецификации: все файлы Java классов начинаются с верхнего уровня, и распределяются по директориям в соответствии с именами пакетов; а дескриптор развертывания должен находиться в поддиректории с названием META-INF на верхнем уровне (все буквы должны быть в верхнем регистре):
Приведенная ниже диаграмма показывает внутреннюю организацию javatheater.jar для компонента ejb-jar Movie.
javatheater.jar
javatheater/
ejb/
MovieHome.class
Movie.class
implementation/
MovieBean.class
META-INF/
ejb-jar.xml
Этот файл является распространяемым модулем вашего компонента: он содержит интерфейсы, используемые клиентским кодом, классы реализации и конфигурационную информацию.
Ejb-jar предназначен для EJB Контейнера, запущенного на сервере, но обычно он не доступен для клиентского приложения. По этой причине вы обычно создаете другой jar-файл. Он называется client.jar и содержит только class-файлы, требующиеся клиентскому приложению для доступа к вашему удаленному компоненту.
В нашем примере есть только два class-файла, которые необходимы клиенту - это определение интерфейсов MovieHome и Movie. Хотя есть другой архитектурный кусок, который понадобиться удаленному клиенту - это RMI прокси. Следующий раздел о развертывании осветит этот кусок.
Развертывание
Развертывание является актом создания EJB компонента, поддерживаемого определенной реализацией EJB Контейнера, которая использует специфичные для приложения внутренние и внешние ресурсы. Развертывание - это неотъемлемая часть работы специфичная для Контейнера и приложения, так как оно вовлекает подстройку EJB Контейнера и компонента (например, ассоциацию общего компонента с предопределенной, установленной базой данных).
Даже для развертывания нашего простого компонента Movie вам понадобится позаботиться, как минимум, о: a) присвоении JNDI имени вашему компоненту, чтобы клиент мог найти его; b) ассоциации Movie entity bean с физической базой данных, и c) необходимо обеспечить удаленного клиента RMI прокси для интерфейсов MovieHome и Movie.
Концептуально, развертывание одинаково для всех платформ, но практика развертывания может значительно отличаться в зависимости от используемого Контейнера. Все коммерческие EJB контейнеры поставляются с некоторой графической утилитой, которую вы используете для сборки вашего ejb-jar и client.jar, чтобы передать ejb-jar вашему контейнеру, для подстройки вашего EJB и ассоциации его с системными ресурсами, такими как база данных, очередь сообщений и так далее.
Пожалуйста, обратите внимание что, так как мы используем JBoss в качестве EJB Контейнера, этот раздел развертывания специфичен для JBoss, но вся информация, которую вы увидите, является не зависящей от Контейнера.
JBoss упрощает процесс развертывания: все, что вам нужно сделать, это разместить ваш ejb-jar в специальном поддиректории установленного пакета JBoss, что будет описано позднее. JBoss определит наличие новых файлов (даже когда Контейнер запущен), просмотрит их и проверит. Если все пройдет без ошибок, новый компонент будет развернут и клиент будет способен использовать его.
Однако, нет GUI инструмента, поставляемого с JBoss, который поможет вам в создании ejb-jar'а, развертывании и конфигурировании вашего enterprise bean. Вы предоставите всю необходимую информацию в дескрипторе развертывания и/или в некоторых специфичных для JBoss XML-файлах.
Давайте теперь взглянем на то, что вам нужно сделать в JBoss для правильной установки EJB имени в JNDI, базы данных и RMI прокси.
JNDI имя: по умолчанию, JBoss создаст JNDI имя для вашего EJB, используя то же имя, что и ejb-name в дескрипторе развертывания (в нашем случае это "Movie"). Однако, в реальном приложении вам может понадобиться указать ваше собственное JNDI имя для Movie bean, возможно, для предотвращения конфликта с другими компонентами. Чтобы сделать это, вы должны предоставить XML файл с именем jboss.xml, который должен быть в том же директории, что и дескриптор развертывания, и использовать его для связывания JNDI с желаемым именем ejb-name компонента. Ниже приведен пример файла jboss.xml (обратите внимание, что JNDI имя отличается от ejb-name).
<?xml version="1.0" encoding="UTF-8"?> <jboss> <enterprise-beans> <entity> <ejb-name>Movie</ejb-name> <jndi-name>javatheater/Movie</jndi-name> </entity> </enterprise-beans> </jboss>
База данных: JBoss поставляется по умолчанию с базой данных, которая стартует при запуске Контейнера. В отсутствии другой информации развертывания, все CMP entity bean будут сделаны постоянными в этой базе данных. JBoss также будет создавать таблицы в базе данных, именуемые так же, как и ваши entity bean, в которых каждый экземпляр entity bean является записью в соответствующей таблице. По умолчанию JBoss использует базу данных HypersonicSQL, которая поставляется в одном пакете с JBoss. Вы можете управлять данными в поставляемой по умолчанию базе данных с помощью Hypersonic Database Manager (более детально смотрите в инструкции по установке). Однако если вы используете JBoss в качестве реального продукта, возможно, вы захотите использовать другую базу данных, и/или предоставить свой собственный механизм отображения entity/таблица (пожалуйста, обратитесь к инструкции для JBoss за более детальной информацией). Также, пожалуйста, обратите внимание, что другие EJB контейнеры требуют, чтобы вы создали схему базы данных заблаговременно, и отобразили ваши entity bean'ы и поля на таблицы и колонки.
RMI прокси: Большинство Контейнеров EJB генерируют класс реализации для RMI прокси при развертывании entity bean с помощью графического инструмента Контейнера. Class-файлы для RMI прокси будут включены в клиентский jar-файл, который вы сделаете доступным для клиентского приложения. JBoss не требует этого шага, так как он синтезирует классы RMI прокси во время выполнения и автоматически посылает class-файлы клиенту по сети. Так что, хотя при использовании JBoss есть RMI прокси, нет необходимости делать какие-либо class-файлы доступными для клиента.
Полная сборка всего
Как вы можете себе представить, процесс сбора всех частей вместе, чтобы закончить правильную развертку компонента, очень утомителен и подвержен ошибкам. Определенно, вы не хотите вручную компилировать все требуемые файлы, создавать ejb-jar и клиентский jar, и производить развертку.
Как я и упоминал, все коммерческие EJB Контейнеры поставляются с некоторыми инструментами для помощи вам в этом процессе. JBoss не сопровождается такими инструментами, но мы все равно можем автоматизировать процесс сборки и развертывания с помощью Ant. Все пример в исходном коде для этой главы поставляются со скриптом для Ant, который вы можете использовать для компиляции, развертывания, создания jar'ов и тому подобное - просто запустите Ant скрипт без аргументов, чтобы получить список возможных целей.
Еще одна вещь, которую делает JBoss, когда вы отдаете ему ejb-jar - это проверка этого файла. То есть, JBoss проверяет содержимое XML файлов и class-файлы в ejb-jar, удостоверяясь, что они соответствуют синтаксическим и семантическим правилам EJB спецификации. Но проверщик JBoss является инструментом, который вы можете также запустить отдельно, и это именно то, что делает наш Ant скрипт в процессе построения и развертывания компоненты javatheater.
И, наконец, отсутствие GUI инструмента для развертывания компонент только помогает, поскольку вы получаете большее управление при программном построении и развертывании компонент.