EJB API - это не только платформа Java2 Enterprise Edition. Это горадно больше: Java Transaction API (JTA), Java Messaging System API (JMS), Сервлеты, JavaServer Pages (JSP) и Java Database Connectivity (JDBC) вот несколько названий. Прежде чем мы приступим к рассмотрению реализации и использованию EJB, нам необходимо коротко рассмотреть пару фундаментальных API: Java Naming and Directory Interface (JNDI) и актуальный EJB API.
JNDI
JNDI - это абстрактный API над различными службами именования, такими как RMI Naming Service или COS Naming Service в CORBA, или служба директорий аналогичная Lightweight Directory Access Protocol (LDAP). Служба имен и служба каталогов концептуально это не одно и то же, но, как я уже говорил ранее, здесь не место для обсуждения различий. То, что мы сделаем - это просто взглянем на то, что в них общего.
Все службы именования и директорий позволяют вам заполнять и искать репозитории некоторого рода. Более подробно, они позволяют вам ассоциировать (или связывает, если хотите) имя с объектом, а затем искать этот объект (или рассматривать его), используя это имя. По аналогии, он может использоваться как директорий белых страниц, когда физический телефон, на который вы хотите перевести звонки (уникально идентифицируемый номером), ассоциируется с именем клиента. В качестве более приближенного к Java примера можно привести RMI: вы можете зарегистрировать объект с именем (строка) в RMI Naming Service, так что другие приложения могут производить поиск этого имени в службе. Это в точности совпадает с тем, что вы можете делать с COS Naming Service. Таким образом, почему JNDI является достаточно удобной абстракцией: он предоставляет вам однообразный интерфейс к различным службам именования и директорий, точно так же, как вы используете JDBC для доступа к различным базам данных.
Как и JDBC, JNDI является абстрактным API, и вы можете найти в JNDI пакете javax.naming только Java интерфейсы. Действительная реализация этих интерфейсов должна быть предоставлена некоторым поставщиком, который хочет предоставить поддержку JNDI для своей службы. Для действительного использования этой службы вам необходим Поставщик Услуг (Service Provider) JNDI - точно так же, как вам необходим соответствующий JDBC драйвер для доступа к определенной базе данных.
JDK1.4 поставляется с четырьмя стандартными Поставщиками Служб JNDI, которые предоставляют доступ к RMI, DNS, COS Naming Service и LDAP. Существует также дополнительный Поставщик Службы JNDI, который вы можете получить с вебсайта Sun, который предоставляет JNDI просмотр вашей локальной файловой системы (так что вы можете искать файлы и директории, используя JNDI).
JNDI поддерживает концепцию пространства имен, которое является логическим пространством, в котором могут определяться имена. Два одинаковых имени, определенных в разных пространствах имен не будут являться причиной неоднозначности или коллизии имен, а пространства имен могут быть вложенные. Эту концепцию вы можете видеть в нескольких различных ситуациях - ваша локальная файловая система использует вложенное пространство имен (директории), DNS Интернета использует вложенное пространство имен (домены и субдомены).
В JNDI пространство имен представлено интерфейсом Context, наиболее часто используемым элементом API. Существуют разные классы, реализующие интерфейс Context, в зависимости от реальной службы, к которой вы обращаетесь посредством JNDI. Интерфейс Context имеет методы для связывания имени и объекта, для удаления связывания, для переименования, для создания и удаления подконтекста, для поиска имен, для получения списка имен и так далее. Вы также должны заметить что, так как контекст в Java является объектом, он может быть зарегистрирован в другом контексте под своим собственным именем. Другими словами, мы можем получить вложенный субконтекст, начав с родительского контекста, и создав связывание между именем субконтекста и вложенным объектом Context.
Другая фундаментальная концепция в JNDI API состоит в том, что независимо от реально используемой вами службы, для того, чтобы получить возможность создать связывание, выполнить поиск и тому подобное, вам нужно начать с некоторого "корневого" контекста. Класс InitialContext - это то, что вы используете, чтобы получить экземпляр Контекста, которые представляет родителя всех субконтекстов, к которым вы можете получить доступ.
Хотя определенные операции в JNDI применимы только к определенному Поставщику Службы, концепция распространяется так далеко, что становится общей и широко применимой. Рассмотрим пример. Приведенный ниже код показывает, как получить список всех имен, зарегистрированных в корне используемой вами службы. Обратите внимание, что конструктор InitialContext получает в качестве аргумента тип Property. Я объясню это немного позже. Сейчас же просто взглянем на приведенный ниже код.
Context context =
new
InitialContext
(
props
)
;
Enumeration names = context.list
(
""
)
;
while
(
names.hasMoreElements
())
System.out.println
(
names.nextElement
())
;
Вы видите, что это достаточно просто. Мы создаем объект Context, который представляет корень вашей службы именования или директории, получаете перечисление всех элементов этого контекста (пустая строка в вызове метода list( ) означает, что вы не ищете определенное имя), затем перебираете и печатаете все элементы из перечисления.
Абстракция и модель программирования достаточно проста. Нас должен волновать вопрос: какую службу именования или директорий мы используем? Ответ содержится в контейнере параметров, который мы передаем в конструктор InitialContext( ). JNDI API полностью определяет несколько стандартных имен параметров (декларированных, как константные Строки и интерфейсе Context), а значения этих параметров определяют природу службы, которую вы будете использовать. Из этих параметров два являются фундаментальными: INITIAL_CONTEXT_FACTORY и PROVIDER_URL. Первая из них указывает класс, который будет производить экземпляр JNDI Context. Если вам нужен DNS, например, вы будете указывать класс, который производит Context, способный взаимодействовать с DNS сервером. Второй параметр - это расположение службы, следовательно, это URL. Формат этого URL зависит от определенной службы.
Ниже приведен законченный пример, который использует JNDI для просмотра содержимого корня контекста в DNS сервере (возможно, вам понадобиться использовать другие IP адреса для вашего DNS, в зависимости от конфигурации вашей сети).
//: c18:jndi:SimpleJNDI.java
import
javax.naming.*;
import
java.util.*;
public class
SimpleJNDI
{
public static
void
main
(
String
[]
args
)
throws
Exception
{
Properties props =
new
Properties
()
;
props.put
(
Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.dns.DnsContextFactory"
)
;
props.put
(
Context.PROVIDER_URL,
"dns://207.155.183.72"
)
;
Context context =
new
InitialContext
(
props
)
;
Enumeration names = context.list
(
""
)
;
while
(
names.hasMoreElements
())
System.out.println
(
names.nextElement
())
;
}
}
// /:~
Если вы хотите использовать другой DNS или полностью отличный тип службы,
в большинстве случаев вы должны просто поместить отличную информацию в контейнер
параметров, а оставшийся код останется без изменения.
Существует другой способ предоставления значений параметров JNDI. Вы можете использовать внешний файл параметров, который является текстовым файлом, который ассоциирует названия параметров с их значениями. Этот файл должен называться jndi.properties, и он должен быть расположен в CLASSPATH вашего JNDI приложения. Отличие состоит в том, что вы не можете использовать константы названия параметров, определенных в интерфейсе Context, а вместо этого вы должны использовать их "строковые" значения (которые вы можете найти в стандартной документации по JDK). Содержимое нашего файла jndi.properties будет следующим:
java.naming.factory.initial=com.sun.jndi.dns.DnsContextFactory
java.naming.provider.url=dns:
//207.155.183.72
Если вы выберете этот подход, вам больше ненужно будет передавать контейнер
параметров в конструктор InitialContext. Другими словами, то, что вы поместите
в файл jndi.properties, то вы и установите для JNDI в качестве значений по
умолчанию.
Последнее, и наиболее важное, что я хотел бы рассказать о JNDI, это как ищутся имена. Как я упоминал, существует метод lookup( ) в интерфейсе Context. В следующем примере мы предполагаем, что существует внешний файл jndi.properties, в котором определено, какую службу мы просматриваем.
//: c18:jndi:SimpleJNDI2.java
import
javax.naming.*;
public class
SimpleJNDI2
{
public static
void
main
(
String
[]
args
)
throws
Exception
{
System.out.println
(
new
InitialContext
()
.lookup
(
args
[
0
]))
;
}
}
// /:~
Мы создали новый InitialContext и вызвали метод lookup( ) для него, передав
Строку в качестве аргумента имени. Так как метод lookup( ) предназначен для
работы с различными службами, он возвращает общий класс Object. Однако, реальный
тип времени выполнения, возвращаемый из lookup( ) определяется специфичной
службой, которую вы используете. Ради этого примера мы игнорируем тип и просто
печатаем результат.
В программировании EJB мы используем JNDI для нахождения всех видов ресурсов, включая EJB, пул соединений с базой данных, информацию об окружении и многое другое. Другими словами, из окна EJB Контейнера вы можете взглянуть на остальной мир посредством JNDI. Клиентское приложение тоже использует JNDI для получения соединения с фабрикой EJB (подробнее об этом позже).
Таким образом, если мы используем JNDI с EJB, что такое Поставщик Услуг JNDI? Ответ состоит в том, что EJB Контейнер работает и выставляет свою собственную службу JNDI, специализированную для работы с ресурсами, управляемыми Контейнером. Другими словами, эта служба EJB JNDI предназначена, чтобы позволить клиентам и Enterprise JavaBean'ам находить ресурсы по JNDI именам. Помните, когда вы запускаете ваш EJB Контейнер, вы также неявно запускаете EJB JNDI службу, которая доступна вам через стандартный JNDI API.
EJB
Другой API, на который нам нужно взглянуть, это сам EJB API. Он определен в пакете javax.ejb, который, что само по себе интересно, состоит только из интерфейсов и нескольких классов исключений.
Приведенная ниже диаграмма показывает главные интерфейсы пакета. Три из них представляют разные EJB типы: EntityBean, SessionBean и MessageDrivenBean. В зависимости от того, EJB какого типа вы кодируете, ваш класс должен реализовывать один из этих интерфейсов. Существуют также интерфейсы EJBHome и EJBObject, который вы используете как базовый интерфейс, когда определяете собственное представление компонента (если оно вам нужно) - по этой причине EJBHome и EJBObject наследуются от интерфейса Remote RMI. Оставшиеся два интерфейса, EJBLocalHome и EJBLocalObject, вы используете для предоставления локального представления вашего компонента (опять таки, предполагается, что вам нужно это локальное представление). В следующем разделе мы увидим, как реальный код использует эти интерфейсы. До этого момента просто держите в уме этот дизайн.
Другой набор элементов, который нужен нам для короткого обзора, это классы исключений. Вы можете видеть на приведенной ниже диаграмме наиболее часто встречающиеся исключения (их существует немного больше в пакете, пожалуйста, обратитесь к документации по JDK за более детальной информацией).
Как вы можете видеть, есть две главных категории: исключения, наследуемые от java.lang.Exception напрямую, и другие исключения, наследуемые от java.lang.RuntimeException. Или, в терминах Java, проверяемые и не проверяемые исключения, соответственно.
В EJB все проверяемые исключения относятся к исключениям уровня приложения. Таким образом, они разбросаны по разным уровням распределенного приложения, и когда они генерятся EJB, они пересекают сеть распределенных уровней и передаются удаленному клиенту.
Фактически, использование проверяемых исключений EJB указано в определениях EJB интерфейсов, используемых клиентом, что предопределяется спецификацией. Например, CreateException должно быть указано в спецификации метода, который клиент использует для создания экземпляра EJB. Аналогично, FinderException должно быть указано в спецификации метода, который клиент использует для нахождения существующего, постоянного объекта EJB. А RemoveException должно указываться в спецификации метода, который удаляет EJB объект.
Не проверяемые исключения используются во внутренней реализации EJB. Другими словами, если какая-то попытка вашего EJB провалилась, вы выбрасываете EJBException или какое-либо наследуемое от него исключение. Причина, по которой EJBException не проверяется состоит в том, что в большинстве случаев они представляют некоторые ошибки, которые приходят из одной подсистемы в другую внутри EJB Контейнера. Даже если вы попытаетесь перехватить эти исключения, вы не сможете сделать это, так как EJB существует только внутри EJB Контейнера, который управляет экземплярами и заботится обо всем, что с ними происходит. Вот пример: у вас есть реализация EJB, который обращается к базе данных и в определенный момент база данных генерирует SQLException. Бессмысленно возвращать это исключение удаленному клиенту, поскольку клиент не может знать, что делать с ним. С другой стороны, вы можете захотеть прервать текущую операцию и известить Контейнер, что что-то пошло не так, таким образом должна существовать какое-либо корректное действие, подобное откату текущей транзакции.
Вы не можете просто позволить SQLException'у остаться в Контейнере, поскольку Контейнер имеет общий способ получения исключений уровня системы - так что он может постоянно принимать исключения от различных, даже будущих, подсистем. Что вы должны сделать - это обернуть проверяемое SQLException в не проверяемое EJBException, используя конструктор, который принимает java.lang.Exception, а затем вы выбросите EJBException.
Все исключения, которые поступают наружу из вашего EJB объекта, перехватываются Контейнером. Действия, предпринимаемые в этом случае Контейнером, могут сильно зависеть от условий, но в общем Контейнер автоматически откатит текущую транзакцию (если она есть) и передаст исключение клиенту. Если исключение, сгенерированое внутри вашего EJB, было типа EJBException, клиент получит общее RMI RemoteException, что проинформирует его, что метод окончился неудачей. Если исключение, сгенерированое в EJB, было исключением уровня приложения, таким как FinderException, Контейнер передаст это исключение клиенту.
← | EJB Роли | EJB Контейнер изнутри | → |