基于版本:Guava 22.0

Wiki:EventBus

0. EventBus简介

提供了发布-订阅模型,可以方便的在EventBus上注册订阅者,发布者可以简单的将事件传递给EventBus,EventBus会自动将事件传递给相关联的订阅者。

支持同步/异步模式。

只能用于线程间通信。

1. EventBus类图

EventBus是同步实现

AsyncEventBus是异步实现

2. 代码实例

        EventBus eventBus = new EventBus();

        eventBus.register(new Object() {
@Subscribe
public void listen(Object subReport) throws Exception {
System.out.println("receive object event!");
} @Subscribe
public void listen(Integer subReport) throws Exception {
System.out.println("receive integer event!");
}
}); eventBus.post(Integer.valueOf(1));

这段代码的输出如下:

receive integer event!
receive object event!

可以看到我们声明了一个EventBus,然后向其注册了两个订阅者(含有Subscribe注解的方法),然后调用post方法向EventBus发布了一条消息。

消息类型是Integer,由于订阅者的关注对象是Integer与Object,都与这条消息有关,所以两个订阅者都收到了通知。

但是这个发布-订阅模式是如何实现的呢?我们下面来逐步分析一下EventBus的源码。

3. EventBus.register()

EventBus.register
/**
* Registers all subscriber methods on {@code object} to receive events.
*
* @param object object whose subscriber methods should be registered.
*/
public void register(Object object) {
subscribers.register(object);
} SubscriberRegistry.register
/**
* Registers all subscriber methods on the given listener object.
*/
void register(Object listener) {
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);//获取传入的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);//线程安全的修改subscribers对象
} eventSubscribers.addAll(eventMethodsInListener);
}
} /**
* Returns all subscribers for the given listener grouped by the type of event they subscribe to.
分析传入的对象,遍历其中所有含有Subscribe注解而且只含有一个参数的方法,然后将其包装成订阅者并返回。
*/
private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {//getAnnotatedMethods方法最终会调用到下面的SubscriberRegistry.getAnnotatedMethodsNotCached方法,这个方法会用反射处理传入的clazz及其所有的父类,提取出含有Subscribe注解并且有且只有一个参数的方法
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];//获取这个方法的唯一参数的Class
methodsInListener.put(eventType, Subscriber.create(bus, listener, method));//将EventBus,订阅者对象,订阅者方法包装一下并放入map,后续触发事件的时候会用到
}
return methodsInListener;
} SubscriberRegistry.getAnnotatedMethodsNotCached
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();//连clazz的父类也会处理
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
for (Class<?> supertype : supertypes) {
for (Method method : supertype.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {//含有Subscribe注解,而且不是合成的方法
// 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());
}

从SubscriberRegistry.getAnnotatedMethods到SubscriberRegistry.getAnnotatedMethodsNotCached的跳转会涉及到Guava的LoadingCache,其他的逻辑都非常直观。

register方法的大概流程如下:

a. 解析传入的Object对象,用反射分析对应的class及其所有父类,找到所有用Subscribe注解修饰,而且只有一个参数的方法。由这个方法可以生成订阅者对象。

b. 解析这个方法的参数(订阅者订阅的事件类型)

c. 每个EventBus在初始化时都会与一个SubscriberRegistry关联,这个SubscriberRegistry内部维护了一个名为subscribers的ConcurrentMap,这个ConcurrentMap的 key是EventBus关心的事件类型,value是订阅了这些事件的订阅者的集合,subscribers在后续发布事件的流程中非常有用(可以通过发布的事件类型,找到这个事件所关联的所有订阅者,并通知这些相关的订阅者)。现在用a,b中的信息来更新subscribers,由于subscribers可能被并发操作,所以用到了ConcurrentMap.putIfAbsent方法以保证线程安全。

总之,调用register方法会更新subscribers,subscribers中含有事件与订阅者的关联关系。

4. EventBus.post()

EventBus.post
public void post(Object event) {
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);//获取发布事件所关联的所有订阅者(包括event的所有父类/接口所关联的订阅者)
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);//用分派器触发所有订阅者的订阅方法,分派器有同步/异步的不同实现
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));//如果这个事件没有订阅者,则将这个事件包装为DeadEvent事件,然后重新触发post方法
}
} SubscriberRegistry.getSubscribers
/**
* Gets an iterator representing an immutable snapshot of all subscribers to the given event at
* the time this method is called.
*/
Iterator<Subscriber> getSubscribers(Object event) {
ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());//flattenHierarchy方法会解析出event的class,及其所有父类/接口的class List<Iterator<Subscriber>> subscriberIterators =
Lists.newArrayListWithCapacity(eventTypes.size()); for (Class<?> eventType : eventTypes) {//遍历event关联的所有class,找到这些class关联的所有订阅者,添加到subscriberIterators中
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
if (eventSubscribers != null) {
// eager no-copy snapshot
subscriberIterators.add(eventSubscribers.iterator());
}
} return Iterators.concat(subscriberIterators.iterator());
} SubscriberRegistry.flattenHierarchy
/**
* Flattens a class's type hierarchy into a set of {@code Class} objects including all
* superclasses (transitively) and all interfaces implemented by these superclasses.
*/
@VisibleForTesting
static ImmutableSet<Class<?>> flattenHierarchy(Class<?> concreteClass) {
try {
return flattenHierarchyCache.getUnchecked(concreteClass);//这里会用Guava的TypeToken工具解析出concreteClass的class,以及所有父类/接口的class并返回
} catch (UncheckedExecutionException e) {
throw Throwables.propagate(e.getCause());
}
}

post方法的大概流程如下:

a. 用Guava的TypeToken工具解析Event的class,以及Event的所有父类/接口的class,所有订阅了这些class的订阅者后续都会收到通知

b. 调用EventBus关联的Dispatcher的dispatcher方法,将Event发布给所有关联的订阅者。

下一步我们会解析Dispatcher,以及Dispatcher是如何实现EventBus的同步/异步语义的。

5. EventBus的默认Dispatcher

EventBus
/**
* Creates a new EventBus named "default".
*/
public EventBus() {
this("default");
} /**
* Creates a new EventBus with the given {@code identifier}.
*
* @param identifier a brief name for this bus, for logging purposes. Should be a valid Java
* identifier.
*/
public EventBus(String identifier) {
this(
identifier,
MoreExecutors.directExecutor(),//由当前线程直接运行提交任务的“线程池”
Dispatcher.perThreadDispatchQueue(),
LoggingHandler.INSTANCE);
} Dispatcher.perThreadDispatchQueue()
static Dispatcher perThreadDispatchQueue() {
return new PerThreadQueuedDispatcher();
} Dispatcher.PerThreadQueuedDispatcher
/**
* Implementation of a {@link #perThreadDispatchQueue()} dispatcher.
*/
private static final class PerThreadQueuedDispatcher extends Dispatcher { // This dispatcher matches the original dispatch behavior of EventBus. /**
* Per-thread queue of events to dispatch.
*/
private final ThreadLocal<Queue<Event>> queue =
new ThreadLocal<Queue<Event>>() {
@Override
protected Queue<Event> initialValue() {
return Queues.newArrayDeque();
}
};//为每个调用post方法的工作线程维护一个发布队列,工作线程独立操作这个队列完成时间发布流程,所以是线程安全的 /**
* Per-thread dispatch state, used to avoid reentrant event dispatching.
*/
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));//将发布事件+事件所关联的所有订阅者包装成一个Event对象,并提交至发布队列 if (!dispatching.get()) {
dispatching.set(true);
try {
Event nextEvent;
while ((nextEvent = queueForThread.poll()) != null) {//尝试从发布队列中poll元素
while (nextEvent.subscribers.hasNext()) {//遍历发布事件的所有订阅者,向订阅者派发事件,由于EventBus默认使用的线程池是MoreExecutors.directExecutor(),所以实际上发布者会串行而且同步的向事件的所有订阅者派发事件,直到全部派发结束,post方法才会返回,所以EventBus在默认情况下是同步的。
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;
}
}
} Subscriber.dispatchEvent
/**
* Dispatches {@code event} to this subscriber using the proper 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));
}
}
});
} Subscriber.invokeSubscriberMethod
/**
* Invokes the subscriber method. This method can be overridden to make the invocation
* synchronized.
*/
@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;
}
}

同步语义的实现关键在于

a. 使用的线程池为MoreExecutors.directExecutor(),这实际上不能算是一个真正的线程池,所有提交到这个池子里的任务,都是由提交任务的线程自身来执行的。

b. EventBus为每个调用post方法的线程维护了一个发布队列,工作线程会将事件提交到这个私有队列里,然后逐个通知事件所关联的订阅者,由于使用的线程池是MoreExecutors.directExecutor(),所以这个过程实际上是完全串行而且同步执行的,调用post方法的工作线程实际上会在将事件通知给所有订阅者后才会返回,从而实现了同步的EventBus语义。

6. AsyncEventBus的Dispatcher

AsyncEventBus
public AsyncEventBus(Executor executor) {
super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE); Dispatcher.legacyAsync()
static Dispatcher legacyAsync() {
return new LegacyAsyncDispatcher();
} Dispatcher.LegacyAsyncDispatcher
private static final class LegacyAsyncDispatcher extends Dispatcher { // This dispatcher matches the original dispatch behavior of AsyncEventBus.
//
// We can't really make any guarantees about the overall dispatch order for this dispatcher in
// a multithreaded environment for a couple reasons:
//
// 1. Subscribers to events posted on different threads can be interleaved with each other
// freely. (A event on one thread, B event on another could yield any of
// [a1, a2, a3, b1, b2], [a1, b2, a2, a3, b2], [a1, b2, b3, a2, a3], etc.)
// 2. It's possible for subscribers to actually be dispatched to in a different order than they
// were added to the queue. It's easily possible for one thread to take the head of the
// queue, immediately followed by another thread taking the next element in the queue. That
// second thread can then dispatch to the subscriber it took before the first thread does.
//
// All this makes me really wonder if there's any value in queueing here at all. A dispatcher
// that simply loops through the subscribers and dispatches the event to each would actually
// probably provide a stronger order guarantee, though that order would obviously be different
// in some cases. /**
* Global event queue.
*/
private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
Queues.newConcurrentLinkedQueue();//公用的,支持并发访问的发布队列
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {//将Event+订阅者包装成EventWithSubscriber,放入发布队列中
queue.add(new EventWithSubscriber(event, subscribers.next()));
} EventWithSubscriber e;
while ((e = queue.poll()) != null) {
e.subscriber.dispatchEvent(e.event);//从发布队列中提取EventWithSubscriber,然后将事件发布给订阅者,这一过程是发送到AsyncEventBus初始化时提供的线程池里执行的,所以是异步的。也就是说post方法返回的时候,可能发布过程尚未完成,还有订阅者没有收到消息。
}
} private static final class EventWithSubscriber {
private final Object event;
private final Subscriber subscriber; private EventWithSubscriber(Object event, Subscriber subscriber) {
this.event = event;
this.subscriber = subscriber;
}
}
}

异步语义实现的关键在于:

a. 发布事件使用的线程池是创建AsyncEventBus时传入的线程池,一般来说都是正常的含有多个工作线程的线程池,提交到线程池里的任务可以被异步执行

b. LegacyAsyncDispatcher中维护了一个公用的ConcurrentLinkedQueue,这个AsyncEventBus收到的所有Event都会被put到这个队列中,put完Event的线程同时也会从这个队列中poll Event,然后submit这些Event到线程池中,如果这个线程池是普通的含有工作线程的线程池,那么submit完Event之后post方法即可立即返回,传递Event给订阅者的任务会由线程池里的工作线程完成。这样就实现了异步语义。

总的来说,EventBus的源码还是比较清晰易懂的,实现手法也非常优雅,值得我们学习。

Guava源码学习(五)EventBus的更多相关文章

  1. Guava源码解析之EventBus

    最近看Elastic-Job源码,看到它里面实现的任务运行轨迹的持久化,使用的是Guava的AsyncEventBus,一个内存级别的异步事件总线服务,实现了简单的生产-消费者模式,从而在不影响任务执 ...

  2. Guava源码学习(二)Ordering

    基于版本:Guava 22.0 Wiki:Ordering 0. Ordering简介 Guava的Ordering提供了链式风格的比较器的实现,我们可以用Ordering轻松构建复杂的比较器. 1. ...

  3. Guava源码学习(零)前言

    Guava是由Google出品的Java类库,功能强大且易用. 后续我会用多篇博客介绍Guava的使用方法,以及从源码层面分析其实现原理. 分析次序基于Guava的官方Wiki 基于版本:Guava ...

  4. [spring源码学习]五-BeanPostProcessor的使用

    一.接口描述 spring提供了一个接口类-BeanPostProcessor,我们叫他:bean的加工器,应该是在bean的实例化过程中对bean做一些包装处理,里边提供两个方法 public in ...

  5. spring源码学习五 - xml格式配置,如何解析

    spring在注入bean的时候,可以通过bean.xml来配置,在xml文件中配置bean的属性,然后spring在refresh的时候,会去解析xml配置文件,这篇笔记,主要来记录.xml配置文件 ...

  6. Guava源码学习(一)Optional

    基于版本:Guava 22.0 Wiki:Using and avoiding null 0:Optional简介 null在很多场景下会引发问题,NullPointerException困扰过无数的 ...

  7. Guava源码学习(三)ImmutableCollection

    基于版本:Guava 22.0 Wiki:Immutable collections 0. ImmutableCollection简介 类似于JDK的Collections.unmodifiableX ...

  8. Guava源码学习(四)新集合类型

    基于版本:Guava 22.0 Wiki:New collection types 0. 简介 Guava提供了很多好用的集合工具,比如Multiset和BiMap,本文介绍了这些新集合类型的使用方式 ...

  9. async-validator 源码学习笔记(五):Schema

    系列文章: 1.async-validator 源码学习(一):文档翻译 2.async-validator 源码学习笔记(二):目录结构 3.async-validator 源码学习笔记(三):ru ...

随机推荐

  1. 强烈推荐android初学者,android进阶者看看这个系列教程

    强烈推荐android初学者,android进阶者看看这个系列教程 转载 2015年05月30日 23:05:44 695 为什么要研究Android,是因为它够庞大,它够复杂,他激起了我作为一个程序 ...

  2. 关于JavaScript设计模式的学习(二)

    第二部分来了,是关于结构型的,同样的,还是在简书中,GitHub上也有代码示例和详细注释 简书:http://www.jianshu.com/p/face1be4b846 github:https:/ ...

  3. mysql基础知识点整理

    数据库与数据表的创建.修改.删除 创建数据库: Create database 数据库名 删除数据库: drop database 数据库名 修改字符集为utf8并指定校对集(mysql默认字符集为l ...

  4. JMeter学习笔记(九) 参数化1--函数助手:_CSVRead

    1.函数助手:_CSVRead 1)准备数据文件 ,文件可以是.csv格式,.dat格式,txt格式等 2)打开函数助手,生成参数 3)添加HTTP请求,引用参数 4)执行HTTP请求,察看结果树中的 ...

  5. Tornado详解

    1.Tornado路由系统 1.1 Tornado程序示例 新建一个tornadodemo.py, import tornado.ioloop import tornado.web user_info ...

  6. Linux下samba编译与安装(Ubuntu和嵌入式linux)

    Ubuntu[i386-linux下安装过程] 1.安装samba $ sudo apt-get install samba $ sudo apt-get install smbfs (可选) $ s ...

  7. MVC学习笔记----@Helper标签(HelperMethod方法)和HtmlExtesion扩展

    1,HtmlHelper扩展 http://www.cnblogs.com/willick/p/3428413.html http://www.cnblogs.com/zengdingding/p/5 ...

  8. jQuery选择器之类选择器

    类选择器,顾名思义,通过class样式类名来获取节点. 描述: $('.class') 类选择器,相对于id选择器来说,效率相对会低一些,但是优势就是可以多选. 同样的jQuery在实现上,对于类选择 ...

  9. js文字效果

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  10. Scala 基础(2)—— 基本数据结构

    1. Scala 的面向对象 在学习 Java 的时候,我们说 Java 是一门面向对象的语言,然而 Java 其实并没有完全遵守“一切皆对象”这一准则. 例如:Java 的8种基本数据类型 & ...