1. 概述
CDI(Contexts and Dependency Injection)是 Jakarta EE 平台的标准依赖注入框架。
在本教程中,我们将深入了解 CDI 2.0,看看它是如何在 CDI 1.x 强大的类型安全注入机制基础上,新增了一个功能完善的事件通知模型(Event Notification Model)。
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.1.6.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
注解封装了强大的功能。
可以看到,该方法是一个事件处理器,接受 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 Baeldung!"));
}
⚠️ 注意:我们使用 SeContainerInitializer
和 SeContainer
是因为当前是在 Java SE 环境中使用 CDI,而非 Jakarta EE。
当 ExampleEvent
被触发时,所有绑定的监听方法都会收到通知。
由于所有在 @Observes
注解后声明的参数都会被自动初始化,CDI 会负责构建完整的 TextService
对象图,并将其注入到 onEvent()
方法中。
简而言之,我们不仅拥有了类型安全的 IoC 容器,还具备了功能丰富的事件通知机制。
4. ContainerInitialized 事件
在前面的例子中,我们通过自定义事件将事件传递给监听方法,并获取了初始化完成的 TextService
实例。
这在需要在应用多个地方传播事件时非常有用。
但有时候,我们只是想在容器启动时获取一些已经初始化好的对象,而不需要额外实现事件。
为此,CDI 2.0 提供了 ContainerInitialized 事件类,该事件会在 Weld 容器初始化时自动触发。
我们可以通过监听该事件将控制权交给 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
参数将监听方法定义为条件监听方法:
public String onEvent(@Observes(notifyObserver=IF_EXISTS) ExampleEvent event, TextService textService) {
return textService.parseText(event.getEventMessage());
}
✅ 使用条件监听方法时,只有在当前上下文中存在该类实例时,监听方法才会被调用。
6. 事务监听方法
我们也可以在事务中触发事件,例如数据库更新或删除操作。为此,可以通过在 @Observes
注解中添加 during
参数来定义事务监听方法。
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 注解来定义监听方法的优先级。
为了演示该功能,我们再定义一个监听类:
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
}
✅ 优先级值越小,越先被调用。如果多个方法具有相同的优先级,则调用顺序仍不确定。
8. 异步事件
前面的所有示例都是同步触发事件。但 CDI 2.0 也支持异步事件,监听方法可以在不同线程中处理事件。
我们可以通过 fireAsync()
方法异步触发事件:
public class ExampleEventSource {
@Inject
Event<ExampleEvent> exampleEvent;
public void fireEvent() {
exampleEvent.fireAsync(new ExampleEvent("Welcome to Baeldung!"));
}
}
✅ Event
是一个接口,Bean 可以像普通 Bean 一样注入它。
要处理异步事件,我们需要使用 @ObservesAsync 注解定义异步监听方法:
public class AsynchronousExampleEventObserver {
public void onEvent(@ObservesAsync ExampleEvent event) {
// ... implementation
}
}
9. 总结
在本教程中,我们学习了如何使用 CDI 2.0 提供的增强事件通知模型。
一如既往,本文中的所有代码示例都可以在 GitHub 上找到。