Перейти к основному содержимому

Введение в модель уведомления о событиях в CDI 2.0

· 7 мин. чтения

1. Обзор

CDI (Contexts and Dependency Injection) — это стандартная среда внедрения зависимостей для платформы Jakarta EE.

В этом руководстве мы рассмотрим CDI 2.0 и то, как он основан на мощном, безопасном по типам механизме внедрения CDI 1.x, добавив улучшенную полнофункциональную модель уведомления о событиях.

2. Зависимости Maven

Для начала мы создадим простой проект Maven.

Нам нужен контейнер, совместимый с CDI 2.0, и Weld , эталонная реализация CDI, хорошо подходит:

<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>2.0.SP1</version>
</dependency>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
<version>3.0.5.Final</version>
</dependency>
</dependencies>

Как обычно, мы можем получить последние версии cdi-api и Weld-Se-Core из Maven Central.

3. Наблюдение и обработка пользовательских событий

Проще говоря, модель уведомления о событиях CDI 2.0 — это классическая реализация паттерна Observer , основанная на аннотации метод-параметр @Observes . Следовательно, это позволяет нам легко определять методы наблюдателя, которые могут автоматически вызываться в ответ на одно или несколько событий.

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

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

3.1. Базовый класс обслуживания

Начнем с создания простого класса TextService :

public class TextService {

public String parseText(String text) {
return text.toUpperCase();
}
}

3.2. Пользовательский класс событий

Далее давайте определим пример класса событий, который принимает аргумент String в своем конструкторе:

public class ExampleEvent {

private final String eventMessage;

public ExampleEvent(String eventMessage) {
this.eventMessage = eventMessage;
}

// getter
}

3.3. Определение метода наблюдателя с помощью аннотации @Observes

Теперь, когда мы определили наши классы службы и событий, давайте воспользуемся аннотацией @Observes для создания метода наблюдателя для нашего класса ExampleEvent :

public class ExampleEventObserver {

public String onEvent(@Observes ExampleEvent event, TextService textService) {
return textService.parseText(event.getEventMessage());
}
}

Хотя на первый взгляд реализация метода onEvent() выглядит довольно тривиально, на самом деле она инкапсулирует множество функций через аннотацию @Observes .

Как мы видим, метод onEvent() — это обработчик событий, который принимает в качестве аргументов объекты ExampleEvent и TextService .

Имейте в виду, что все аргументы, указанные после аннотации @Observes , являются стандартными точками внедрения. В результате CDI создаст для нас полностью инициализированные экземпляры и внедрит их в метод наблюдателя.

3.4. Инициализация нашего контейнера CDI 2.0

На данный момент мы создали наши классы обслуживания и событий, и мы определили простой метод наблюдателя, чтобы реагировать на наши события. Но как указать CDI внедрять эти экземпляры во время выполнения?

Здесь модель уведомления о событиях демонстрирует свои функциональные возможности в полной мере. Мы просто инициализируем новую реализацию SeContainer и запускаем одно или несколько событий с помощью метода fireEvent() :

SeContainerInitializer containerInitializer = SeContainerInitializer.newInstance(); 
try (SeContainer container = containerInitializer.initialize()) {
container.getBeanManager().fireEvent(new ExampleEvent("Welcome to ForEach!"));
}

Обратите внимание, что мы используем объекты SeContainerInitializer и SeContainer , поскольку используем CDI в среде Java SE, а не в Jakarta EE.

Все прикрепленные методы наблюдателя будут уведомлены, когда будет запущено событие ExampleEvent путем распространения самого события.

Поскольку все объекты, переданные в качестве аргументов после аннотации @Observes , будут полностью инициализированы, CDI позаботится о подключении всего графа объектов TextService для нас, прежде чем внедрять его в метод onEvent() .

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

4. Событие ContainerInitialized

В предыдущем примере мы использовали пользовательское событие, чтобы передать событие методу наблюдателя и получить полностью инициализированный объект TextService .

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

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

С этой целью CDI 2.0 предоставляет класс событий ContainerInitialized , который автоматически запускается при инициализации контейнера Weld .

Давайте посмотрим, как мы можем использовать событие ContainerInitialized для передачи управления классу ExampleEventObserver :

public class ExampleEventObserver {
public String onEvent(@Observes ContainerInitialized event, TextService textService) {
return textService.parseText(event.getEventMessage());
}
}

И имейте в виду, что класс событий ContainerInitialized специфичен для Weld . Итак, нам потребуется реорганизовать наши методы наблюдения, если мы будем использовать другую реализацию CDI.

5. Методы условного наблюдателя

В своей текущей реализации наш класс ExampleEventObserver по умолчанию определяет безусловный метод наблюдателя. Это означает, что метод наблюдателя всегда будет уведомлен о предоставленном событии , независимо от того, существует ли экземпляр класса в текущем контексте.

Точно так же мы можем определить метод условного наблюдателя , указав notifyObserver=IF_EXISTS в качестве аргумента аннотации @Observes :

public String onEvent(@Observes(notifyObserver=IF_EXISTS) ExampleEvent event, TextService textService) { 
return textService.parseText(event.getEventMessage());
}

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

6. Методы наблюдения за транзакциями

Мы также можем запускать события внутри транзакции, такие как операция обновления или удаления базы данных . Для этого мы можем определить методы наблюдения за транзакциями, добавив аргумент while в аннотацию @Observes .

Каждое возможное значение аргумента while соответствует определенной фазе транзакции :

  • ДО_ЗАВЕРШЕНИЯ
  • ПОСЛЕ ЗАВЕРШЕНИЯ
  • AFTER_SUCCESS
  • AFTER_FAILURE

Если мы запускаем событие ExampleEvent внутри транзакции, нам необходимо соответствующим образом реорганизовать метод onEvent() для обработки события на требуемом этапе:

public String onEvent(@Observes(during=AFTER_COMPLETION) ExampleEvent event, TextService textService) { 
return textService.parseText(event.getEventMessage());
}

Метод наблюдателя транзакций будет уведомлен о предоставленном событии только на этапе сопоставления данной транзакции .

7. Порядок методов наблюдателя

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

Мы можем легко определить порядок, в котором будут вызываться методы наблюдателя, указав аннотацию @Priority после @Observes.

Чтобы понять, как работает эта функция, давайте определим еще один метод наблюдателя, помимо того, что реализует ExampleEventObserver :

public class AnotherExampleEventObserver {

public String onEvent(@Observes ExampleEvent event) {
return event.getEventMessage();
}
}

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

Мы можем легко исправить это, назначив каждому методу приоритет вызова через аннотацию @Priority :

public String onEvent(@Observes @Priority(1) ExampleEvent event, TextService textService) {
// ... implementation
}
public String onEvent(@Observes @Priority(2) ExampleEvent event) {
// ... implementation
}

Уровни приоритета следуют естественному порядку. Следовательно, CDI сначала вызовет метод наблюдателя с уровнем приоритета 1 , а затем вызовет метод с уровнем приоритета 2 .

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

8. Асинхронные события

Во всех изученных нами примерах события запускались синхронно. Однако CDI 2.0 также позволяет нам легко запускать асинхронные события. Затем асинхронные методы наблюдателя могут обрабатывать эти асинхронные события в разных потоках .

Мы можем запустить событие асинхронно с помощью метода fireAsync() :

public class ExampleEventSource {

@Inject
Event<ExampleEvent> exampleEvent;

public void fireEvent() {
exampleEvent.fireAsync(new ExampleEvent("Welcome to ForEach!"));
}
}

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

Чтобы обработать наше асинхронное событие, нам нужно определить один или несколько асинхронных методов наблюдателя с аннотацией @ObservesAsync :

public class AsynchronousExampleEventObserver {

public void onEvent(@ObservesAsync ExampleEvent event) {
// ... implementation
}
}

9. Заключение

В этой статье мы узнали, как приступить к работе с улучшенной моделью уведомлений о событиях в комплекте с CDI 2.0.

Как обычно, все примеры кода, показанные в этом руководстве, доступны на GitHub .