一、架构速读

传统上,Java的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的。设计EventBus就是为了取代这种显示注册方式,使组件间有了更好的解耦。EventBus不是通用型的发布-订阅实现,不适用于进程间通信。

架构图如下:

二、简单使用

步骤如下:

1.构造一个事件总线
2.构造一个事件监听器
3.把事件监听器注册到事件总线上
4.事件总线发布事件,触发监听器方法

主测试类如下:

 package guava.eventbus;

 import com.google.common.eventbus.EventBus;

 /**
* @Description 主测试类
* @author denny
* @date 2018/7/18 上午9:54
*/
public class MainTest { public static void main(String[] args) {
// 1.构造一个事件总线
EventBus eventBus = new EventBus("test"); // 2.构造一个事件监听器
EventListener listener = new EventListener(); // 3.把事件监听器注册到事件总线上
eventBus.register(listener); // 4.事件总线发布事件,触发监听器方法
eventBus.post(new TestEvent1(1));
eventBus.post(new TestEvent2(2));
// 事件3是事件2子类,虽然监听器只订阅了父类事件2,一样可以监听到子类
eventBus.post(new TestEvent3(3));
// 未被订阅的事件,用DeadEvent可订阅
eventBus.post(new TestEvent4(4));
}
}

事件监听器如下:

 package guava.eventbus;

 import com.alibaba.fastjson.JSON;
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.Subscribe; /**
* @Description 事件监听器
* @author denny
* @date 2018/7/18 上午9:53
*/
public class EventListener { private int message = 0; /**
* @Description 订阅事件1
* @param event 事件1
* @return void
* @author denny
* @date 2018/7/18 上午9:46
*/
@Subscribe
public void onEvent1(TestEvent1 event) {
message = event.getMessage();
System.out.println("EventListener onEvent1 监听器接收到消息:"+message);
} /**
* @Description 订阅事件2
* @param event 事件2
* @return void
* @author denny
* @date 2018/7/18 上午9:59
*/
@Subscribe
public void onEvent2(TestEvent2 event) {
message = event.getMessage();
System.out.println("EventListener onEvent2 监听器接收到消息:"+message);
} /**
* @Description 死亡事件(该事件没有被订阅会触发)
* @param event 未订阅事件
* @return void
* @author denny
* @date 2018/7/18 上午9:59
*/
@Subscribe
public void onDeadEvent(DeadEvent event) {
System.out.println("EventListener DeadEvent 有消费没有被订阅!!!!event="+ event.toString());
}
}

事件类:

 package guava.eventbus;

 /**
* @Description 事件1
* @author denny
* @date 2018/7/18 上午9:54
*/
public class TestEvent1 { private final int message; /**
* 构造方法
* @param message
*/
public TestEvent1(int message) {
this.message = message;
System.out.println("TestEvent1 事件message:"+message);
} public int getMessage() {
return message;
}
} /**
* @Description 事件2
* @author denny
* @date 2018/7/18 上午9:54
*/
public class TestEvent2 { private final int message; /**
* 构造方法
* @param message
*/
public TestEvent2(int message) {
this.message = message;
System.out.println("TestEvent2 事件message:"+message);
} public int getMessage() {
return message;
}
} /**
* @Description 事件3
* @author denny
* @date 2018/7/18 上午9:54
*/
public class TestEvent3 extends TestEvent2{ private final int message; /**
* 构造方法
* @param message
*/
public TestEvent3(int message) {
super(message);
this.message = message;
System.out.println("TestEvent2 事件message:"+message);
} @Override
public int getMessage() {
return message;
}
} /**
* @Description 事件4
* @author denny
* @date 2018/7/18 上午9:54
*/
public class TestEvent4 { private final int message; /**
* 构造方法
* @param message
*/
public TestEvent4(int message) {
this.message = message;
System.out.println("TestEvent4 事件message:"+message);
} public int getMessage() {
return message;
}
}

运行结果如下:

TestEvent1 事件message:1
EventListener onEvent1 监听器接收到消息:1 ---》触发订阅的事件1
TestEvent2 事件message:2
EventListener onEvent2 监听器接收到消息:2---》触发订阅的事件2(一个监听器可以订阅多个事件)
TestEvent2 事件message:3
TestEvent2 事件message:3
EventListener onEvent2 监听器接收到消息:3---》订阅事件2,可触发订阅子类事件3
TestEvent4 事件message:4
EventListener DeadEvent 有消费没有被订阅!!!!event="DeadEvent{source=EventBus{test}, event=guava.eventbus.TestEvent4@19e1023e}"---》事件4没有被订阅,触发DeadEvent死亡事件。

注意:

1.事件总线EventBus

不提供单列,用户自己看着用~

2.监听器Listener

1)监听器使用@Subscribe标记的方法(参数为自定义事件),即可实现事件的监听。要监听多个事件,就写多个方法(每个方法都用@Subscribe标记)即可。

2)注意一定要把Listener注册到eventbus上。

三、源码剖析

源码版本为:guava-22.0.jar,先来回顾下第一节的样例代码:

 public static void main(String[] args) {
// 1.构造一个事件总线
EventBus eventBus = new EventBus("test"); // 2.构造一个事件监听器
EventListener listener = new EventListener(); // 3.把事件监听器注册到事件总线上
eventBus.register(listener); // 4.事件总线发布事件,触发监听器方法
eventBus.post(new TestEvent1(1));
eventBus.post(new TestEvent2(2));
// 事件3是事件2子类,虽然监听器只订阅了父类事件2,一样可以监听到子类
eventBus.post(new TestEvent3(3));
// deadEvent未被订阅的事件,供用户自行处理
eventBus.post(new TestEvent4(4));
}

如上图,虽然是google封装的事件总线,但是依然是观察者模式,那么核心就是发布、订阅。下面就从这两个方面来看一下源码,看看有没有值得借鉴的地方。

3.1 核心类速读

1.EventBus事件总线

核心方法

register:把监听器中申明的所有订阅事件方法注册到SubscriberRegistry(订阅者注册器)中。

post:发布事件给所有已注册过的订阅者,最终开启线程完成订阅方法。

具体如下图:

 @Beta
public class EventBus {
private final String identifier;//事件总线标识:用于自定义标识这个事件总线
private final Executor executor;//默认的线程执行器,用于把事件转发给订阅者
private final SubscriberRegistry subscribers = new SubscriberRegistry(this);//订阅注册器
private final Dispatcher dispatcher;//事件转发器
//构造器:使用默认字符串
public EventBus() {
this("default");
}
//构造器:使用自定义字符串
public EventBus(String identifier) {
this(
identifier,
MoreExecutors.directExecutor(),
Dispatcher.perThreadDispatchQueue(),
LoggingHandler.INSTANCE);
}
//注册监听者中申明的所有订阅方法(@Subscribe标记的),用以接收事件
public void register(Object object) {
subscribers.register(object);
}
// 解除订阅
public void unregister(Object object) {
subscribers.unregister(object);
} //发布事件给所有已注册过的订阅者
public void post(Object event) {
// 找到事件的所有订阅者
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
// 事件转发器,把事件转发给订阅者
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// 如果该事件即没有订阅者,也没事DeadEvent,那么封装成DeadEvent并重新发布
post(new DeadEvent(this, event));
}
...省略非重要方法 }

2.Subscriber订阅者

核心方法:dispatchEvent使用executor线程执行器,单独开启线程执行订阅方法。

 class Subscriber {

   /**
* 构造 */
static Subscriber create(EventBus bus, Object listener, Method method) {
return isDeclaredThreadSafe(method)
? new Subscriber(bus, listener, method)
: new SynchronizedSubscriber(bus, listener, method);
} /** 订阅者所属的事件总线*/
@Weak private EventBus bus; /** 监听器 listener*/
@VisibleForTesting final Object target; /** 订阅者方法 */
private final Method method; /** 线程执行器,用来分发事件给订阅者 */
private final Executor executor;
/** 构造器:使用事件总线、监听器、订阅方法 */
private Subscriber(EventBus bus, Object target, Method method) {
this.bus = bus;
this.target = checkNotNull(target);
this.method = method;
method.setAccessible(true); this.executor = bus.executor();
} /**
* 使用executor线程执行器,执行订阅方法*/
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
} /**
* 调用订阅者方法*/
@VisibleForTesting
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
try {
method.invoke(target, checkNotNull(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;
}
}
...
}

本节我们了解了2个核心类EventBus(注册监听器、发布事件)、Subscriber订阅者(执行订阅方法),下面我们从源码流程上来串连一遍。

3.2.注册监听器

我们从注册监听器开始看,eventBus.register(listener); 如下图所示:

 public void register(Object object) {
subscribers.register(object);
} /**
* 把listener中申明的所有订阅方法都注册
*/
void register(Object listener) {
// 获取该监听器类型对应的所有订阅方法,key是事件类型,value是订阅者集合
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
// 遍历map Map<K, Collection<V>>
for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
//key:事件类型
      Class<?> eventType = entry.getKey();
//value:订阅者集合
Collection<Subscriber> eventMethodsInListener = entry.getValue();
//从subscribers并发map 中获取事件对应的事件订阅者set,subscribers:private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers = Maps.newConcurrentMap();
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
//不存在,就构造
if (eventSubscribers == null) {
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
}
//存在,把订阅者集合添加进subscribers
eventSubscribers.addAll(eventMethodsInListener);
}
}

1.调用SubscriberRegistry的register(listener)来执行注册监听器。

2.register步骤如下:

EventBus-包含-》SubscriberRegistry-包含-》ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers 用以维护事件和订阅者的映射

1). findAllSubscribers从缓存中获取该监听器类型对应的所有订阅方法,key是event class,value是Subscriber集合

2).遍历map,把订阅者集合添加进SubscriberRegistry-》subscribers。

其中findAllSubscribers详细代码如下:

   /**
* 返回所有该监听器订阅者,以事件分组
*/
private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
//从缓存中获取该监听器类型对应的所有订阅方法,遍历塞进Multimap
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
}
return methodsInListener;
}

如上图,

1.方法getAnnotatedMethods:从缓存中取 listener中订阅方法的不可变列表,

2.遍历塞进Multimap:一个key,多个value,每次put进去,往Collection<V>中add(value)。

getAnnotatedMethods源码如下:

 private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
return subscriberMethodsCache.getUnchecked(clazz);
} private static final LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache =
CacheBuilder.newBuilder()
.weakKeys()
.build(
new CacheLoader<Class<?>, ImmutableList<Method>>() {
@Override
public ImmutableList<Method> load(Class<?> concreteClass) throws Exception {
return getAnnotatedMethodsNotCached(concreteClass);
}
});

如上图,我们发现这里用到了google cache来做缓存,关于google cache飞机票

这个cache的源码注释翻译如下:一个线程安全的缓存,包含从每个类到该类中的所有方法和所有超类的映射,这些超类都用{@code @Subscribe}注释。缓存是跨该类的所有实例共享的;如果创建了多个EventBus实例,并且在所有这些实例上注册了同一个类的对象,这将大大提高性能。

值得借鉴点:EventBus-包含->SubscriberRegistry-->static final subscriberMethodsCache,即所有EventBus类的实例共享一个静态cache,性能高且线程安全。

下面来看看具体怎么获取的订阅事件方法(监听器@Subscribe注解的订阅事件方法),核心方法getAnnotatedMethodsNotCached如下:

 private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
//获取超类class集合
Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
    //遍历超类
for (Class<?> supertype : supertypes) {
5   //遍历超类中的所有定义的方法  
  for (Method method : supertype.getDeclaredMethods()) {
//如果方法上有@Subscribe注解
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
// 方法的参数类型数组
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);
        // 去重并添加进map
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, method);
}
}
}
}
    // map转ImmutableList
return ImmutableList.copyOf(identifiers.values());
}

3.3 发布事件

eventBus.post(new TestEvent1(1));

调用事件转发器Dispatcher,分发事件给订阅者,

 public void post(Object event) {
//从注册器中获取当前事件对应的订阅者集合:eventBus-包含-》SubscriberRegistry-包含-》ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
//如果该事件即没有订阅者,也没事DeadEvent,那么封装成DeadEvent并重新发布
7 post(new DeadEvent(this, event));
}
}

Dispatcher是个抽象类,有多个内部类复写不同dispatch方法。EventBus默认构造时使用PerThreadQueuedDispatcher,即每个线程一个待转发事件队列。如下图所示:

   private static final class PerThreadQueuedDispatcher extends Dispatcher {

     // This dispatcher matches the original dispatch behavior of EventBus.

     /**
* 每个线程待转发事件队列
*/
private final ThreadLocal<Queue<Event>> queue =
new ThreadLocal<Queue<Event>>() {
@Override
protected Queue<Event> initialValue() {
return Queues.newArrayDeque();
}
}; /**
* 每个线程的转发状态,用于避免重入事件转发,初始化状态为fasle,即不在转发。
*/
private final ThreadLocal<Boolean> dispatching =
new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
}; @Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
checkNotNull(subscribers);
Queue<Event> queueForThread = queue.get();
// 事件添加进队列
queueForThread.offer(new Event(event, subscribers));
// 当前不在转发中,开始转发
if (!dispatching.get()) {
dispatching.set(true);
try {
Event nextEvent;
// 迭代从线程队列中取事件
while ((nextEvent = queueForThread.poll()) != null) {
// 迭代事件的Iterator<Subscriber> subscribers,调用订阅者转发事件
while (nextEvent.subscribers.hasNext()) {
nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
}
}
} finally {
dispatching.remove();
queue.remove();
}
}
} private static final class Event {
private final Object event;
private final Iterator<Subscriber> subscribers; private Event(Object event, Iterator<Subscriber> subscribers) {
this.event = event;
this.subscribers = subscribers;
}
}
}

可见核心方法在dispatchEvent,调用订阅者转发事件

   final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
} /**
* 执行订阅者方法 */
@VisibleForTesting
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
try {
method.invoke(target, checkNotNull(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;
}
}

四、总结

本文我们先快速了解Google EventBus总体架构,然后从一个简单的应用入手知道如何使用,再深入剖析源码彻底了解原理,并分析了有哪些值得借鉴的地方,最后我们来看一下传统观察者模式和EventBus的区别:

传统观察者模式和EventBus区别
 

监听者管理

监听特定事件

把监听者注册到生产者

按事件超类监听 检测没有监听者的事件 分发事件

传统观察者模式

用列表管理监听者,还要考虑线程同步;或者使用工具类 定义相应的事件监听者类

调用事件生产者的register方法,开发者必须知道所有事件生产者的类型,才能正确地注册监听者

很困难,需要开发者自己去实现匹配逻辑 在每个事件分发方法中添加逻辑代码 开发者自己写代码,包括事件类型匹配、异常处理、异步分发
EventBus 内部已经实现了监听者管理

以自定义Event为唯一参数创建方法,

并用Subscribe注解标记。

EventBus.register(Object) EventBus自动把事件分发给事件超类的监听者 EventBus会把所有发布后没有监听者处理的事件包装为DeadEvent

EventBus.post(Object)

异步分发可以直接用EventBus的子类AsyncEventBus

==参考==

官方介绍https://github.com/google/guava/wiki/EventBusExplained

Guava 12:Guava EventBus源码剖析的更多相关文章

  1. Guava cacha 机制及源码分析

    1.ehcahce 什么时候用比较好:2.问题:当有个消息的key不在guava里面的话,如果大量的消息过来,会同时请求数据库吗?还是只有一个请求数据库,其他的等待第一个把数据从DB加载到Guava中 ...

  2. 《python解释器源码剖析》第12章--python虚拟机中的函数机制

    12.0 序 函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作.当然在调用函数时,会干什么来着.对,要在运行时栈中创建栈帧,用于函数的执行. 在python ...

  3. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  4. Java多线程9:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

  5. Node 进阶:express 默认日志组件 morgan 从入门使用到源码剖析

    本文摘录自个人总结<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 章节概览 morgan是express默认的日志中间件, ...

  6. socket_server源码剖析、python作用域、IO多路复用

    本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...

  7. 【Android】EventBus 源码解析

    EventBus 源码解析 本文为 Android 开源项目实现原理解析 中 EventBus 部分项目地址:EventBus,分析的版本:ccc2771,Demo 地址:EventBus Demo分 ...

  8. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  9. 玩转Android之Picasso使用详详详详详详解,从入门到源码剖析!!!!

    Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写.如 ...

随机推荐

  1. JS中for in 与 for of

    // 数组var A=[4,6,74,67]; for in:拿到的是数组下标 for (let i in A){ console.log(i); } //0,1,2,3 for of:拿到的是数组元 ...

  2. 20190226_xlVba提取查新标题和关键词

    Sub MainProc() Dim Sht As Worksheet Dim Wb As Workbook Set Wb = Application.ThisWorkbook Set Sht = W ...

  3. php 安装 redis扩展

    https://segmentfault.com/a/1190000009422920 wget 源码编译

  4. 『计算机视觉』Mask-RCNN_训练网络其二:train网络结构&损失函数

    Github地址:Mask_RCNN 『计算机视觉』Mask-RCNN_论文学习 『计算机视觉』Mask-RCNN_项目文档翻译 『计算机视觉』Mask-RCNN_推断网络其一:总览 『计算机视觉』M ...

  5. js里获取页面高度和文档高度

    $(window).height() 和 $(document).height()的区别 $(window).height()代表了当前可见区域的大小,$(document).height()则代表了 ...

  6. mongoose中connect()、createConnection()和connection的区别和作用

    转文:原文 1 mongoose简介 在使用mongodb数据库开发项目中,nodejs环境下可能会使用到mongoose模块连接并操作mongodb数据库.mongoose模块相当于Java中的数据 ...

  7. python -- 面向对象 - 反射

    1.isinstance ,type, issubclass       isinstance:判断给的对象是否是**类型       type:返回**对象的数据类型       issubclas ...

  8. python--jianja2

    一:渲染模版 要渲染一个模板,通过render_template方法即可. @app.route('/about/')def about():return render_template('about ...

  9. unity中 UGUI的按下、拖动接口事件的实现

    using UnityEngine; using System.Collections.Generic; using DG.Tweening; using UnityEngine.EventSyste ...

  10. JS之event flow

    DOM事件流 1.定义: DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素节点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为DOM ...