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分析的更多相关文章

  1. guava中eventbus注解使用

    guava是 google 几个java核心类库的集合,包括集合.缓存.原生类型.并发.常用注解.基本字符串操作和I/O等等.学会使用该库相关api的使用,能使我们代码更简洁,更优雅,本章节我们来谈谈 ...

  2. Guava 源码分析(Cache 原理 对象引用、事件回调)

    前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...

  3. Guava 源码分析之Cache的实现原理

    Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...

  4. Guava中Lists.partition(List, size) 方法懒划分/懒分区

    目录 Guava中Lists.partition(List, size) 方法懒划分/懒分区 背景 分析 总结 Guava中Lists.partition(List, size) 方法懒划分/懒分区 ...

  5. Guava中Predicate的常见用法

    Guava中Predicate的常见用法 1.  Predicate基本用法 guava提供了许多利用Functions和Predicates来操作Collections的工具,一般在 Iterabl ...

  6. Java经典类库-Guava中的函数式编程讲解

    如果我要新建一个java的项目,那么有两个类库是必备的,一个是junit,另一个是Guava.选择junit,因为我喜欢TDD,喜欢自动化测试.而是用Guava,是因为我喜欢简洁的API.Guava提 ...

  7. Google Guava中的前置条件

    前置条件:让方法调用的前置条件判断更简单. Guava在Preconditions类中提供了若干前置条件判断的实用方法,我们建议[在Eclipse中静态导入这些方法]每个方法都有三个变种: check ...

  8. (转)ffmpeg 中 av_read_frame_internal分析

    作者: chenwei1983    时间: 2012-3-5 04:21 PM标题: ffmpeg 中 av_read_frame_internal分析                       ...

  9. Google guava 中的Monitor

    synchronized 自从Java提供了多线程编程,我们经常需要处理这样的情况:在特定的时间,我们需要限制访问,确保只有一个线程访问我们的代码.Java提供了同步关键字synchronized来实 ...

随机推荐

  1. Jmeter 函数助手对话框简单使用

    第一步 打开Jmeter,Tools > 函数助手对话框  下拉框选择_CSVRead ,如下图所示: 第二步: 准备一份测试数据,保存在本地盘符里,数据如下图所示: 第三步,填数据,  (1) ...

  2. python机器学习的开发流程

    标准机器学习的开发编程流程 关注公众号"轻松学编程"了解更多. 一.流程 标准机器学习的开发编程流程: 1.获取数据(爬虫.数据加载.业务部门获取) 2.数据建模(摘选样本数据(特 ...

  3. [Luogu P2827] 蚯蚓 (巧妙的模拟)

    题面: 传送门:https://www.luogu.org/problemnew/show/P2827 Solution 看到这题,我们肯定会有一个大胆想法. 那就是直接用堆模拟这个过程. 对于q,我 ...

  4. Android操作系统及APP

    1.   Android操作系统 1.1.  介绍 Android操作系统最初由Andy Rubin开发,主要支持手机.2005年8月由Google收购注资.第一部Android智能手机发布于2008 ...

  5. P6773 [NOI2020]命运

    整体DP 很明显计算答案需要用容斥计算,如果暴力容斥的话,就是枚举哪些路径不符合条件,在这些路径的并集中的边都不能取,其他边任意取,设当前取了$i$条路径,那么对答案的贡献是$(-1)^i2^{n-1 ...

  6. C# 集合类(二)

    C# 集合类自己经常用到: 数组(Array).动态数组(ArrayList).列表(List).哈希表(Hashtable).字典(Dictionary),对于经常使用的这些数据结构,做一个总结,便 ...

  7. 浮动布局问题多,还是用inline-block吧

    说说知识陈旧的问题. 目前我的前端开发知识积累最大的问题就是版本问题,也许我已经经历了很多,尝试了很多, 但是有些知识的版本已经过时了,而我还没有来得及更新它们.更悲剧的可能是有些部分我还没有意识到. ...

  8. GSO和TSO

    http://www.cnhalo.net/2016/09/13/linux-tcp-gso-tso/ TSO(TCP Segmentation Offload): 是一种利用网卡来对大数据包进行自动 ...

  9. Cache一致性和内存模型

    -------------------------------

  10. uiautomatorviewer 启动报错

    我的sdk是随着AndroidStudio中下载下来的,这样做是有好处的,建议直接装个AndroidStudio这样管理sdk很方便,虽然很大,但是总比后期发现有问题好一点.最近在研究Appium要定 ...