В данной статье рассмотрим EJB — Enterprise JavaBeans. Данная технология является частью спецификации Java EE. Мы коснемся таких вопросов, как:
  • что такое EJB;
  • какова история возникновения EJB;
  • какие есть типы EJB.
А еще — напишем небольшое HelloWorld приложение с использованием EJB и сервлетов. Знакомство с EJB - 1Данная статья окажется полезной читателям, которые освоились в Java SE и начинают изучение Java EE. Для полноценного понимания практической части данной статьи рекомендуется сперва ознакомиться со статьей “Настройка локального окружения”.

Краткая история EJB

В далеком 1996 году, когда автору данной статьи было 5 лет, Java уже пользовалась популярностью среди разработчиков. Причиной тому были дружелюбный API, автоматическая сборка мусора, и т.д. Java широко использовалась в системах, отвечавших за бэкенд. Однако, несмотря на все прелести языка, программисты того времени нуждались в определенном функционале, еще не реализованном в JDK. Такими потребностями были:
  • обеспечение персистентности данных;
  • целостность транзакций
  • конкурентный доступ к данным (управление многопоточностью);
  • и, скорее всего, что-то еще.
Все это приводило к естественному росту популяции доморощенных, самописных, закрытых библиотек. Иными словами каждый реализовывал свои потребности как мог. Так длилось до тех пор, пока IBM в 1997 году не выступила с лозунгом: "Все должны реализовывать свои потребности одинаково", и не выпустила спецификацию Enterprise Java Bean (EJB). Именно она позволила унифицировать процесс разработки и взять решение типовых проблем (описанных выше как потребности) на сторону фреймворка. Компания Sun занималась адаптацией детища IBM на протяжении 2 лет, и в 1999 году выпустила спецификацию EJB 1.0. Так появилась на свет технология, о которой далее речь пойдет в более прикладном ключе.

Что такое EJB

EJB в некотором смысле — собирательный термин, который в зависимости от контекста может подразумевать под собой либо саму технологию Enterprise JavaBeans в общем, либо некоторый конкретный программный компонент (бин) Enterprise JavaBean, который является частью технологии EJB. Определение EJB как технологии приводится на википедии: Enterprise JavaBeans (также часто употребляется в виде аббревиатуры EJB) — спецификация технологии написания и поддержки серверных компонентов, содержащих бизнес-логику. Является частью Java EE. Эта технология обычно применяется, когда бизнес-логика требует как минимум один из следующих сервисов, а часто — все из них:
  • поддержка сохранности данных (persistence): данные должны быть в сохранности даже после остановки программы. Чаще всего достигается с использованием базы данных;
  • поддержка распределенных транзакций;
  • поддержка параллельного изменения данных и многопоточность;
  • поддержка событий;
  • поддержка именования и каталогов (JNDI);
  • безопасность и ограничение доступа к данным;
  • поддержка автоматизированной установки на сервер приложений;
  • удалённый доступ.
Сервисы, перечисленные выше — это несомненное преимущество технологии EJB. Еще одним таким преимуществом является то, что все перечисленное выше работает из коробки, сразу же. Т.е. программисту не нужно думать о поддержке распределенных транзакций. Программисту нужно думать только о бизнес-логике, которую он в данный момент пытается реализовать. EJB как некоторый конкретный программный компонент — это Java-класс с одной или несколькими аннотациями из спецификации EJB, который содержит в себе некоторую бизнес логику приложения. Аннотации из спецификации EJB наделяют помеченный класс определенными полномочиями, силами, суперспособностями. Подробнее об этом читайте ниже.

Типы EJB

Резюмируем. EJB — это обычный Java класс, отмеченный одной из специальных аннотаций. Такие классы называют бинами. В зависимости от того, какой аннотацией отмечен класс, он будет являться представителем того или иного типа EJB (бинов). Есть три основные типа бинов:
  • Message Driven Beans (бины, управляемые сообщениями);
  • Entity Beans (объектные бины) — определены в спецификации JPA (Java Persistence API) и используются для хранения данных;
  • Session Beans (cессионные бины).
Последние (сессионные бины) подразделяются на несколько подвидов:
  • stateless (без состояния);
  • stateful (с поддержкой текущего состояния сессии);
  • singleton (один объект на все приложение; начиная с версии EJB 3.1).
Знакомство с EJB - 2Ниже рассмотрим каждый тип бинов подробнее.

Session Beans

Session Beans, или сессионные бины — определенный вид бинов. Они инкапсулируют в себе бизнес-логику, которую клиент может программно вызвать посредством вызова методов этого бина. Вызов метода может выполнить:
  • локально, другим классом в той же JVM, что и сессионный бин;
  • удаленно, по сети, из другой JVM, с помощью технологии Java RMI (Remote Method Invocation).
Слово «сессионный» предполагает, что бин доступен только на время выполнения определенной задачи сервером и безвозвратно уничтожается в случае аварии или остановки сервера. Жизненный цикл экземпляра сессионного бина управляется EJB контейнером (подробнее об EJB контейнерах можно почитать в первой лекции цикла). Сессионные бины без состояния (stateless) не сохраняют информацию о своем состоянии. Компонент такого типа можно использовать различными клиентами. Stateless бины используются для реализации бизнес-процессов, которые можно завершить за одну операцию. Например, проверка кредитной истории клиентов. Так как один экземпляр бина может быть использован различными клиентами, разработчик должен обеспечить потокобезопасный доступ к данным бина. Создать бин такого типа (впрочем, как и все остальные сессионные бины) довольно просто. Это обычный Java-класс с аннотацией @Stateless. Приведем пример ниже:

import javax.ejb.Stateless;

@Stateless
public class StatelessEjbExample {
    public String sayHi() {
        return "Hi, I'm Stateless EJB!";
    }
}
Сессионные бины с поддержкой текущего состояния сессии (Stateful) сохраняют информацию о своем состоянии между обращениями к нему от одного и того же клиента и завершают свое существование по явному запросу от клиента. Достигается это за счет того, что stateful бины уникальны для каждого клиента. Пример задачи, за которую может отвечать такой тип бинов — поддержка в актуальном состоянии корзины покупок в интернет-магазине для каждого пользователя. Жизненным циклом данных бинов управляет EJB контейнер. Данные бины также уничтожаются, когда клиент завершает свою работу. Подобные бины тоже довольно просто создавать. Это Java класс, помеченный аннотацией Stateful. Пример ниже:

import javax.ejb.Stateful;

@Stateful
public class StatefulEjbExample {
    public String sayHi() {
        return "Hi, I,m Stateful EJB";
    }
}
Сессионные бины одиночки (singleton) инициируются один раз за время существования приложения и существуют все время "жизни" приложения. Такие бины разрабатываются для ситуаций, в которых одно состояние должно быть разделено между всеми клиентами. Подобно stateless бинам, в бинах одиночках разработчику необходимо следить за организацией потокобезопасной среды внутри бина. Приведем пример Singleton бина, который также прост в создании как и его собратья, речь о которых шла выше. Нетрудно догадаться, что это Java-класс с аннотацией @Singleton. Однако в данном случае необходимо быть внимательным. Есть две аннотации, идентичные по синтаксису, но различные по назначению и расположенные в разных пакетах:
  • javax.ejb.Singleton
  • javax.inject.Singleton
Для создания EJB необходимо использовать аннотацию из пакета javax.ejb. Пример ниже:

import javax.ejb.Singleton;

@Singleton
public class SingletonEjbExample {
    public String sayHi() {
        return "Hi, I'm Singleton EJB!";
    }
}

Message Driven Beans

Message Driven Beans, или MDB, или бины управляемые сообщениями, подобно сеансовым бинам реализуют некоторую бизнес-логику. Но в отличие от своих родственников, у MDB есть одно важное отличие. Клиенты никогда не вызывают методы MDB напрямую. Такие бины чаще всего выступают в роли слушателей JMS (Java Message Service) сообщений и служат для организации асинхронного обмена сообщениями между частями системы. Примером такого сообщения может быть запрос на доставку товарных запасов от автоматизированной системы розничной торговли к системе управления поставками. Ниже приведем пример MDB бина. В отличие от сессионных бинов, его создание немного интереснее:

import javax.annotation.Resource;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(mappedName = "jms/TestQueue")
public class MessageDrivenEjbExample implements MessageListener {
    
    @Resource
    private MessageDrivenContext messageDrivenContext;

    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                TextMessage msg = (TextMessage) message;
                msg.getText();
            }
        } catch (JMSException e) {
            messageDrivenContext.setRollbackOnly();
        }
    }
    
}
Аннотация MessageDriven делает наш класс MDB бином. Внутри аннотации с помощью JNDI (читайте про JNDI тут) определяется имя JMS рассылки, слушателем которой становится наш класс. Помимо этого, наш класс реализует интерфейс MessageListener и его метод onMessage. Данный метод будет вызван, когда придет некоторое сообщение из очереди/рассылки с именем, определенным внутри аннотации MessageDriven.

Entity beans

Частью технологии EJB является JPA спецификация. JPA, или Java Persistence API — это спецификация, которая обеспечивает объектно-реляционное отображение (ORM — Object-Relational Mapping) Java объектов (Entity бинов) и предоставляющая API для сохранения, получения и управления такими объектами. JPA позволяет представлять данные из БД в виде Java-объектов, а также сохранять Java-объекты в виде записей в базе данных. В роли подобного объекта может выступать не каждый класс, а как раз-таки Entity бины. Entity Bean — это Java класс, который является отображением некоторой таблицы в базе данных. Отображение (маппинг) достигается за счет использования специальных аннотаций. С их помощью осуществляется сопоставление Java-класса с таблицей в базе данных, а также сопоставление полей Java-класса c полями таблицы БД. Приведем пример Entity бина, c комментариями в коде:

@Entity // Делает данный класс Entity бином
@Table(name = "employee") // "Связывает" данный класс с таблицей employee в БД
public class Employee implements Serializable {

    @Id // Говорит о том, что поле ниже является первичным ключом
    @GeneratedValue(strategy = GenerationType.AUTO) // Определяет тип генерации значений первичного ключа
    private int id;

    @Column(name="name") // "Связывает" поле ниже с полем name в таблице employee в БД
    private String name;

    @Column (name="age") // "Связывает" поле ниже с полем age в таблице employee в БД
    private int age;

    // getters and setters...
}
Стоит отметить, что данный тип бинов имеет смысл изучать только в контексте изучения спецификации JPA.

Пишем приложение: EJB HelloWorld

В данном разделе мы напишем небольшое Java EE HelloWorld приложение, которое развернем на сервере GlassFish. Перед прочтением данной статьи настоятельно рекомендуется прочитать статью о настройке локального окружения.
  1. Создаем новый Maven проект в IntelliJ IDEA.

    File -> New -> Project...

    Знакомство с EJB - 3
  2. Нажимаем Next.

  3. Заполняем параметры Maven проекта:

    Знакомство с EJB - 4
  4. Нажимаем Finish

  5. Проект создан и имеет следующую структуру:

    Знакомство с EJB - 5
Файл pom.xml выглядит следующим образом: Знакомство с EJB - 6Первым делом нам необходимо добавить зависимость от Java EE API, а также указать упаковку нашего проекта в виде архива веб-приложения (war). Чтобы сделать это, необходимо привести код pom.xml к следующему виду:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javarush.lectures</groupId>
    <artifactId>ejb_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
        </dependency>
    </dependencies>

</project>
Далее можно приступать к Java-коду. Наше приложение будет наипростейшим. У нас будет 1 сервлет и 1 EJB. Это будет сессионный бин без сохранения состояния (stateless). Внутри EJB мы определим всего 1 метод, который будет возвращать строку “Hello World”. Первым делом создадим пакет com.javarush.lectures. Затем, внутри пакета com.javarush.lectures, создадим наш бин — DemoEJB. Код бина приведен ниже:

import javax.ejb.Stateless;

@Stateless
public class DemoEJB {
    public String helloWorld() {
        return "Hello world!";
    }
}
Как было сказано ранее, все довольно просто. Наш следующий шаг — создать сервлет, который будет передавать значение из EJB в качестве ответа на HTTP-запрос. Стоит отметить, что сервлеты не относятся к теме данной статьи, но для демонстрации EJB все же придется их использовать. Для этого создадим новый сервлет DemoServlet в том же пакете, что и EJB. Его код ниже:

@WebServlet("/helloWorld")
public class DemoServlet extends HttpServlet {

    @EJB
    private DemoEJB ejb;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write(ejb.helloWorld());
    }
}
Приведем небольшие комментарии к коду. Аннотация @WebServlet("/helloWorld") — определяет наш класс как сервлет, который будет обрабатывать HTTP запросы на эндпоинт /helloWorld. У нашего класса есть одно поле — DemoEJB ejb. Это наш бин, определенный ранее. Аннотация над полем класса — @EJB осуществляет инъекцию зависимости (DI). Т.е. Переменная ejb автоматически инициализируется новым экземпляром, когда это потребуется. Наш класс является наследником HttpServlet и переопределяет один из методов суперкласса — doGet. Данный метод обрабатывает HTTP GET запросы и принимает в себя два параметра — HttpServletRequest и HttpServletResponse. HttpServletRequest служит для получения информации о поступившем HTTP запросе. HttpServletResponse нужен для формирования ответа на запрос. Внутри метода мы получаем объект PrintWriter у объекта ответа (HttpServletResponse), с помощью метода getWriter(). Далее мы можем записать в полученный объект некоторое значение с помощью метода write. Чем, собственно мы и пользуемся, записывая в объект PrintWriter-а значение, полученное из определенного нами EJB (значение — строка “Hello World!”). Данное значение клиент, отправивший HTTP-запрос, получит в качестве ответа на свой запрос. Следующий шаг — запуск приложения на Java EE сервере GlassFish. Для этого создадим новую конфигурацию, как это описано в статье о настройке локального окружения. Ниже скрин готовой конфигурации для текущего проекта. Убедитесь, что у вас установлен сервер GlassFish перед запуском: Знакомство с EJB - 7После создания конфигурации запуска — запускаем приложение с помощью меню Run -> Run ‘ejb_demo’ либо с помощью хоткея Shift+F10. После запуска можно увидеть его логи: Знакомство с EJB - 8А также открывшийся браузер: Знакомство с EJB - 9Все это говорит о том, что приложение работает, как задумывалось.

Заключение

В данной статье мы знакомились с EJB — Enterprise JavaBeans. Рассмотрели такие вопросы, как:
  • Что такое EJB?
  • История EJB
  • Различные типы EJB
Вспомним, что EJB бывают следующих типов:
  • Message Driven Beans (бины, управляемые сообщениями);
  • Entity Beans (объектные бины) — определены в спецификации JPA (Java Persistence API) entities и используются для хранения данных;
  • Session Beans (cессионные бины):
    • stateless (без состояния)
    • stateful (с поддержкой текущего состояния сессии)
    • singleton (один объект на все приложение; начиная с версии EJB 3.1)
А также мы написали небольшое HelloWorld приложение с использованием EJB. В качестве ДЗ ты можешь повторить практическую часть данной статьи самостоятельно. А затем добавить в свое приложение еще два сервлета, которые будут использовать stateful и singleton бины для получения значения.