Contents

CDI 事件通知模型简介

1. 概述

CDI (上下文和依赖注入)是 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>

像往常一样,我们可以从 Maven Central拉取最新版本的cdi-api 和*weld-se-core * 。

3. 观察和处理自定义事件

简单地说,**CDI 2.0 事件通知模型是观察者模式 **的经典实现,基于@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()方法是一个事件处理程序,它将ExampleEventTextService对象作为参数。

**请记住,@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 Blogdemo!")); 
}

请注意,我们正在使用SeContainerInitializer和 SeContainer对象,因为我们在 Java SE 环境中使用 CDI,而不是在 Jakarta EE 中。

当通过传播事件本身触发ExampleEvent时,将通知所有附加的观察者方法。

由于在*@Observes注释之后作为参数传递的所有对象都将被完全初始化,因此 CDI 将在将其注入onEvent()方法之前为我们连接整个TextService*对象图。

简而言之,我们拥有类型安全的 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-specific。因此,如果我们使用不同的 CDI 实现,我们将需要重构我们的观察者方法。

5. 条件观察者方法

在当前的实现中,我们的ExampleEventObserver类默认定义了一个无条件的观察者方法。这意味着无论当前上下文中是否存在该类的实例,都将始终通知观察者方法提供的事件。

同样,我们可以通过将notifyObserver=IF_EXISTS指定为*@Observes*注释的参数来定义条件观察者方法:

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

当我们使用条件观察者方法时,只有在当前上下文中存在定义观察者方法的类的实例时,才会通知该方法匹配事件

6. 事务观察者方法

我们还可以在事务中触发事件,例如数据库更新或删除操作。为此,我们可以通过将during 参数添加到*@Observes*注释来定义事务观察者方法。

during参数的每个可能值对应于事务的特定阶段:

  • BEFORE_COMPLETION
  • AFTER_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 的事件通知模型中包含的另一个很好的改进是能够为调用给定事件的观察者设置排序或优先级。

我们可以通过在*@Observes* 之后指定@Priority 注释来轻松定义调用观察者方法的顺序。

为了理解这个特性是如何工作的,让我们定义另一个观察者方法,除了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的方法。

同样,如果我们在两个或多个方法中使用相同的优先级,那么顺序又是 undefined

8. 异步事件

在到目前为止我们学习的所有示例中,我们同步触发了事件。但是,CDI 2.0 也允许我们轻松触发异步事件。然后异步观察者方法可以在不同的线程中处理这些异步事件

我们可以使用*fireAsync()*方法异步触发事件:

public class ExampleEventSource {
    
    @Inject
    Event<ExampleEvent> exampleEvent;
    
    public void fireEvent() {
        exampleEvent.fireAsync(new ExampleEvent("Welcome to Blogdemo!"));
    }   
}

Bean 触发事件,这些事件是Event 接口的实现。因此,我们可以像任何其他常规 bean 一样注入它们

为了处理我们的异步事件,我们需要使用@ObservesAsync 注解定义一个或多个异步观察者方法:

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