前言
Spring 的事件监听机制,采用了观察者的设计模式。一套完整的事件监听流程是这样的,首先定义事件类,即ApplicationEvent的子类,它包含事件发生的时间戳timestamp和产生事件的来源source,以及自定义的其它事件属性;然后实现事件监听器 ApplicationListener 并注册到容器,订阅感兴趣的事件,Spring 会在事件发生时触发监听器;最后通过事件发布器 ApplicationEventPublisher 发布自定义事件。
应用场景
事件监听机制的好处主要有两点:
- 解耦:解耦了事件的发布方和消费方,利于构建高内聚低耦合的模块,程序的扩展性也会更好
- 异步:监听器可以同步触发,也可以异步触发,对于非主流程的业务可以选择异步触发以获得更快的响应
任何可以晚点做的事,都应该晚点再做。
举个例子,电商系统中,当有新用户注册时,除了要保存用户信息外,还需要额外发送一封邮件通知用户,以及给新用户送一些专属优惠券。
在这个场景下,除了保存用户信息外的其它流程都不是主流程,接口在保存完用户信息后就可以返回结果了,后续的其它业务就可以通过发布事件来处理。
第一步,定义新用户注册的事件类 UserRegisterEvent
public class UserRegisterEvent extends ApplicationEvent {
private final String username;
private final String email;
public UserRegisterEvent(Object source, String username, String email) {
super(source);
this.username = username;
this.email = email;
}
}
第二步,实现 ApplicationListener,分别处理的是发送邮件和发放优惠券,因为不需要同步执行,我们给方法加上@Async
注解
@Component
public class UserRegisterEmailListener implements ApplicationListener<UserRegisterEvent> {
@Override
@Async
public void onApplicationEvent(UserRegisterEvent event) {
String content = String.format("欢迎 %s 成为我们的用户", event.getUsername());
System.err.println("@" + event.getEmail() + ":" + content);
}
}
@Component
public class NewUserCouponListener implements ApplicationListener<UserRegisterEvent> {
@Override
@Async
public void onApplicationEvent(UserRegisterEvent event) {
System.err.println("发放新用户优惠券:" + event.getUsername());
}
}
第三步,用户注册成功后,发布事件
@Service
public class UserService {
@Autowired
ApplicationEventPublisher applicationEventPublisher;
public void register(String username, String password, String email) {
// save db
UserRegisterEvent event = new UserRegisterEvent(this, username, email);
applicationEventPublisher.publishEvent(event);
}
}
设计实现
事件监听机制由四个组件构成:
- 事件类 ApplicationEvent 负责定义事件本身
- 事件发布器 ApplicationEventPublisher 负责发布事件
- 事件多播器 ApplicationEventMulticaster 根据订阅规则,触发事件对应的监听器
- 事件监听器 ApplicationListener 负责处理事件
Spring 的事件监听机制,遵循了JDK的开发规范,所有的事件类都派生自java.util.EventObject
,所有的监听器都派生自java.util.EventListener
。
ApplicationEventPublisher 只是套了一层壳,发布事件会委托给 AbstractApplicationContext,主要做了两件事:
- 委托给 ApplicationEventMulticaster 派发事件
- 如果存在父容器,在父容器里发布事件
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
} else {
// 非ApplicationEvent子类 封装成PayloadApplicationEvent
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 多播事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// 如果存在父容器,也发布事件
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
ApplicationEventMulticaster 的默认实现是 SimpleApplicationEventMulticaster,它首先会解析 event 对象的类型,然后查找该事件类型对应的监听器,最后触发监听器事件。
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 解析事件类型 便于后续查找对应的监听器
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 查找监听器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
// 触发监听器
invokeListener(listener, event);
}
}
}
判断ApplicationListener是否能处理某个类型的event开销较大,Spring 解析一次后会把他们的缓存起来,容器是ConcurrentHashMap,其中 ListenerCacheKey 封装的是 eventType 和 sourceType,CachedListenerRetriever 封装的是一组 applicationListeners。
final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
最后还剩一个问题,ApplicationEventMulticaster 维护了 EventType 和 ApplicationListener 的关系,ApplicationListener 是什么时候被注册到 ApplicationEventMulticaster 的?
通过源码发现,Spring 内置了一个 ApplicationListenerDetector 类,它是 BeanPostProcessor 的子类,重写了 postProcessAfterInitialization() 方法,Spring 容器内所有的 Bean 初始化完成后,都会判断是不是 ApplicationListener 的子类,如果是就会把 Bean 注册到 ApplicationEventMulticaster。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof ApplicationListener) {
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
} else if (Boolean.FALSE.equals(flag)) {
this.singletonNames.remove(beanName);
}
}
return bean;
}
尾巴
Spring框架提供了一个强大的事件监听机制,允许应用程序的不同组件之间进行松耦合的通信。事件的发布方和消费方都无需知道对方的存在,双方利用事件来通信,利于应用程序中实现模块化、可扩展和可重用的组件。另外,对于非主流程的业务逻辑,事件消费方可以选择异步处理,以获得更好的响应速度。