Компонентный подход в программировании

Компоненты данных и сеансовые компоненты


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

  • Удаленный интерфейс (remote interface).

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

    Удаленный интерфейс должен наследовать интерфейс javax.ejb.EJBObject (в свою очередь, наследующий java.rmi.Remote).

    Для компонента данных он определяет набор свойств (в смысле JavaBeans, т.е. пар методов Type getName()/void setName(Type)), служащих для работы с отдельными полями данных или компонентами, связанными с этим компонентом по ссылкам. Это могут быть и часто используемые дополнительные операции, как-то выражающиеся через операции с отдельными полями данных, в том числе и вычислимые свойства. Например, для книги в базе данных приложения хранится набор ссылок на данные об ее авторах, а число авторов может быть часто используемым свойством книги, вычисляемым по этому набору ссылок.

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

  • Локальный интерфейс (local interface).

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

    Локальный интерфейс должен наследовать интерфейсу javax.ejb.EJBLocalObject.

  • Удаленный исходный интерфейс (remote home interface).

    Исходные интерфейсы служат для поиска и создания компонентов. Такой интерфейс может декларировать метод поиска компонента данных по значению его первичного ключа findByPrimaryKey(…) и метод создания такого компонента с указанным значением первичного ключа create(…). Могут быть также определены методы, создающие компонент по набору его данных или возвращающие коллекцию компонентов, данные которых соответствуют аргументам такого метода. Например, метод, создающий компонент, который представляет книгу с данным названием, createByTitle(String title), или метод, находящий все книги с данным набором авторов Collection findByAuthors(Collection authors).

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

    Такой интерфейс должен наследовать интерфейсу javax.ejb.EJBHome (являющемуся наследником java.rmi.Remote).

  • Локальный исходный интерфейс (local home interface).

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



    Должен наследовать интерфейсу javax.ejb.EJBLocalHome.

  • Класс компонента (bean class).

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

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


    Свойства, соответствующие полям хранимых данных или ссылкам на другие компоненты данных, должны быть определены в виде абстрактных пар методов getName()/setName(). В этом случае EJB-контейнер может взять на себя управление синхронизацией их значений с базой данных. Вычислимые свойства, значения которых не хранятся в базе данных, реализуются в виде пар неабстрактных методов.

    Для сеансовых компонентов класс компонента должен быть неабстрактным классом, реализующим интерфейс javax.ejb.SessionBean и все методы удаленного и локального интерфейсов.

    Кроме того, класс компонента может (а иногда и должен) реализовывать некоторые методы, которые вызываются EJB-контейнером при переходе между различными этапами жизненного цикла компонента.

    Например, при инициализации экземпляра компонента всегда вызывается метод ejbCreate(). Для компонентов данных он принимает на вход и возвращает значение типа первичного ключа компонента. Если первичный ключ — составной, он должен принимать на вход значения отдельных его элементов. Такой метод для компонента данных должен возвращать null и всегда должен быть реализован в классе компонента. Для сеансовых компонентов он имеет тип результата void, а на вход принимает какие-то параметры, служащие для инициализации экземпляра компонента. Для каждого метода исходных интерфейсов с именем createSomeSuffix(…) в классе компонента должен быть реализован метод ejbCreateSomeSuffix(…) с теми же типами параметров. Для компонентов данных все такие методы возвращают значение типа первичного ключа, для сеансовых — void.

    Другие методы жизненного цикла компонента, которые можно перегружать в классе компонента, декларированы в базовых интерфейсах компонентов соответствующего вида (javax.ejb.EntityBean или javax.ejb.SessionBean). Это, например, ejbActivate() и ejbPassivate(), вызываемые при активизации и деактивизации экземпляра компонента; ejbRemove(), вызываемый перед удалением экземпляра компонента из памяти; для компонентов данных — ejbStore() и ejbLoad(), вызываемые при сохранении данных экземпляра в базу приложения или при их загрузке оттуда.



    Схема жизненного цикла компонента данных показана на рис. 14.2.

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

    Жизненный цикл сеансового компонента различается в зависимости от того, поддерживает ли компонент состояние сеанса или нет.


    Рис. 14.2.  Жизненный цикл EJB компонента данных

    Схема жизненного цикла сеансового компонента с состоянием показана на рис. 14.3. Отличие от жизненного цикла компонента данных единственное — метод ejbCreate() сразу переводит компонент в активное состояние.


    увеличить изображение
    Рис. 14.3.  Жизненный цикл сеансового компонента с состоянием

    Жизненный цикл сеансового компонента без состояния гораздо проще. Его схема представлена на рис. 14.4.

  • Класс первичного ключа (primary key class).

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

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

    Чаще всего это библиотечный класс, например, String или Integer. Пользовательский класс необходим, если первичный ключ составной, т.е. состоит из нескольких значений простых типов данных. В таком классе должен быть определен конструктор без параметров и правильно перегружены методы equals() и hashCode(), чтобы EJB-контейнер мог корректно управлять коллекциями экземпляров компонентов с такими первичными ключами. Такой класс также должен реализовывать интерфейс java.io.Serializable.


    Рис. 14.4.  Жизненный цикл сеансового компонента без состояния



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



В рамках этой схемы, состоящей из таблиц, где хранятся данные книг и организаций-издателей, каждая книга связана с одним и только одним издателем, а каждый издатель может иметь ссылки на некоторое множество книг (возможно, пустое). Каждая таблица имеет поле ID, являющееся первичным ключом. Таблица Book имеет поле PublisherID, содержащее значение ключа записи об издателе данной книги.


Рис. 14.5.  Пример схемы БД

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

package ru.msu.cmc.prtech.examples;

import java.rmi.RemoteException; import java.util.Collection; import javax.ejb.EJBObject;

public interface PublisherRemote extends EJBObject { public String getTitle () throws RemoteException; public void setTitle (String title) throws RemoteException;

public Collection getBooks ()throws RemoteException; public void setBooks (Collection books) throws RemoteException;

public void addBook (String title, String isbn) throws RemoteException; public void removeBook (String title, String isbn) throws RemoteException; }

package ru.msu.cmc.prtech.examples;

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

public interface BookRemote extends EJBObject { public String getTitle () throws RemoteException; public void setTitle (String title) throws RemoteException;

public String getISBN () throws RemoteException; public void setISBN (String isbn) throws RemoteException;

public PublisherRemote getPublisher () throws RemoteException; public void setPublisher (PublisherRemote publisher) throws RemoteException; }

Пример 14.1.

Примеры кода удаленных исходных интерфейсов.

package ru.msu.cmc.prtech.examples;

import java.rmi.RemoteException;

import javax.ejb.CreateException; import javax.ejb.EJBHome; import javax.ejb.FinderException;

public interface PublisherHomeRemote extends EJBHome { public PublisherRemote create (Integer id) throws CreateException, RemoteException;

public PublisherRemote findByPK (Integer id) throws FinderException, RemoteException; }



package ru.msu.cmc.prtech.examples;

import java.rmi.RemoteException;

import javax.ejb.CreateException; import javax.ejb.EJBHome; import javax.ejb.FinderException;

public interface BookHomeRemote extends EJBHome { public BookRemote create (Integer id) throws CreateException, RemoteException;

public BookRemote createBook (String title, String isbn) throws CreateException, RemoteException;

public BookRemote findByPK (Integer id) throws FinderException, RemoteException; }

Пример 14.2.

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

package ru.msu.cmc.prtech.examples;

import java.util.Collection; import java.util.Iterator;

import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EntityBean; import javax.naming.InitialContext; import javax.naming.NamingException;

public abstract class PublisherBean implements EntityBean { public Integer ejbCreate (Integer pk) { setId(pk); return null; }

public void ejbPostCreate (Integer pk) { }

public abstract Integer getId (); public abstract void setId (Integer pk);

public abstract String getTitle (); public abstract void setTitle (String title);

public abstract Collection getBooks (); public abstract void setBooks (Collection books);

public void addBook (String title, String isbn) { try { InitialContext context = new InitialContext(); BookHomeRemote bookHome = (BookHomeRemote)context.lookup("BookHomeRemote"); BookRemote book = bookHome.createBook(title, isbn);

Collection books = getBooks(); books.add(book); } catch (NamingException e) { e.printStackTrace(); } catch (CreateException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } }

public void removeBook (String title, String isbn) { Collection books = getBooks(); Iterator it = books.iterator();

try { while(it.hasNext()) { BookRemote book = (BookRemote)it.next(); if( book.getTitle().equals(title) && book.getISBN().equals(isbn) ) { it.remove(); break; } } } catch (RemoteException e) { e.printStackTrace(); } } }

package ru.msu.cmc.prtech.examples;

import javax.ejb.EntityBean;

public abstract class BookBean implements EntityBean { public Integer ejbCreate (Integer pk) { setId(pk); return null; }

public void ejbPostCreate (Integer pk) { }

public Integer ejbCreateBook (String title, String isbn) { setTitle(title); setISBN(isbn); return null; }

public void ejbPostCreateBook (String title, String isbn) { }

public abstract Integer getId (); public abstract void setId (Integer pk);

public abstract String getTitle (); public abstract void setTitle (String title);

public abstract String getISBN (); public abstract void setISBN (String isbn);

public abstract PublisherRemote getOrganization (); public abstract void setOrganization (PublisherRemote pr); }

Пример 14.3.


Содержание раздела