在软件设计和开发过程中,事件驱动是一种非常主流的架构模式,它的基本组成见下图,可以看到存在一个事件中心,而各个服务可以执行事件发布、订阅和消费等基本过程。
事件驱动架构代表的是一种架构设计风格,实现方法和工具有很多。今天的内容将围绕Spring框架来介绍它的事件发布和监听机制。事实上,事件处理也是Spring内置的一个功能强大的扩展点,这种扩展点实现机制更加灵活,但也更加复杂。
对于所应该具备的技术组件而言,事件驱动架构与观察者模式比较类似,基本上都采用了如下图所示的组件结构。
作为类比,观察者模式中的主题就相当于是上图的事件(Event),观察者则对应事件监听器(EventListener)。而这里的事件发布器(EventPublisher)用来发布事件,并管理着一组事件监听器。一旦某个事件被发布,那么对它感兴趣的监听器就会监听到该事件,并触发事先定义好的处理逻辑。
Spring框架采用的也是与上图类似的设计方法。接下来,让我们看一下Spring中对整个事件驱动架构的抽象和实现过程。
Spring事件发布和监听机制
用户发布事件的ApplicationEventPublisher和用于监听事件的ApplicationListener是Spring框架中实现事件驱动编程的核心类。我们知道在ApplicationContext的整个生命周期中,每一个阶段或操作的开始和结束都可以触发一种事件,这些事件被封装成ApplicationEvent对象。通过这些接口,可以实现针对ApplicationContext中各种事件的处理。
一旦一个Bean实现了ApplicationListener接口,那么每当ApplicationEventPublisher发布事件时,这个Bean将自动触发监听器处理程序。ApplicationListener接口定义如下所示。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
有了事件的监听器,就有事件的发布器。在Spring中,事件发布器由ApplicationEventPublisher接口进行表示,该接口定义如下所示。
public interface ApplicationEventPublisher {
void publishEvent(ApplicationEvent event);
}
请注意,ApplicationContext接口本身就继承了这个ApplicationEventPublisher接口,意味着所有ApplicationContext的实现类都具有发布事件的能力。我们可以通过一个简单的代码示例来实现事件发布和消费的完整流程,如下所示。
public class EmailEvent extends ApplicationEvent{
...
}
public class SpringTest {
public static void main(String args[]){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//创建一个ApplicationEvent对象
EmailEvent event = new EmailEvent(...);
//主动触发该事件
context.publishEvent(event);
}
}
public class EmailNotifier implements ApplicationListener{
public void onApplicationEvent(ApplicationEvent event) {
//监听EmailEvent事件
if (event instanceof EmailEvent) {
//处理EmailEvent事件
EmailEvent emailEvent = (EmailEvent)event;
...
} else {
...
}
}
}
在上述代码示例中,我们使用了一个自定义事件EmailEvent。事实上,Spring框架中已经内置了一批常见的事件,其中最常用的就是ContextRefreshedEvent。顾名思义,每当ApplicationContext被刷新时,Spring就会发布ContextRefreshedEvent事件。
我们在AbstractApplicationContext的refresh方法中看到有一个finishRefresh方法,该方法中包含了如下所示的核心代码。
protected void finishRefresh() {
...
// 发布ContextRefreshedEvent事件
publishEvent(new ContextRefreshedEvent(this));
...
}
可以看到这里通过这里publishEvent方法语句发布了一个ContextRefreshedEvent。当然,我们也可以直接使用refresh方法来触发该事件。
ContextRefreshedEvent是一个非常常用和重要的事件,我们在后续介绍Dubbo与Spring框架之间的整合过程时还会提到。
另外诸如ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent等事件会在Spring容器的生命周期处于启动、停止以及关闭阶段自动触发。如果我们对这些事件感兴趣,就可以直接实现ApplicationListener接口从而对具体某一个事件进行响应。例如,如果我们想对ContextStoppedEvent事件进行响应,可以使用如下所示的实现方法。
@Component
public class TestApplicationListener implements ApplicationListener<ContextStoppedEvent>{
@Override
public void onApplicationEvent(ContextStoppedEvent contextStoppedEvent) {
System.out.println(contextStoppedEvent);
}
}
另外,我们还可以使用一个@EventListener注解来对整个事件监听的开发过程进行简化。使用该注解时,我们可以不需要显式实现ApplicationListener接口就可以消费事件。例如,如下代码完成了与前面代码同样的操作。
@Component
public class TestApplicationListener
@EventListener(classes={ ContextStoppedEvent.class})
public void listen(ContextStoppedEvent contextStoppedEvent){
System.out.println(contextStoppedEvent);
}
}
ApplicationListener实现原理
介绍完Spring事件发布和监听的基本流程之后,我们来进一步剖析它的实现原理。前面提到,ApplicationContext接口本身就继承了这个ApplicationEventPublisher接口,因此在AbstractApplicationContext中我们就会发现如下所示的publishEvent方法。
public void publishEvent(ApplicationEvent event) {
getApplicationEventMulticaster().multicastEvent(event);
if (this.parent != null) {
this.parent.publishEvent(event);
}
}
显然,对于一个事件而言,可以存在多个消费者。所以上述代码中首先获取了Spring中的事件多播器ApplicationEventMulticaster,并调用它的事件多播方法multicastEvent实现事件的多播发送。我们不难明白在ApplicationEventMulticaster中维护着一个ApplicationListener列表,并能实现对这些ApplicationListener发送事件。
SimpleApplicationEventMulticaster是ApplicationEventMulticaster的最终实现类,它的multicastEvent方法实现如下所示。
public void multicastEvent(final ApplicationEvent event) {
for (final ApplicationListener listener : getApplicationListeners(event)) {
//获取线程执行器
Executor executor = getTaskExecutor();
if (executor != null) {
//执行监听线程
executor.execute(new Runnable() {
public void run() { listener.onApplicationEvent(event);
}
});
}
else {
listener.onApplicationEvent(event);
}
}
}
这里通过JDK提供的Executor为每个ApplicationListener启动一个独立的线程,并在该线程中回调了onApplicationEvent方法。
我们已经明白了事件发布的独立过程,现在回到Spring容器来获取用于事件发布的ApplicationEventMulticaster以及各个用于事件监听的ApplicationListener的具体方式。让我们再次来到已经多次强调的AbstractApplicationContext中的refresh方法,这其中有两个步骤与事件相关,即initApplicationEventMulticaster和registerListeners。
public void refresh() throws BeansException, IllegalStateException {
try {
...
// 初始化自定义事件广播器
initApplicationEventMulticaster();
// 执行刷新
onRefresh();
// 注册监听器
registerListeners();
...
}
}
initApplicationEventMulticaster方法比较简单,先判断容器中是否实现了一个自定义的ApplicationEventMulticaster,如果有就直接使用;反之就会创建一个新的SimpleApplicationEventMulticaster。
另一个registerListeners方法则获取当前上下文中的所有ApplicationListener并添加到ApplicationEventMulticaster中。那么这些ApplicationListener是什么时候添加到当前上下文中的呢?显然,最合理的时机就是在Bean初始化之后。
至此,整个Spring的事件发布和监听机制已经介绍完毕。作为总结,我们可以通过一张图描述整个过程中所涉及的类层关系。
应该说,Spring中的事件处理机制虽然有点复杂,但功能丰富,架构设计也很优雅。上图中所展示的类层结构为开发人员自己实现一套自定义的事件驱动架构提供了很好的参考价值。
Spring事件处理机制在Dubbo框架中的应用
现在,我们已经理解了Spring中如何实现事件发布和监听的过程,接下来让我们看看它在开源框架中的实际应用。今天我们讨论框架仍然是Dubbo。
我们来到Dubbo服务端的启动类ServiceBean,该类的定义如下所示。
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware
显然,ServiceBean实现了Spring的ApplicationListener等接口,该接口的onApplicationEvent方法如下所示。代码X。
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
export();
}
}
可以看到Dubbo的ServiceBean类从ApplicationContext监听ContextRefreshedEvent事件。也就是说,只要Spring容器发布了ContextRefreshedEvent事件,Dubbo接收到该事件之后就会执行export方法,这个export方法会完成Dubbo服务的对外发布。
那么,Spring在什么时候会发布ContextRefreshedEvent事件呢?这个问题的答案,实际上在我们前面的讨论中已经明确了。一旦AbstractApplicationContext的refresh方法被调用,ContextRefreshedEvent事件就会被发布。作为总结,我们可以得到如下所示的执行流程图。
可以看到,Dubbo框架正是基于Spring框架所提供的事件发布和监听机制,完成了与Spring框架的无缝整合。
通过本讲内容的学习,我们掌握了Spring中事件处理的实现过程,这也为实现自定义的事件驱动架构提供了很好的基础,很多实现上的细节可供我们参考。在日常开发过程中,事件处理是一个常见的需求。在分布式环境下,我们可以通过消息中间件完成这个操作。而在单体系统内部,基于对本文中ApplicationEventPublisher和ApplicationListener这两个核心接口及其各个实现类的理解,我们可以抽象出更加轻量级的、符合具体业务场景的事件处理机制。