- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF99:Java EE
Материал из Linuxformat.
Java EE |
---|
Содержание |
Экстракт кофе
ЧАСТЬ 11 Наша серия, увы, подходит к концу, но на десерт Александр Бабаев припас нечто особенное — ароматные зерна Enterprise Java Beans
Все предыдущие статьи содержали огромное количество полезной информации. Мы рассмотрели алгоритмы, структуры, принципы работы, множество библиотек, так или иначе связанных с созданием серьезных приложений на Java. И пусть вас не смущает то, что примеры были простые. В основе больших, серьезных, важных и сложных приложений лежит именно то, о чем мы говорили.
Теперь вы уже готовы узнать, что обозначает загадочная аббревиатура EJB3. Расшифровывается она как «Enterprise Java Beans, version 3» и содержит внутри огромный мир, окошко в который мы сегодня приоткроем.
Почему именно EJB3?
Дело в том, что это стандарт. Тройка в названии указывает, что были еще версии один и два, также была версия два-точка-один… Но только текущая, третья версия действительно является великолепным инструментов для борьбы с хаосом корпоративных систем. Все предыдущие версии строились по такому принципу: «Мы (Sun/IBM/ и пр.) тут собрались, посовещались и решили, что вы (разработчики) будете использовать вот это… (EJB2.1)». Было круто, но разработчики хоть и использовали, но плевались (странно, правда?). EJB3 создавалась иначе. Те же люди собрались, посмотрели на то, как работают программисты, какие есть библиотеки, удачные решения, технологии… И, выбрав лучшее и добавив свой (огромный) опыт, выдали третью версию спецификации.
Получившийся стандарт хорош. Хорош и простотой (можно обойтись без специфических XML, которых в предыдущих вариантах были сотни), и привычностью (Hibernate использовали? Нет? Ну, это теперь reference implementation, то есть стандартная реализация для JPA1, части EJB3), и заменой старых неудобных частей на новые, «блестящие и шелковистые».
Для чего оно?
Большие системы никогда не создаются в одиночку. Существуют люди, которые зовутся архитекторами: они придумывают систему. Система обычно состоит из блоков. Блоки, в свою очередь, состоят из других блоков… Блоки отвечают за совершенно разные вещи: за хранение бизнес-объектов, за просчет алгоритмов, за управление элементами системы, и так далее. Разрабатываются эти составные части системы разными людьми, часто совершенно не связанными друг с другом.
В таких условиях нужен стандарт, который обеспечивал бы, чтобы блок, написанный в Индии, и блок, написанный в России, заработали вместе. Можно этот стандарт каждый раз придумывать заново, но на это никогда нет времени. Лучше использовать EJB3.
Общая структура EJB-проекта
Упрощенная схема проекта приведена на Рис. 1.
Это так называемая трехзвенная структура. Она проста, и для более сложных систем может разрастись до четырех-, пяти-, n-звенной. Серверов приложений может быть кластер, СУБД тоже может представлять кластер с распределением нагрузки и резервированием, и так далее.
В качестве СУБД сгодится практически любая: у нас это будет MySQL, но, грамотно используя JPA (об этом чуть дальше), СУБД можно сменить хотя и за ненулевое, но вполне приемлемое время. Клиенты могут быть либо тонкими (браузер), либо полноценными приложениями (и даже не обязательно на Java).
А вот про сервер приложений поговорим подробнее. Это именно то место, где соединяются те самые блоки, написанные в Индии, России, Китае, США и так далее. Чтобы все они работали вместе, написаны специальные приложения, которые обеспечивают связь с СУБД, предоставляют огромное количество стандартных API для работы блоков, дают возможность эти блоки выгружать и загружать без перезапуска сервера, контролировать их исполнение… и много чего еще. Сервера приложений есть как коммерческие (IBM WebSphere, BEA WebLogic), так и бесплатные (GlassFish, JBoss, IBM WebSphere Community Edition). Мы посмотрим поближе на JBoss, который, помимо прочего, продвигается Red Hat и распространяется в составе Red Hat Enterprise Linux.
JBoss
Итак, сервер приложений. Он состоит из огромного количества блоков, интегрированных вместе. Некоторые нам уже так или иначе знакомы: например, Tomcat или подсистема RMI-подключений. Но есть и множество других: Hibernate, который отвечает за «связь с СУБД», система кэширования, кластеризации, распространения сообщений, транзакций (уровня приложения), …
Короче, система большая. Мы рассмотрим относительно небольшую часть, которая работает с EJB3, задержимся на Hibernate (и JPA) и немного поглядим на транзакции (JTA). Все рассмотреть, конечно, не успеем, поэтому после статьи приведен список литературы — выберите книжку по вкусу, чтобы заняться подробным изучением.
Так что же такое EJB?
Ну, во-первых, это технология, это уже понятно. Во-вторых, это Enterprise Java Bean (без «s») — то самое «зерно», то есть блок, из множества которых строится приложение. Блок стандартным образом упаковывается, и помещается в каталог JBoss, после чего (если все сделано правильно), JBoss подключает блок к системе.
То есть блок — это бин (bean). И наоборот. Бин в простейшем случае — это всего лишь класс, описанный и объявленный специальным образом. Например, пусть он считает площадь круга.
@Remote public interface Calculator { public double getSquare(double aRadius); }
Пока это только объявление, то есть интерфейс — то, что видит клиент. Причем от обычного интерфейса он отличается только словом @Remote (так называемой аннотацией). Оно обозначает, что клиенты, которые используют этот бин, могут находиться как на серверном компьютере, так и на любых других узлах сети. Если доступ извне локального компьютера не предполагается, то можно использовать аннотацию @Local, или вообще ничего не писать, так как интерфейсы считаются локальными по умолчанию. Умолчания — это одно из огромных достоинств EJB3, так как не нужно прописывать банальности, которые в крупных проектах превращаются в мегабайты ненужного кода.
Но где же считается сама площадь? В классе, который реализует интерфейс:
@Stateless public class CalculatorBean implements Calculator { public double getSquare(double aRadius) { return Math.PI*aRadius*aRadius; } }
Тут опять появилась аннотация, которая обозначает, что этот бин (да, это настоящий Enterprise Java Bean; да, больше — кроме упаковки в jar — не нужно вообще ничего) является Stateless-бином, то есть не сохраняет состояние в процессе работы. Клиент вызывает метод, метод выполняется, и следующий метод ничего не будет знать о предыдущем исполнении.
Теперь давайте посмотрим на клиент и, наконец, поставим JBoss, запустив наш бин.
Клиент будет немного «не в стиле EJB3». Правильный EJB3-клиент выглядит примерно так:
public class CalculatorClient { @EJB private Calculator _calculator; private void start() { System.out.println(“Square for circle with radius 2.345 = “ + _calculator.getSquare(2.345)); } public static void main(String[] args) { new CalculatorClient().start(); } }
Но JBoss (который мы будем использовать для демонстрации) такого стиля (пока) не понимает, поэтому выйдет чуть-чуть подлиннее:
public class CalculatorClient { private Calculator _calculator; private void start() throws NamingException { _calculator = (Calculator) getInitialContext(). lookup(“CalculatorBean/remote”); System.out.println(“Square for circle with radius 2.345 = “ + _calculator.getSquare(2.345)); } private InitialContext getInitialContext() throws NamingException { Properties properties = new Properties(); properties.put(Context.INITIAL_CONTEXT_FACTORY, “org.jnp.interfaces.NamingContextFactory”); properties.put(Context.PROVIDER_URL, “localhost:1099”); return new InitialContext(properties); } public static void main(String[] args) throws NamingException { new CalculatorClient().start(); } }
Разница, как можно заметить, в отсутствии аннотации @EJB. А заменяет ее тот код, который мы написали (получение контекста, а из него — ссылки на нужный нам бин).
Нужно сделать небольшое отступление. При поиске объекта мы использовали его имя: «CalculatorBean/remote». Почему именно так? И как вообще ищутся объекты? Для этого используется так называемая служба имен Java (JNDI) — нечто похожее на реестр RMI, который использовался для регистрации и поиска RMI-серверов. А имя присваивается автоматически и по умолчанию. В принципе, можно дать указания серверу приложений, как должен называться бин в системе.
Именно служба имен гарантирует, что бин, даже если его выгрузить и загрузить обновленный, будет найден остальными частями системы. А если при этом не изменять его интерфейс, то можно безболезненно улучшать реализацию, при этом не куроча всю систему и не навлекая гнев соратников по клавиатуре.
Осталось упаковать классы, скинуть их серверу приложений и посмотреть, что же из этого выйдет.
Первый бин
Для начала скачаем JBoss. Это можно сделать на страничке http://labs.jboss.com/jbossas/downloads/; берите стабильную версию (4.2.2.GA). Ее также можно найти на диске, прилагаемом к журналу.
Установка JBoss’а заключается в разархивировании его куданибудь. После этого он готов к работе (предполагается, что пути к /bin/java уже находятся в PATH).
Теперь сделаем бин. Для этого нужно скомпилировать наш интерфейс и класс (путь к JBoss’у, конечно, подставьте свой):
javac -cp ~/bin/jboss/server/default/lib/jboss-ejb3x.jar Calculator.java javac -cp ~/bin/jboss/server/default/lib/jboss-ejb3x.jar CalculatorBean. java
после чего запаковать все в jar-файл:
jar -c CalculatorBean.class Calculator.class > CalculatorEJB.jar
Теперь начинается волшебство. Запускаем JBoss:
jboss/bin/run.sh -c default &
(понятно, что путь к JBoss’овскому run.sh будет другой, понятно, что если требуется запустить его с возможностью закрыть терминал, нужно использовать nohup… это самый простой вариант) и кидаем jar-файл в каталог /jboss/server/default/deploy.
Все, через секунду бин «воткнут» в JBoss. Теперь можно запускать клиента. Компилируем его:
javac -cp ~/bin/jboss/server/default/lib/jboss-ejb3x.jar:. CalculatorClient. java
Запускаем клиент, при этом подключая нужные библиотеки (все они есть в JBoss’е):
java -cp ~/bin/jboss/client/jbossall-client.jar:. client.CalculatorClient
Получаем:
Square for circle with radius 2.345 = 17.275696541906616
Кто не верит, пусть пересчитает на калькуляторе.
Второй бин. JPA
Почувствовали, насколько это просто и быстро? Теперь попробуем что-нибудь сохранить в СУБД. Для этого немного настроимся. Подключать будем MySQL, которая запущена на локальной машине, с настройками по умолчанию (учетная запись root без пароля). Чтобы подключить СУБД, нужно положить специальный XML-файл в тот же каталог jboss/server/default/deploy. Файл будет называться mysql-ds.xml, а содержать будет следующее:
<?xml version=”1.0” encoding=”UTF-8”?> <datasources> <local-tx-datasource> <jndi-name>jdbc/jpaPool</jndi-name> <connection-url>jdbc:mysql://localhost:3306/nisp</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password></password> <exception-sorter-class-name>org.jboss.resource.adapter.jdbc. vendor.MySQLExceptionSorter</exception-sorter-class-name> </local-tx-datasource> </datasources>
Тут все просто: строка соединения нам попадалась уже при раcсмотрении JDBC в LXF93 (можно ее усложнить, введя при необходимости всякие параметры), имя учетной записи и пароль — тоже вполне самоочевидные вещи. Еще указывается jndi-name, то есть имя, по которому этот пул соединений можно будет найти в недрах JBoss.
Положили? Теперь перезапустите сервер приложений:
jboss/bin/shutdown.sh -s jnp://127.0.0.1:1099 -S jboss/bin/run.sh -c default &
Теперь мы готовы; начинаем писать EntityBean. Так называются классы-сущности, экземпляры которых хранятся в СУБД.
JPA, или Java Persistence API — это стандартный API, предназначенный для того, чтобы в удобном виде хранить Java-объекты в БД. При этом создается структура БД, создаются классы, в которых при помощи аннотаций прописываются связи между полями классов и полями таблиц БД, а остальное (преобразования, проверки, …) делает конкретная реализация JPA. В JBoss это Hibernate.
Итак, сделаем микробиблиотеку. Вот класс, который хранит в БД информацию о книге:
@Entity public class Book implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long Id; public String Title; public String Author; }
И все. Мы вновь использовали аннотацию, на сей раз @Entity. Все остальное — умолчания, а значит, поля класса Title и Author будут полями в таблице Book. Id — это идентификатор объекта, он уникален, и аннотацией мы указываем, чтобы JPA сам его создавал, когда будет нужно. Можно прописать и подробности: самостоятельно дать имена полям и таблице, указать типы полей и т. п., но пока оставим все это на усмотрение JPA.
Расширим микробиблиотеку, введя туда микрочитателей. Предположим, что один читатель может читать только одну книгу.
@Entity public class Reader implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long Id; public String Name; @OneToOne public Book Book; }
Аннотация @OneToOne обозначает, что связь между Reader и Book — один к одному. Физически эта связь будет реализована посредством внешнего ключа в таблице.
Возникает вопрос: а в какой таблице будет прописана связь (создан внешний ключ)? В Book? Или в Reader? Нужно как-то определиться, чтобы более четко понимать, что происходит в системе — иначе как ошибки-то искать? Для этого пропишем ссылку на Reader’а в книге, плюс укажем аннотацией, с какой стороны должна быть ссылка в БД. Теперь книга выглядит так:
@Entity public class Book implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long Id; public String Title; public String Author; @OneToOne(mappedBy = “Book”) public Reader Reader; }
Параметр аннотации @OneToOne mappedBy показывает, что указанное поле в БД (в таблице Book) не существует, а извлекается из таблицы, на которую указывает связь, то есть Reader.
После деплоймента (установки и развертывания) бина микробиблиотеки, JPA автоматически создал все необходимые таблицы, связи между ними и так далее. Казалось бы, это счастье. Пишем код, JPA делает таблицы – все довольны... Но не так все просто. Во-первых, зачастую таблицы уже есть. Во-вторых, далеко не всегда разработчики, которые создают бины, умеют хорошо создавать таблицы. В третьих, иногда таблицы и бины создают разные люди.
И это правильно. Нехорошо, если разработчик «и швец, и жнец, и на дуде игрец» (то есть хорошо, конечно, но где ж их взять в достаточном для пропитания количестве?). Правильный путь разработки структуры данных – когда сначала создается структура таблиц в СУБД, правильно прописываются связи, при необходимости реализуются триггеры и другие особенности, связанные с СУБД, и только потом на этой основе создаются бины, объекты, связи между ними. JPA рассчитан именно на такое применение и, в принципе, умеет не только создавать структуру СУБД по Java-файлам, но и наоборот, создавать классы по базе. Стоит это учитывать. Иначе часто получается, что «хотели как лучше, а получилось как всегда», или «что тут этот дурак JPA насоздавал – я совсем не то имел в виду; да и тормозит всё».
Третий бин
Создав бины для хранимых сущностей (Entity Beans), нужно сделать бин для работы с ними: извлечения из СУБД, сохранения, выдачи книги и возврата ее обратно в библиотеку.
Для экономии места не будем увлекаться всевозможными проверками (например, выдана ли книга повторно), сосредоточимся на JPA и EJB3.
Как и в предыдущий раз, сначала интерфейс:
@Remote public interface Library { public void addBook(Book aBook); public void addReader(Reader aReader); public void giveBook(String aReaderName, Book aBook); public void returnBook(String aReaderName); public Reader getReader(String aReaderName); public Book getBook(String aBookTitle); }
Потом его реализация (и на ней остановимся поподробнее):
@Stateless public class LibraryBean implements Library { @PersistenceContext private EntityManager _entityManager; public Book addBook(Book aBook) { _entityManager.persist(aBook); return aBook; } public Reader addReader(Reader aReader) { _entityManager.persist(aReader); return aReader; } public void giveBook(String aReaderName, Book aBook) { getReader(aReaderName).Book = aBook; } public void returnBook(String aReaderName) { getReader(aReaderName).Book = null; } public Reader getReader(String aReaderName) { return (Reader) _entityManager.createQuery( “SELECT r FROM Reader AS r WHERE r.Name = :name”). setParameter(“name”, aReaderName).getSingleResult(); } public Book getBook(String aBookTitle) { return (Book) _entityManager.createQuery( “SELECT b FROM Book AS b WHERE b.Title = :title”). setParameter(“title”, aBookTitle).getSingleResult(); } }
Тут есть два интересных момента. Первый — @PersistenceContext. Это так называемый контекст сохранения объектов, в котором обретается все то, что должно «жить» между перезапусками приложения (то есть записываемое в БД). Он нигде не инициализируется явно, так как эту работу берет на себя JPA.
У этого контекста есть несколько (не так много, как могло бы быть) методов, которые позволяют сохранять (persist) и удалять (remove) объекты. Но нет метода, который позволял бы обновлять объекты… Почему? Посмотрим более внимательно на метод giveBook, который «выдает книгу» читателю. Мы просто извлекаем читателя, присваиваем ему ссылку на книгу… и все. Контекст сохранения сам обновит объект в БД. Не правда ли, просто?
И последний интересный момент: запросы. Несмотря на то, что они очень похожи на SQL, это совсем не SQL. Это JPQL (Java Persistence Query Language), который оперирует не столбцами, но объектами. Например, предположим на минутку, что разные экземпляры одной и той же книги могут читать сразу несколько читателей (связь OneToMany). Тогда найти всех людей, которые держат книгу с определенным названием, можно так:
SELECT r FROM Reader AS r WHERE r.Book.Title=:title
На SQL нам пришлось бы выполнить JOIN, учесть внешние ключи и так далее. Здесь все это будет сделано автоматически.
Мало того, контекст заботится и о транзакциях. Он сам открываеттранзакцию перед входом в методы и сам же закрывает ее после выхода. Естественно, используя JTA (Java Transaction API), это можно контролировать вручную, если есть необходимость.
Собираем, запускаем
Собрать jar-файл, чтобы положить его в JBoss, можно аналогичным образом: скомпилировать, собрать jar и поместить его в каталог deploy. Но при этом нужно сделать пару вещей, которые позволят JPA автомтически создать структуру таблиц при запуске бина. Для этого необхо димо перейти в каталог jboss/server/default/deploy/ejb3.deployer/META-INF и в файле persistence.properties убрать комментарий (решетку) перед строкой
hibernate.hbm2ddl.auto=create.
После чего перезапустить JBoss, скинуть jar… бины встали на место. Попробуем простенький клиент для проверки. Основной код остался тем же, что и у клиента калькулятора, поменялся только метод start.
private void start() throws NamingException { _library = (Library) getInitialContext(). lookup(“LibraryBean/remote”); Book book = new Book(); book.Title = “Linux strikes back”; book.Author = “Community org.”; book = _library.addBook(book); Reader reader = new Reader(); reader.Name = “Hacker I.A.”; _library.addReader(reader); _library.giveBook(“Hacker I.A.”, book); reader = _library.getReader(“Hacker I.A.”); System.out.println(“У читателя \”” + reader.Name + “\” “ + “есть книга \”” + reader.Book.Title + “\””); }
Запустите его и убедитесь, что все работает, как надо.
Вместо заключения
Заключения тут не получится. Мы рассмотрели только камень на вершине айсберга, который называется EJB3. Чтобы только перечислить, что же оно может, нужно раз в 5-10 больше места. А чтобы разобраться, нужно потратить не одну неделю. Поэтому хочется просто отметить, что, в отличие от стандарта EJB2, который использовать очень и очень трудно, EJB3 использовать можно и нужно. Есть надежда, что скоро все основные сервера приложений будут поддерживать EJB3 в полном объеме, что позволит быстро и просто разрабатывать сложные, надежные и производительные приложения, рассчитанные на работу в системах уровня предприятия. Если вы заинтересовались данной темой, и хотите узнать про EJB3 побольше, обратите внимание на врезку Литература. Удачного освоения! LXF
Литература
К сожалению, про EJB3 пока очень мало что написано на русском языке. Но на английском есть несколько книг, из которых хочется порекомендовать следующие:
- Raghu R. Kodali, Jonathan R. Wetherbee, Peter Zadrozny. Beginning EJB 3 Application Development: From Novice to Professional (ISBN 1590596714).
- Mike Keith, Merrick Schincariol. Pro EJB 3: Java Persistence API (ISBN 1590596455).
Книги написаны приличным языком, со знанием дела (авторы участвовали в разработке стандартов и работают в компаниях, которые сами используют EJB3), читать их достаточно легко.
Ну, и, конечно, нельзя не указать на сами стандарты: