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

1. Обзор

CDI (внедрение контекстов и зависимостей) - это стандартная структура внедрения зависимостей платформы Jakarta EE.

В этом уроке мы рассмотрим http://www.cdi-spec.org/news/2017/05/15/CDI 2 is__released/[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 и https://search .maven.org/search? q = g: org.jboss.weld.se% 20AND% 20a: сварка-сердечник и сердечник = gav[сварка-сварка-сердечник] от Maven Central.

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

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

Например, мы можем определить один или несколько 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. Определение метода наблюдателя с помощью аннотации @ Observed

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

public class ExampleEventObserver {

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

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

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

  • Следует иметь в виду, что все аргументы, указанные после аннотации @ Observed , являются стандартными точками внедрения. ** В результате 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 Baeldung!"));
}
  • Обратите внимание, что мы используем объекты SeContainerInitializer и SeContainer , потому что мы используем CDI в среде Java SE, а не в Jakarta EE. **

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

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

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

4. Событие ContainerInitialized

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

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

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

Для этого CDI 2.0 предоставляет http://javadox.com/org.jboss.weld.se/weld-se-core/2.2.6.Final/org/jboss/weld/environment/se/events/ContainerInitialized . .html[ ContainerInitialized ]класс событий, который запускается автоматически при инициализации контейнера Weld .

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

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

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

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

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

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

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

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

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

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

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

  • BEFORE COMPLETION__

  • ПОСЛЕ ЗАВЕРШЕНИЯ

  • 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 после @ Observed .

Чтобы понять, как работает эта функция, давайте определим другой метод наблюдателя, кроме того, который реализует 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 Baeldung!"));
    }
}
  • Bean запускает события, которые являются реализациями интерфейса Event . Поэтому мы можем внедрить их как любой другой обычный бин ** .

Для обработки нашего асинхронного события нам нужно определить один или несколько методов асинхронного наблюдателя с помощью @ObserveAsync . аннотация:

public class AsynchronousExampleEventObserver {

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

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

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

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