前言

EventBus的核心思想是观察者模式 (生产/消费者编程模型) 。

SpringBoot+EventBus使用教程(一)

SpringBoot+EventBus使用教程(二)

通过前面的文章我们已经知道,如何使用eventBus了。我们需要先定义一个Observer(前文中的EventListener类),然后将其注册到eventBus里,通过 @Subscribe 定义消息回调函数。

那我们先看看register(Object object) 和unregister(Object object) 方法。

register (Object object) 解析

 
1
2
3
4
5
6
7
8
9
10
public void register(Object object) {
    Multimap<Class<?>, EventSubscriber> methodsInListener =
        finder.findAllSubscribers(object);
    subscribersByTypeLock.writeLock().lock();
    try {
      subscribersByType.putAll(methodsInListener);
    } finally {
      subscribersByTypeLock.writeLock().unlock();
    }
  }

可以看到是先通过SubscriberFindingStrategy接口里的findAllSubscribers方法获取所有标记了@ Subscribe 注解的方法,其中该接口的具体实现是AnnotatedSubscriberFinder类。放到一个guava里定义的Multimap里。然后是把获取到的methodsInListener放到一个叫subscribersByType的 guava里定义的SetMultimap里 。

 
1
2
3
4
5
6
7
8
9
10
11
public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create();
    Class<?> clazz = listener.getClass();
    for (Method method : getAnnotatedMethods(clazz)) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Class<?> eventType = parameterTypes[0];
      EventSubscriber subscriber = makeSubscriber(listener, method);
      methodsInListener.put(eventType, subscriber);
    }
    return methodsInListener;
  }

findAllSubscribers方法里,最重要的是methodsInListener,它的结构可以简单理解为一个map,其中key是eventType,在我前文写的例子中就是com.sww.eventbus.domain.MessageEvent,其中value是subscriber,就是例子中的com.sww.eventbus.listener.EventListener#onMessageEvent。

总之,一句话就是先通过标记找到所有已经注册进来的观察者,然后存放到容器里备用。

那unregister就是从容器删除它们,

unRegister (Object object) 解析

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void unregister(Object object) {
    Multimap<Class<?>, EventSubscriber> methodsInListener = finder.findAllSubscribers(object);
    for (Entry<Class<?>, Collection<EventSubscriber>> entry :
          methodsInListener.asMap().entrySet()) {
      Class<?> eventType = entry.getKey();
      Collection<EventSubscriber> eventMethodsInListener = entry.getValue();
 
      subscribersByTypeLock.writeLock().lock();
      try {
        Set<EventSubscriber> currentSubscribers = subscribersByType.get(eventType);
        if (!currentSubscribers.containsAll(eventMethodsInListener)) {
          throw new IllegalArgumentException(
              "missing event subscriber for an annotated method. Is " + object + " registered?");
        }
        currentSubscribers.removeAll(eventMethodsInListener);
      } finally {
        subscribersByTypeLock.writeLock().unlock();
      }
    }
  }

post( Object event)解析

有了观察者,下面就是发送事件了,阅读过前文会知道是通过eventBus.post(Object event)来发送事件消息。那咱们来看看这个post方法。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void post(Object event) {
    Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
 
    boolean dispatched = false;
    for (Class<?> eventType : dispatchTypes) {
      subscribersByTypeLock.readLock().lock();
      try {
        Set<EventSubscriber> wrappers = subscribersByType.get(eventType);
 
        if (!wrappers.isEmpty()) {
          dispatched = true;
          for (EventSubscriber wrapper : wrappers) {
            enqueueEvent(event, wrapper);
          }
        }
      } finally {
        subscribersByTypeLock.readLock().unlock();
      }
    }
 
    if (!dispatched && !(event instanceof DeadEvent)) {
      post(new DeadEvent(this, event));
    }
 
    dispatchQueuedEvents();
  }

该方法就是从之前的容器subscribersByType里获取到eventType对应的观察者,然后组装成EventWithSubscriber放到队列里。

 
1
2
3
void enqueueEvent(Object event, EventSubscriber subscriber) {
    eventsToDispatch.get().offer(new EventWithSubscriber(event, subscriber));
  }

然后就是最后的dispatchQueuedEvents(),经过一层层深入进去,可以发现wrapper.handleEvent(event),其中 handleEvent方法就是最终的关键了

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void handleEvent(Object event) throws InvocationTargetException {
    checkNotNull(event);
    try {
      method.invoke(target, new Object[] { event });
    } catch (IllegalArgumentException e) {
      throw new Error("Method rejected target/argument: " + event, e);
    } catch (IllegalAccessException e) {
      throw new Error("Method became inaccessible: " + event, e);
    } catch (InvocationTargetException e) {
      if (e.getCause() instanceof Error) {
        throw (Error) e.getCause();
      }
      throw e;
    }
  }

就是通过Java的反射机制实现。

需要说明的是,如果没有订阅者注册到要发送的event事件上,并且该event不是DeadEvent,那么它将被包装成DeadEvent中并重新发布。也就是其中这三行代码索要做的

 
1
2
3
if (!dispatched && !(event instanceof DeadEvent)) {
      post(new DeadEvent(this, event));
    }

本文系本人原创,同步更新在我的独立博客http://791202.com/上,如要转载,请注明出处!

EventBus原理解析的更多相关文章

  1. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  2. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  3. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  4. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  5. GeoHash原理解析

    GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...

  6. alibaba-dexposed 原理解析

    alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...

  7. 支付宝Andfix 原理解析

    支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...

  8. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

  9. Request 接收参数乱码原理解析三:实例分析

    通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...

随机推荐

  1. Azure Application Gateway (1) 入门

    <Windows Azure Platform 系列文章目录> 请读者注意,Azure Application Gateway在ASM模式下,只能通过PowerShell创建 具体可以参考 ...

  2. 网络设备模拟器 GNS3

    https://www.gns3.com/support/docs/linux-installation sudo dpkg --add-architecture i386 sudo add-apt- ...

  3. hdu 1166 树状数组 线段树入门

    点修改 区间求和 #include <cstdio> #include <cstdlib> #include <cmath> #include <map> ...

  4. 关于上次我写的那个ATM程序 ,程序没有什么错,但是有些麻烦,两个类中有好多成员函数重复,因此我把ATM重新写了一边。

    不好意思!但是现在这个程序比上次那个好多了,而且没有重复,程序看起来比较简练,以下是新程序: #include<iostream>#include<string>using n ...

  5. Android Activity和Fragment传递数据

    1.Activity与Activity传递数据 UserLoginActivity.java: Intent welcomePage = new Intent(); Bundle dataBundle ...

  6. Use Zabbix Monitor Find ‘DBCC CheckDB’ Problem

    下面是修改前后的对比截图: 如下图: 下图是确定问题并修改后对比图,左边圈是修改前,右边圈是修改后对比截图:当看到周期性的性能指数,一般是计划性任务引起:通过DMV视图,找到引起等待的原因检查数据库完 ...

  7. Jquery mobile中用Jquery的append()追加的内容没有Jquery mobile的样式

    Jquery Mobile 动态添加块之后, 样式不是JM内定的样式,解决方案如下: $('#content').append(html).enhanceWithin();//Jquery Mobil ...

  8. web前端学习python之第一章_基础语法(二)

    web前端学习python之第一章_基础语法(二) 前言:最近新做了一个管理系统,前端已经基本完成, 但是后端人手不足没人给我写接口,自力更生丰衣足食, 所以决定自学python自己给自己写接口哈哈哈 ...

  9. Linux C++ TCP Socket通信实例

    环境:Linux 语言:C++ 通信方式:TCP 下面用TCP协议编写一个简单的服务器.客户端,其中服务器端一直监听本机的6666号端口.如果收到连接请求,将接收请求并接收客户端发来的消息:客户端与服 ...

  10. 关于Bootstrap自定义图标

    1.网站:http://fontello.com/(可能是网站本身原因,总是加载很慢,需要等待一下) 2.eps格式即AI文件,所需画布大小是512*512的,且需要布满整个画布,否则存储出来的图标显 ...