文档章节

Guava中EventBus的使用和实现

ddfeye
 ddfeye
发布于 2015/12/12 11:30
字数 1477
阅读 844
收藏 6

EventBus 看到这个名字,相信你已经大概明白是个什么东东了。
Event就是事件,Bus 这里不是巴士汽车的意思,在计算机领域一般翻译为总线。那么EventBus就是装载Event,然后做分发的。若做过java swing开发,肯定记得XXEvent,以及XXEventListener接口,都会有一个方法onEvent(XXEvent event)。没错EventBus干的就是相同的事情,但借着采用jdk1.5引入的注解,使得开发消息发布与订阅系统非常简洁方便,无需实现接口或继承基类。

关于消息发布与订阅系统的应用场景,其实在服务端系统开发中也经常用到,游戏中的任务系统或者一些电商系统的活动系统中经常会用到。例如,游戏中收集几个道具就完成某个任务,获取奖励。或者很多平台的签到系统,累计签到多少天获得奖励。这里就使用Guava中的EventBus开发一个签到系统。

public class SignInEvent {
    // 签到天数
    private int count;
    public SignInEvent(int count) {
        this.count = count;
    }
    public int getCount() {
        return count;
    }
}
public class SignInProcessor {
    @Subscribe
    public void signIn(SignInEvent event) {
        int count = event.getCount();
        // TODO 根据签到的天数发放奖励
        System.out.println("签到" + count + "天");
    }
}
public class AppTest {
    @Test
    public void eventBusTest() {
        EventBus signInEventBus = new EventBus("SignInEventBus");
        SignInProcessor processor = new SignInProcessor();
        signInEventBus.register(processor);
        signInEventBus.post(new SignInEvent(2));
    }
}

事件处理类SignInProcessor并不需要实现某个接口,只需要在需要处理的方法上加上@Subscribe注解,这里也并没有限制SignInProcessor只能处理SignInEvent,要在SignInProcessor添加其他事件的处理逻辑也只需要添加一个方法,添加注解@Subscribe,第一个参数传入需要处理的事件实例。

public class SignInProcessor {
    @Subscribe
    public void signIn(SignInEvent event) {
        int count = event.getCount();
        // TODO 根据签到的天数发放奖励
        System.out.println("签到" + count + "天");
    }
    
    @Subscribe
    public void logout(LogoutEvent event) {
        // 获取登出的时间
        Date date = event.getTime();
        // TODO
    }
}

这里先说下,EventBus是如何确定一个类中的某个方法来处理相应的事件的呢,Guava中提供了一个接口

interface SubscriberFindingStrategy {
  Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object source);
}

并提供了一个机遇注解的实现

class AnnotatedSubscriberFinder implements SubscriberFindingStrategy

findAllSubscribers的实现逻辑

@Override
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;
}

1. 获取listener对象类,以及父类中所有被@Subscriber注解的方法,而且这个方法有且只有一个参数
2. 获取这些方法的第一个参数的类型
3. 创建EventSubscriber,保存listener实例,以及对应的Method实例,方便以后反射调用方法处理事件

讲完如何查找处理方法后,再看下具体的事件对象是如何发布出去的呢?这里的具体逻辑就在EventBus中的post方法中

public void post(Object event) {
    // 获取事件对象的所有父类以及父类实现的接口
    // 获取父类的目的为的是可以把event发布给某些接收父类Event的处理方法
    Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
    boolean dispatched = false;
    for (Class<?> eventType : dispatchTypes) {
        // subscribersByType 是一个非线程安全的集合,所以在操作的时候需要添加锁
        subscribersByTypeLock.readLock().lock();
        try {
            Set<EventSubscriber> wrappers = subscribersByType.get(eventType);
            if (!wrappers.isEmpty()) {
                dispatched = true;
                for (EventSubscriber wrapper : wrappers) {
                    // 放入当前线程对应的Queue中,这里使用到ThreadLocal变量
                    enqueueEvent(event, wrapper);
                }
            }
        } finally {
            subscribersByTypeLock.readLock().unlock();
        }
    }
    // 若未能找到对应Event的处理器而且当前事件的类型不是DeadEvent就把传入的事件包装成DeadEvent
    if (!dispatched && !(event instanceof DeadEvent)) {
        post(new DeadEvent(this, event));
    }
    // 从当前线程的对应的队列中事件处理器和事件,并处理事件
    dispatchQueuedEvents();
}

从EventBus中的post方法处理逻辑来看,事件的分发和处理是在同一个线程中同步处理的。
但是很多时候事件的处理逻辑比较复杂耗时,需要将事件的分发和处理异步。事件的处理不阻塞分发的主线程。
Guava提供了AsyncEventBus,就是将分发和处理异步化。AsyncEventBus的实现并不复杂。
AsyncEventBus 继承自 EventBus, 将EventBus中的存放待分发的事件队列eventsToDispatch从ThreadLocal<Queue<EventWithSubscriber>>换成了ConcurrentLinkedQueue<EventWithSubscriber> 支持多个线程并发访问获取事件处理,AsyncEventBus的构造函数需要传入一个Executor,可以根据实际需要传入定制的线程池。

某些场景下,在事件处理类的实例中需要保存事件相关状态,多线程并发访问的时候可能出现问题。Guava提供了注解@AllowConcurrentEvents,它的用途标记多个线程能否同时调用同一个事件处理器的处理方法来处理相依的事件。
具体处理逻辑在AnnotatedSubscriberFinder类中的

private static EventSubscriber makeSubscriber(Object listener, Method method) {
    EventSubscriber wrapper;
    // 这里判断事件处理方法是否有被@AllowConcurrentEvents注解
    if (methodIsDeclaredThreadSafe(method)) {
        wrapper = new EventSubscriber(listener, method);
    } else {
        // 若没有被@AllowConcurrentEvents注解,多个线程在处理的时候就需要同步调用该处理器来处理事件
        wrapper = new SynchronizedEventSubscriber(listener, method);
    }
    return wrapper;
}

好了,Guava中的EventBus相关使用及实现基本讲完了。其实并不复杂。需要你对下面的相关类和处理机制比较熟悉
1. ThreadLocal
2. ConcurrentLinkedQueue
3. 反射获取类的父类和接口,这里使用Guava中的TypeToken封装类,已经反射方法调用
4. ReentrantReadWriteLock
5. Cache,Guava对常用的缓存做了一些封装,下一篇将会讲到

© 著作权归作者所有

ddfeye
粉丝 0
博文 4
码字总数 4470
作品 0
广州
私信 提问
Guava库学习:学习Guava EventBus(一)EventBus

在软件开发过程中,对象信息的分享以及相互直接的协作是必须的,困难在于确保对象之间的沟通是有效完成的,而不是拥有成本高度耦合的组件。当对象对其他组件的责任有太多的细节时,它被认为是...

Realfighter
2014/12/29
4.4K
0
为JFinal添加event消息事件

在之前使用spring mvc的时候,在复杂的下单和支付中有一部分功能使用的Spring事件驱动模型去完成!具体优点不啰嗦了,发现涛哥有篇文章讲得比较详细:[详解Spring事件驱动模型][1] 最初准备基...

如梦技术
2015/04/28
1K
20
Android EventBus二三事

废话很多的前言 EventBus,也即事件总线。在[wiki][event_monitor]上有关于Event Monitor的一个说法: Event monitoring makes use of a logical bus to transport event occurrences from so......

苦辛味
2014/09/21
1K
0
guava eventbus源码解析

说在前面 本文转自“天河聊技术”微信公众号 事件驱动模型设计是一种优雅的程序设计方式,实现有很多,原理都是发布与订阅,观察者设计模式实现,java自带的实现、spring ioc的事件驱动模型,...

天河2018
2018/07/07
240
0
Guava学习笔记:EventBus

  EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的...

pior
2015/04/01
205
0

没有更多内容

加载失败,请刷新页面

加载更多

目标检测中 yolo 的mAP是什么含义?

mAP定义及相关概念 P => precision,即 准确率 R => recall,即 召回率 PR曲线 = >即 以 precision 和 recall 作为 纵、横轴坐标 的二维曲线。一般来说,precision 和 recall 是 鱼与熊掌 的...

小松1
4分钟前
1
0
用jdk1.8的断言来做非空判断

Assert.notNull(user, "没有获得登录用户信息"); 看源码如下: public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgum......

architect刘源源
8分钟前
2
0
免费节假日api每一时间更新 2020年 部分节假日安排

根据国务院办公厅关于2020年部分节假日安排的通知国办发明电〔2019〕16号.免费节假日api每一时间更新 2020年 部分节假日安排 http://tool.bitefu.net/jiari/ 各省、自治区、直辖市人民政府,...

xiaogg
11分钟前
3
0
2018NOIP各省一等奖分数线

提高组 普及组

SamXIAO
20分钟前
5
0
常见的PPT时间轴怎么制作,这几种方法你要知道

在PPT当中,时间轴是一个非常重要的一个版块,很多PPT会用它来表示公司的发展历程和项目进度。但是对于PPT时间轴的制作很多人做法是一条直线上添几个点,标注出事件就完成了,可是这样也太过...

TeFuiro
26分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部