Guava中EventBus分析
EventBus
1. 什么是EventBus
总线(Bus)一般指计算机各种功能部件之间传送信息的公共通信干线,而EventBus则是事件源(publisher)向订阅方(subscriber)发送订阅事件的总线,它解耦了观察者模式中订阅方和事件源之间的强依赖关系。

2. guava EventBus的构成
下面以guava 19版本的EventBus的源码进行分析。EventBus有三个操作:注册Listener--register(Object Listener),注销Listener--unregister(Object Listener),发布Event--post(Object event)。在19版本之前,listener的注册和注销,事件的发布的工作都是由EventBus完成,在18版本之后,EventBus把Listener的注册和注销的工作委托给SubscriberRegistry, 把事件发布的工作委托给Dispatcher来完成,这样的修改职责分明,代码结构更加清晰了。在并发方面也有大的修改,以前对维护某类事件和其感兴趣的Subscriber的操作用了读写锁来控制对容器的操作,19版本及之后用了并发容器ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>>避免了对锁的使用。

3. SubscriberRegistry
在观察者模式中,事件源中会维护一个Listener的列表,而且向这个事件源注册的Listener一般只会收到一类事件的通知,如果Listener对多个不同类的事件感兴趣,则需要向多个事件源注册。EventBus是怎样实现Listener一次注册,能够知道Listener对那些事件感兴趣的,进而在有某类事件发生时通知到Listener的呢?答案在SubscriberRegistry这个类中。在SubscriberRegister有一个实例属性ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers,它维护了某个事件类型和对其感兴趣的Subscriber的列表。

register(Object listener)的工作就是找出这个Listener对哪些事件感兴趣,然后把这种事件类型和该Listener构建成的Subscriber加到subscribers中。unregister的过程和register类似,只从subscribers删掉Listener感兴趣的事件。下面我们分别看看18版本和19版本register,主要的区别就是一个是加锁的版本,一个用的是并发容器。
18版本:
/**
* Registers all subscriber methods on {@code object} to receive events.
* Subscriber methods are selected and classified using this EventBus's
* {@link SubscriberFindingStrategy}; the default strategy is the
* {@link AnnotatedSubscriberFinder}.
*
* @param object object whose subscriber methods should be registered.
*/
public void register(Object object) {
Multimap<Class<?>, EventSubscriber> methodsInListener =
finder.findAllSubscribers(object);
subscribersByTypeLock.writeLock().lock();
try {
subscribersByType.putAll(methodsInListener);
} finally {
subscribersByTypeLock.writeLock().unlock();
}
}
19版本:
/**
* Registers all subscriber methods on the given listener object.
*/
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);
}
}
SubscriberRegister通过reflection找出该Listener对象(包括其父类)哪些Method用@Subscriber注解了,用@Subscriber注解的方法表示当某件事件发生时,希望收到事件通知。在@Subscriber注解的方法中只能包含一个参数那就是Event,否则会出错。在reflection的时候用LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache缓存了该Listener Class和对应的Method,加快了后面对同一类Listener进行register的效率。用MethodIdentifier作为Map的key来判别Method的是否相等。
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
for (Class<?> supertype : supertypes) {
for (Method method : supertype.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
// TODO(cgdecker): Should check for a generic parameter type and error out
Class<?>[] parameterTypes = method.getParameterTypes();
checkArgument(parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters."
+ "Subscriber methods must have exactly 1 parameter.",
method, parameterTypes.length);
MethodIdentifier ident = new MethodIdentifier(method);
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, method);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}

在构建Subscriber的时候根据方法是否有@AllowConcurrentEvents,分为同步和并发执行method。
4. Dispatcher
Dispatcher发布事件的时候有三种模式:
1. ImmediateDispatcher,来了一个事件则通知对这个事件感兴趣的订阅者。
2. LegacyAsyncDispatcher,会有一个全局的队列ConcurrentLinkedQueue<EventWithSubscriber> queue保存EventWithSubscriber(事件和subscriber),如果被不同的线程poll 不能保证在queue队列中的event是有序发布的。
3. PerThreadQueuedDispatcher,在同一个线程post的Event,执行的顺序是有序的。用ThreadLocal<Queue<Event>> queue来实现每个线程post的Event是有序的,在把事件添加到queue后会有一个ThreadLocal<Boolean> dispatching来判断当前线程是否正在分发,如果正在分发,则这次添加的event不会马上进行分发而是等到dispatching的值为false才进行。这样做的原因是为了防止同一个事件被重复分发。我的理解是这样的:如果没有dispatching这个状态变量,在ThreadA中EventA发布了,ListenerA收到了,ListenerA进行处理,在处理的过程中如果又触发了EventA的发布,如果该线程不结束则会陷入到一种循环中去。

5.EventBus的使用
一般的应用的场景就是在用观察者模式的地方就可以用EventBus进行替代。结合Spring的使用过程如下:
5.1定义Listener

5.2向EventBus注册Listener

5.3发送事件

Guava中EventBus分析的更多相关文章
- guava中eventbus注解使用
guava是 google 几个java核心类库的集合,包括集合.缓存.原生类型.并发.常用注解.基本字符串操作和I/O等等.学会使用该库相关api的使用,能使我们代码更简洁,更优雅,本章节我们来谈谈 ...
- Guava 源码分析(Cache 原理 对象引用、事件回调)
前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...
- Guava 源码分析之Cache的实现原理
Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...
- Guava中Lists.partition(List, size) 方法懒划分/懒分区
目录 Guava中Lists.partition(List, size) 方法懒划分/懒分区 背景 分析 总结 Guava中Lists.partition(List, size) 方法懒划分/懒分区 ...
- Guava中Predicate的常见用法
Guava中Predicate的常见用法 1. Predicate基本用法 guava提供了许多利用Functions和Predicates来操作Collections的工具,一般在 Iterabl ...
- Java经典类库-Guava中的函数式编程讲解
如果我要新建一个java的项目,那么有两个类库是必备的,一个是junit,另一个是Guava.选择junit,因为我喜欢TDD,喜欢自动化测试.而是用Guava,是因为我喜欢简洁的API.Guava提 ...
- Google Guava中的前置条件
前置条件:让方法调用的前置条件判断更简单. Guava在Preconditions类中提供了若干前置条件判断的实用方法,我们建议[在Eclipse中静态导入这些方法]每个方法都有三个变种: check ...
- (转)ffmpeg 中 av_read_frame_internal分析
作者: chenwei1983 时间: 2012-3-5 04:21 PM标题: ffmpeg 中 av_read_frame_internal分析 ...
- Google guava 中的Monitor
synchronized 自从Java提供了多线程编程,我们经常需要处理这样的情况:在特定的时间,我们需要限制访问,确保只有一个线程访问我们的代码.Java提供了同步关键字synchronized来实 ...
随机推荐
- 【开源】使用.Net Core和GitHub Actions实现哔哩哔哩每日自动签到、投币、领取奖励
BiliBiliTool是一个B站自动执行任务的工具,使用.NET Core编写,通过它可以实现B站帐号的每日自动观看.分享.投币视频,获取经验,每月自动领取会员权益.自动为自己充电等功能,帮助我们轻 ...
- 关于appium
一.特点 1.appium是开源的移动端自动化测试框架: 2.appium可以测试原生的.混合的.以及移动端的web项目: "移动原生应用"是指那些用iOS或者 Android S ...
- (C#2,.net framework2.0,Visual Studio 2003)之前版本
(C#2,.net framework2.0,Visual Studio 2003)之前版本归为最初的版本(主要是针对.net framework),其主要定义了最基本的类型.特性. 1.基本的类型 ...
- python数据分析 Numpy基础 数组和矢量计算
NumPy(Numerical Python的简称)是Python数值计算最重要的基础包.大多数提供科学计算的包都是用NumPy的数组作为构建基础. NumPy的部分功能如下: ndarray,一个具 ...
- 小规模DES手写加解密
1.加密 2.解密
- Shell 筛选符合条件的 ELF 文件
0 运行环境 本机系统:Windows 10 虚拟机软件:Oracle VM VirtualBox 6 虚拟机系统:Ubuntu 18 1 引言 - 编译过程 我们知道在 CPU 上执行的是低级别的机 ...
- MCscan-Python-jcvi 共线性画图最后一章更新
经过几轮调试和修改,共线性图终于可以上眼了.如下: 图中红色的为目标基因,蓝色的为reference species目标基因周围15个基因,天蓝色为再往外15个基因,黄色为与reference spe ...
- PyQt5信号与槽关联的两种方式
目录 通过QtDesigner 手动关联的方式 通过QtDesigner 单击菜单栏切换到信号槽编辑模式 单击控件并拖动鼠标到信号的接收对象上,一般为对话框自己,松开鼠标弹出信号和槽选择框 选中cli ...
- centos7安装数据库 (系统包含预装环境mariadb)
查看系统是否安装了MySQL,有些没安装但是有路径,那是因为系统安装了mariadb-libs,对应的配置文件目录:/etc/my.cnf(下面就根据mariadb来安装mysql) 查看系统预装ma ...
- zabbix自动发现的python方式数据生成
前言 zabbix里面有个功能是自动发现,比如文件系统和网卡的获取的时候,因为预先无法知道这个网卡的名称,所以就有了这个自动发现的功能,这里我是因为要用到存储池的自动发现,所以需要对数据进行生成 实现 ...