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 .