Google-Guava-EventBus源码解读
Guava是Google开源的一个Java基础类库,它在Google内部被广泛使用。Guava提供了很多功能模块比如:集合、并发库、缓存等,EventBus是其中的一个module,本篇结合EventBus源码来谈谈它的设计与实现。
概要
首先,我们先来预览一下EventBus模块的全部类图:
类并不是多而且几乎没有太多继承关系。
下面,我们来看一下各个类的职责:
- EventBus:核心类,代表了一个事件总线。Publish事件也由它发起。
- AsyncEventBus:在分发事件的时候,将其压入一个全局队列的异步分发模式。
- Subscriber:对某个事件的处理器抽象,封装了事件的订阅者以及处理器,并负责事件处理(该类的类名及其语义有些不明确,后续会谈到)。
- SubscriberRegistry:订阅注册表,它用于存储Subscriber跟Event的对应关系,以便于EventBus在publish一个事件时,可以找到它对应的Subscriber。
- Dispatcher:事件分发器,它定义了事件的分发策略。
- @Subscribe:用于标识事件处理器的注解,当EventBus publish一个事件后,相应的Subscriber将会得到通知并执行事件处理器。
- @AllowConcurrentEvents:该注解跟@Subscribe一同使用,标识该订阅者的处理方法为线程安全的,该注解还用于标识该方法将可能会被EventBus在多线程环境下执行。
- DeadEvent:死信(没有订阅者关注的事件)对象。
- SubscribeExceptionHandler:订阅者抛出异常的处理器。
- SubscribeExceptionContext:订阅者抛出异常的上下文对象。
分“类”解读
EventBus
- identifier:事件总线的标识,这说明在一个应用里是可以有多个EventBus的。如果不指明它的值,它将以“default”作为其默认名称。
- executor:它是Executor接口的实例,用于对订阅者处理事件方法的执行。这里需要注意的是,该字段的实例化是在EventBus内部构造器中,并不是从外部注入进来的,另外真正的执行订阅者方法的时机也不由EventBus负责,而是由Subscriber负责,因此该字段会被公开给外部访问。
- exceptionHandler:它是SubscribeExceptionHandler的实例,用于处理订阅者在执行事件处理方法时抛出的异常。EventBus可以接收一个外部定义的异常处理器,也可以采用内部缺省的日志记录处理器。
- subscribers:订阅者注册表,用于存储所有的事件以及事件处理器、订阅对象的对应关系。
- dispatcher:事件分发器,用于分发事件给订阅对象的事件处理器,该对象在EventBus构造方法内部初始化,默认的实现是PerThreadQueuedDispatcher,该分发器将事件存入队列,并保证在同一个线程上发送的事件能够按照他们发布的顺序被分发给所有的订阅者。
- register:注册subscriber;
- unregister:移除注册过的subscriber;
- post:发布事件;
你可以将EventBus看做是一个代理,这些方法真正的实现者都是上面的这些对象。
AsyncEventBus
Subscriber
static Subscriber create(EventBus bus, Object listener, Method method) {return isDeclaredThreadSafe(method)? new Subscriber(bus, listener, method): new SynchronizedSubscriber(bus, listener, method);}
它接收三个参数:
- bus:EventBus的实例,通过它来获取事件的执行器(executor)
- listener:真实的订阅者对象
- method:订阅对象的事件处理方法的Method实例
final void dispatchEvent(final Object event) {executor.execute(new Runnable() {@Overridepublic void run() {try {invokeSubscriberMethod(event);} catch (InvocationTargetException e) {bus.handleSubscriberException(e.getCause(), context(event));}}});}
它调用一个多线程执行器来执行事件处理器方法。
SubscriberRegistry
register
void register(Object listener) {Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {Class<?> eventType = entry.getKey();Collection<Subscriber> eventMethodsInListener = entry.getValue();CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);if (eventSubscribers == null) {CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();eventSubscribers = MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);}eventSubscribers.addAll(eventMethodsInListener);}}
它首先获得一个Multimap实例(它是Google Guava集合框架提供的一个多值Map类型,也就是说一个key可以对应多个value),该Multimap用于存储事件类型对应的该订阅者内所有关于该事件的处理器方法集合,其key为事件的Class类型。这里在for循环的中通过asMap获取其map视图,即可将Multimap对应的多个值存储到一个Collection中。
unregister
findAllSubscribers
private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();Class<?> clazz = listener.getClass();for (Method method : getAnnotatedMethods(clazz)) {Class<?>[] parameterTypes = method.getParameterTypes();Class<?> eventType = parameterTypes[0];methodsInListener.put(eventType, Subscriber.create(bus, listener, method));}return methodsInListener;}
同样涉及这个问题的,还有根据事件类型获取Subscriber实例的方法:getSubscribers。
getSubscribers
Iterator<Subscriber> getSubscribers(Object event) {ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());List<Iterator<Subscriber>> subscriberIterators =Lists.newArrayListWithCapacity(eventTypes.size());for (Class<?> eventType : eventTypes) {CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);if (eventSubscribers != null) {// eager no-copy snapshotsubscriberIterators.add(eventSubscribers.iterator());}}return Iterators.concat(subscriberIterators.iterator());}
Dispatcher
dispatcher用于分发事件给Subscriber。它内部实现了多个分发器用于提供在不同场景下不同的事件顺序性。Dispatcher是一个抽象类,定义了一个核心抽象方法:
abstract void dispatch(Object event, Iterator<Subscriber> subscribers);
该方法用于将一个指定的事件分发给所有的订阅者。
另外在Dispatcher提供了三个不同的分发器实现:
PerThreadQueuedDispatcher
它比较常用,针对每个线程构建一个队列用于暂存事件对象。保证所有的事件都按照他们publish的顺序从单一的线程上发出。保证从单一线程上发出,没什么特别的地方,主要是在内部定义了一个队列,将其放在ThreadLocal中,用以跟特定的线程关联。
LegacyAsyncDispatcher
ImmediateDispatcher
DeadEvent
- source:事件源(通常指发布事件的EventBus对象)
- event:事件对象
总结
Google-Guava-EventBus源码解读相关推荐
- Google guava cache源码解析1--构建缓存器(2)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. CacheBuilder-->maximumSize(long size) /*** 指定cache中 ...
- Google guava cache源码解析1--构建缓存器(3)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 下面介绍在LocalCache(CacheBuilder, CacheLoader)中调用的一些方法: Ca ...
- spark源码解读3之RDD中top源码解读
更多代码请见:https://github.com/xubo245/SparkLearning spark源码解读系列环境:spark-2.0.1 (20161103github下载版) 1.理解 输 ...
- Bert系列(三)——源码解读之Pre-train
https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...
- Ubuntu 16.04下Caffe-SSD的应用(四)——ssd_pascal.py源码解读
前言 caffe-ssd所有的训练时的参数,全部由ssd_pascal.py来定义,之后再去调用相关的脚本和函数,所以想要训练自己的数据,首先要明白ssd_pascal.py各个定义参数的大体意思. ...
- Android 开源框架之 Android-async-http 源码解读
开源项目链接 Android-async-http仓库:https://github.com/loopj/android-async-http android-async-http主页:http:// ...
- 源码解读之Pre-train
pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现实(在Google Cloud TPU v2 上训练BERT-Base要花费近 ...
- RTC 月度小报 5 月 | WebRTC M83、SOLO 源码解读、实时美声……
本月亮点速览 RTC开发者社区: CSDN专访RTC编程大赛获奖者 如何高效实现PSTN/SIP接入实时音视频网络 在线教育的创新模式及AI应用实践 开源与技术科普: WebRTC M83 Relea ...
- golang源码解读之 net.Dial
1.调用 例子 conn, err := net.Dial("tcp", "google.com:80") if err != nil {// handle e ...
最新文章
- 解析codepage和charset的含义及其应用
- Trinity 1.1 发布,Linux 桌面环境
- html 子级选择器,css3子选择器(选择某个标签做内容)
- [书目20071127]图书 时间陷阱 目录
- 中医:看脚十秒钟可知身体疾病
- 掌握神经网络,我应该学习哪些至关重要的知识点?
- FastJson的使用方法总结
- 图像处理中ct图的通道是多少_新一代安检CT机,智能安防领域又一明星产品
- getconnection java_在MyEclipse用java写的一个GetConnection1.java,用于连接MySQL,却总是出错。(没有财富值了,见谅!)...
- 实用小程序,快速求A类不确定度(物理实验),保留六位
- linux学习书籍汇总 值得推荐的linux学习书籍
- ModifyStyle()
- Internet结构和ISP
- 男男java_猛男学习Java的第二天
- 使用Visual Paradigm如何复制表格
- 揭露安利!!!!!!
- web前端数据表格有合并项的一种简单实现方法
- 面试问烂了的测试用例: 登录界面的测试用例
- 接口中的变量为什么不能是普通变量,只能是static final
- 本周大新闻|沙特PIF再投Magic Leap,周融资超5.1亿美元
热门文章
- duration java_Java Duration类| minusMinutes()方法与示例
- 1补码 2补码_8085微处理器中8位数字的1和2的补码
- mui实现分享功能_MUI 分享功能(微信、QQ 、朋友圈)
- linux ida 图形界面,linux – IDA在屏幕内不起作用
- Ubuntu14.04设置开机可以以root用户身份登录
- c语言 包络算法,包络检测C程序
- java毛玻璃_模糊效果(毛玻璃效果)
- 操作系统(四)文件管理
- django-celery定时任务以及异步任务and服务器部署并且运行全部过程
- 去除list集合中重复项的几种方法