一、概述
许多现代应用都采用了事件驱动设计。事件驱动应用可以用任何一种编程语言来创建,因为事件驱动本身是一种编程方法,而不是一种编程语言。事件驱动架构可以最大程度减少耦合度,因此是现代化分布式应用架构的理想之选。
事件是指系统硬件或软件的状态出现任何重大改变。事件与事件通知不同,后者是指系统发送的消息或通知,用于告知系统的其他部分有相应的事件发生。
而事件的来源可以是内部或外部输入。事件可以来自用户(例如点击鼠标或按键)、外部源(例如传感器输出)或系统(例如加载程序)。
事件驱动架构是一种用于设计应用的软件架构和模型。对于事件驱动系统而言,事件的捕获、通信、处理和持久保留是解决方案的核心结构。这和传统的请求驱动模型有很大不同。
事件驱动架构由事件发起者和事件使用者组成。事件的发起者会检测或感知事件,并以消息的形式来表示事件。它并不知道事件的使用者或事件引起的结果。
检测到事件后,系统会通过事件通道从事件发起者传输给事件使用者,而事件处理平台则会在该通道中以异步方式处理事件。事件发生时,需要通知事件使用者。他们可能会处理事件,也可能只是受事件的影响。
事件处理平台将对事件做出正确响应,并将活动下发给相应的事件使用者。通过这种下发活动,我们就可以看到事件的结果。
1.1 事件驱动架构模型
1.1.1 发布订阅模式
1.1.2 事件流模式
- 事件流处理使用诸如 Apache Kafka 等数据流平台来提取事件并处理或转换事件流。事件流处理可用于检测事件流中有用的模式。
- 简单事件处理是指事件立即在事件使用者中触发操作。
- 复杂事件处理则需要事件使用者处理一系列事件以检测模式。
1.2 Java事件/监听器编程模型
1.2.1 设计模式
观察者设计模式,详见
1.2.2 JDK 观察者接口
- 消息发送者
java.util.Observalbe
- 观察者
java.util.Observer
由于该接口存在局限性,在JDK 1.9 被移除。 For a richer event model, consider using the java.beans package. For reliable and ordered messaging among threads, consider using one of the concurrent data structures in the java.util.concurrent package. For reactive streams style programming, see the java.util.concurrent.Flow API.
1.2.3 标准化接口
- 事件对象
java.util.EventObject
-
二、Spring标准事件 ApplicationEvent
ApplicationEvent
扩展Java标准事件EventObject
。继承体系如下 EventObject
是 JDK 标准事件类,内部持有一个Object
引用,表示最初发生事件的对象。ApplicationEvent
是 Spring 内建对象,增加了事件发生时间。所有的事件(无论自定义还是Spring内建事件)都需要继承此抽象类。ContextClosedEvent
、ContextStartedEvent
、ContextStoppedEvent
、ContextRefreshedEvent
则为 Spring 内建对象,见名知义。分别表示容器关闭事件、容器启动事件、容器停止事件、容器刷新事件。2.1 基于接口的 Spring 事件监听器
2.1.1 JDK 事件接口标记 EventListener
EventListener
为所有事件监听器必须实现的标记接口。2.1.2 Spring 扩展 ApplicationListener
应用事件监听器接口,继承 JDK 标准事件监听标记接口,基于观察者模式。 ```java @FunctionalInterface public interface ApplicationListener
extends EventListener { /**
- Handle an application event.
- @param event the event to respond to */ void onApplicationEvent(E event);
}
可以通过实现 `ApplicationListener` 接口创建自定义 Spring 事件监听器,并将其注册为 Spring Bean 。
```java
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
NOTE: 默认情况下,事件监听同步接收事件,意味着
publishEvent()
方法会阻塞,直到所有监听器都完成对事件的处理。这种方式优点在于,当监听器收到事件时,如果事务上下文可用,它将在发布者的事件上下文中进行操作。 Spring 的事件机制是为同一应用程序上下文中 Spring 之间的简单通信而设计的,而不适用更复杂的企业集成需求。
2.1.3 通用事件 EntityCreatedEvent<T>
使用泛型进一步定义事件的结构,考虑使用 EntityCreatedEvent<T>
,其中 T
为实际类型。
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
在某些情况下,可以实现 ResolvableTypeProvider
指导框架提供比支行时环境所提供的更多的信息。
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
2.2 基于注解的 Spring 事件监听器
2.2.1 事件监听器注解 @EventListener
注解@EventListener
在org.springframework.context.event
包下,使用注解无接口约束。可与以下注解配合使用:
@Async
异步处理。- 异步监听器执行时抛出异常,不会传播到调用方。
-
2.2.2 示例
```java @Component public class MyAnnotationListener {
@Async @Order(42) @EventListener public void event(MyAnnotationEvent event) {
System.out.println("get annotation event: " + event); System.out.println(Thread.currentThread().getName());
} }
// 监听多个事件 @EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleContextStart() { // … }
// 定义SpEL表达式: 只有当事件的content属性值==my-event时才能被调用 @EventListener(condition = “#blEvent.content == ‘my-event’”) public void processBlockedListEvent(BlockedListEvent blockedListEvent) { // notify appropriate parties via notificationAddress… }
<a name="Em7Pb"></a>
## 2.3 注册 Spring ApplicationListener
<a name="fKVwm"></a>
### 方式一 使用`@Component`注解交给Spring容器管理
```java
@Component
public class MyAnnotationListener {}
方式二 通过 ConfigurableApplicationContext
API添加事件监听器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.addApplicationListener((ApplicationListener<MyApplicationEvent>) event -> {
System.out.println("监听获取消息: " + event.getMsg());
});
方式三 ApplicationEventMulticaster
ApplicationEventMulticaster
是 Spring 底层负责管理事件监听器和发布事件的接口,ApplicationEventPublisher
以及 ApplicationContext
的底层都是由它代理完成事件的监听与发布。
继承体系
Spring 容器是在AbstractApplicationContext#initApplicationEventMulticaster();
为应用上下文初始化事件广播器。
// AbstractApplicationContext#initApplicationEventMulticaster
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
ApplicationEventMulticaster
接口也比较简单,定义包括注册/移除事件监听器、发布事件等。
2.4 Spring 事件发布器
若在发布 Spring 事件,请使用 ApplicationEventPublisher#publishEvent()
方法。通常通过实现与之相关的意识接口,即 ApplicationEventPublisherAware
的类并将其注册为 Spring Bean 来完成的。
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
2.5 Spring 层次性上下文事件传播
- 说明
- 当 Spring 应用出现多层次 Spring 应用上下文(ApplicationContext)时,如 Spring MVC、Spring Boot或 Spring Cloud 场景下,由子 ApplicationContext 发起 Spring 事件可能会传递到其 Parent ApplicationContext(直到 Root) 的过程。
- 如何避免
- 定位 Spring 事件源(ApplicationContext)进行过滤处理。
三、底层实现
Spring 事件模型就是设计模式的观察者模式。无论是发布事件还是监听事件,底层都是由EventMulticaste
实现。而底层也是根据类型获取相应的事件监听器并执行。为了高效,添加了缓存处理。事件发布核心代码如下:// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); Executor executor = getTaskExecutor(); // getApplicationListeners 目的获取与event/type相关的事件监听器,通过for循环遍历完成事件监听后续动作 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
- 定位 Spring 事件源(ApplicationContext)进行过滤处理。