Guava EventBus 源码解析

1,865 阅读8分钟

简介

Guava,谷歌出品的优秀脚手架,广泛应用于各大Java项目中,其源码也被称作《Effective Java》一书的最佳实践,值得广大程序员学习和参考。

EventBus,Guava 提供的事件总线,使得事件和订阅之间得到解耦,今天我们就来看看 EventBus 的使用方式和内部实现。

架构和组件图

使用方式

首先,添加 Guava 依赖

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.0-jre</version>
        </dependency>

以下先来演示 EventBus 的基本使用,考虑以下业务场景:

用户填写完注册表单,使用短信和邮件,给用户发送注册成功信息。

对应代码实现为:

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

public class Application {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        eventBus.register(new RegisteredEventEmailSubscriber());
        eventBus.register(new RegisteredEventSmsSubscriber());

        // 注册逻辑...
        // 注册后
        RegisteredEvent event = new RegisteredEvent();
        event.name = "张小胖";
        event.mobile = "18800000001";
        event.email = "zhangxiaopang@gmail.com";

        eventBus.post(event);
    }
}

class RegisteredEventSmsSubscriber {
    @Subscribe
    public void handle(RegisteredEvent event) {
        System.out.println("sms to " + event.mobile + ": Welcome " + event.name);
    }
}

class RegisteredEventEmailSubscriber {
    @Subscribe
    public void handle(RegisteredEvent event) {
        System.out.println("email to " + event.email + ": Welcome " + event.name);
    }
}

class RegisteredEvent {
    public String name;
    public String mobile;
    public String email;
}

执行后,可以看到控制台输出

email to zhangxiaopang@gmail.com: Welcome 张小胖
sms to 18800000001: Welcome 张小胖

用简图来表示一下上述代码逻辑:

以上代码的好处是,注册完成逻辑,和后续逻辑完全解耦。如果看着不是很明白,建议先去网上搜下“观察者设计模式”,EventBus就是观察者设计模式的工程落地,本文侧重源码分析,对于应用不作过多阐述。

EventBus 的构造过程

EventBus 的构造函数大概有这些

EventBus()
EventBus(String identifier);
EventBus(SubscriberExceptionHandler exceptionHandler)
EventBus(String identifier, Executor executor, Dispatcher dispatcher, SubscriberExceptionHandler exceptionHandler)

构造函数的逻辑也很纯粹,就是给 EventBus 实例的属性赋值。我们来看看主要属性都有哪些

// 日志
private static final Logger logger = Logger.getLogger(EventBus.class.getName());

// 总线标识符,一个系统内可以有多组总线,之间的事件不共享
private final String identifier;

// Executor juc 里的接口,用于并发任务处理。guava 里还基于此接口实现了一个 DirectExecutor,其实就是不用并发,直接串行去执行每个观察者监听到事件后的逻辑。默认情况下就是这个 Executor
private final Executor executor;

// 观察者执行逻辑抛出的异常处理
private final SubscriberExceptionHandler exceptionHandler;

// 观察者注册中心
private final SubscriberRegistry subscribers = new SubscriberRegistry(this);

// 事件派发器
private final Dispatcher dispatcher;

EventBus 自身代码不多,不到一百行,我们就不展开了。逻辑重点委托给了观察者注册中心(SubscriberRegistry)和 事件派发器(Dispatcher),下面就重点看看这两者的源码。

观察者注册中心(SubscriberRegistry)

顺着 EventBus::register() 的逻辑,只有一行代码

  public void register(Object object) {
    subscribers.register(object);
  }

subscribers 对象是 EventBus 初始化的时候就构建好了

private final SubscriberRegistry subscribers = new SubscriberRegistry(this);

可以看出,EventBus 自己并没有去记录观察者的信息,而是委托给了 SubscriberRegistry 去注册观察者。

接下来我们就来看看 SubscriberRegistry 这个类。先看看它所拥有的属性

  // 指向对应的 EventBus 实例
  private final EventBus bus;

  // 使用一个 HashMap,来记录每个事件(如上文的 RegisteredEvent)的多个观察者。观察者是对每个 @Subscribe 注解对应方法的一个封装,下文会有分析
  private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers = Maps.newConcurrentMap();

再进入 SubscriberRegistry::register(Object Listener)

  void register(Object listener) {
    // 找到对应 listener 类的所有 Subscirbes,也就是所有包含 @Subscribe 注解的方法。
    // Multimap 是 Guava 基于 java Map 数据结构的一个变种,用 json 表示其结构大概为 ["listener1": ["subscriber1", "subscriber2"], "listener2": ["subcriber3"]]
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    // 从 listenerMethods 中,取出每个 Subscriber,注册到 eventSubscribers 属性中去
    for (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<>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      eventSubscribers.addAll(eventMethodsInListener);
    }
  }

继续追进 SubscriberRegistry::findAllSubscribers(Object listener),看看是如何从一个观察者类中构建出 Subscriber

  private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
    Class<?> clazz = listener.getClass();
    // 去查找 listener 类对应的 @Subscribe 注解的方法
    for (Method method : getAnnotatedMethods(clazz)) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Class<?> eventType = parameterTypes[0];
      // 构建出 Subscriber 对象
      methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
    }
    return methodsInListener;
  }

这里我们可以顺便看下 Subscriber 对象的构造方法

  /**
  * @param bus 相关联的 EventBus 总线
  * @param target 被观察的对象(事件),如上文的 AfterReisgerEvent
  * @param method 对应的观察方法 Method
  * 
  * 另外还有个 exector, 这个实例就是实现了 java.util.concurrent.Execotor 接口,到时候用来串行或并行来这行观察者的方法。
  * 关于 exector,会在事件派发阶段有更详细的说明。
  */
  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();
  }

事件注册阶段,重点看下 Subscriber 的构建逻辑,关于 Subscriber 的执行逻辑,会在时间派发阶段展开。

我们接着钻进 SubscriberRegistry::getAnnotatedMethods(Class<?> clazz) 方法,看看找到 @Subscribe 方法:

  private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
    return subscriberMethodsCache.getUnchecked(clazz);
  }

subscriberMethodsCache 在对象初始化时生成,是一个 Guava LoadingCache 类。 LoadingCache 类主要就是 get(cacheKey) 方法,获取缓存的值,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);
                }
              });

既然是 Cache,我们重点看缓存是如何是构建的,也就是 SubscriberRegistry::getAnnotatedMethodsNotCached() 方法

  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) {
      // 遍历 listener 的每个方法
      for (Method method : supertype.getDeclaredMethods()) {
        // 判断方法上是是否存在 @Subscribe 注解
        if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
          ...
          MethodIdentifier ident = new MethodIdentifier(method);
          if (!identifiers.containsKey(ident)) {
            identifiers.put(ident, method);
          }
        }
      }
    }
    return ImmutableList.copyOf(identifiers.values());
  }

在这里,我们就看到 method.isAnnotationPresent(Subscribe.class) 这个判断方法存在注解的代码,这就和我们当时定义观察者(@SubScribe)的应用代码给联系起来了。

事件派发器(Dispatcher)

事件派发的逻辑,我们可以以示例代码中的 eventBus.post() 作为入口,顺藤摸瓜一直追下去。 点开 post 的代码

public void post(Object event) {
    // 从观察者注册中心,查找对应 event 及 父类event 的观察者
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);

    if (eventSubscribers.hasNext()) {
      // 这里就转交 Dispatcher 去处理观察者要执行的逻辑
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      post(new DeadEvent(this, event));
    }
  }

首先是 subscribers.getSubscribers(event) 这行代码,要留意的是,guava 不仅会找当前 event 的观察者,还会找其父类实例的观察者。 另外,我一开始没搞懂为啥会有 else if post(new DeadEvent(this, event)) 这个逻辑,没有观察者直接结束不就好了嘛,后来我想到的是,好处是可以让业务代码自己定义一个观察者去监听这个事件。如

class DeadEventEmailSubscriber {
    @Subscribe
    public void handle(DeadEvent event) {
        System.out.println("event " + event + " without subscribers");
    }
}

接下来我们重点说说 dispatcher.dispatch(event, eventSubscribers) 的逻辑;

guava EventBus 里 dispatcher 有三种实现,分别是

  1. ImmediateDispatcher,立即处理,串行处理
  2. PerThreadQueuedDispatcher,每个线程一个队列,每个队列的任务串行处理
  3. LegacyAsyncDispatcher,使用线程池,进行异步处理

EventBus 里默认使用的是 PerThreadQueuedDispatcher,但我们先从最简单的 ImmediateDispatcher 讲起,带大家走完观察到任务后的全流程。

  private static final class ImmediateDispatcher extends Dispatcher {
    private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher();

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      checkNotNull(event);
      while (subscribers.hasNext()) {
        // 遍历对应的每个观察者,直接让观察者处理事件
        subscribers.next().dispatchEvent(event);
      }
    }
  }

再看观察者 Subscriber::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));
            }
          }
        });
  }

这里我们能看到,如果有了 InvocationTargetException 异常,则会交给我们初始化 EventBus 时的 SubscriberExceptionHandler 来处理。 至于 invokeSubscriberMethod(event) 里的逻辑,主要就是 method.invoke(target, checkNotNull(event)) 这一行通过反射调用的观察者的观察方法。

至此,我们差不多就走完了事件观察的全部流程。但还要说说 PerThreadQueuedDispatcher 和 LegacyAsyncDispatcher。

先来看看 EventBus 默认使用的 PerThreadQueuedDispatcher

  class PerThreadQueuedDispatcher extends Dispatcher {
    // 线程独享的无界队列
    private final ThreadLocal<Queue<Event>> queue =
        new ThreadLocal<Queue<Event>>() {
          @Override
          protected Queue<Event> initialValue() {
            return Queues.newArrayDeque();
          }
        };

    // 线程独享的一个状态位,为了标记如果有任务正在执行,则不继续执行
    private final ThreadLocal<Boolean> dispatching =
        new ThreadLocal<Boolean>() {
          @Override
          protected Boolean initialValue() {
            return false;
          }
        };

    @Override
    void dispatch(Object event, Iterator<Subscriber> 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) {
            // 事件出队
            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;
      }
    }
  }

主要就是通过 ThreadLocal 搞了个线程独享的队列,每个事件来了先进队列。然后只允许一个线程去队列里取事件,串行派发事件。

到了这里想问下大家,看了上面两个 Dispatcher,有没有发现,一个事件对应的多个观察者,他们的逻辑是串行的,假设某个观察者执行时间特别长,那么就会影响到后续的观察者开始时间。

接下来就要介绍 LegacyAsyncDispatcher,就是为了解决这个观察者串行的问题。 这里还要结合 EventBus 的一个变种 AsyncEventBus 来看,简单看下它的源码

public class AsyncEventBus extends EventBus {
  public AsyncEventBus(String identifier, Executor executor) {
    super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
  }

  public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) {
    super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler);
  }

  public AsyncEventBus(Executor executor) {
    super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
  }
}

留意他的构造函数,和 EventBus 的差别有两个

  1. 让用户从外部传入 Executor,这是为并发执行观察者做基础,guava 默认使用的 Executor 是串行的;
  2. 指定 Dispatcher 使用 Dispatcher.legacyAsync(),也就是 LegacyAsyncDispatcher 的实例;

接下来我们再看下 LegacyAsyncDispatcher 的源码:

class LegacyAsyncDispatcher extends Dispatcher {
    // 一个无界队列
    private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
        Queues.newConcurrentLinkedQueue();

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      while (subscribers.hasNext()) {
        queue.add(new EventWithSubscriber(event, subscribers.next()));
      }

      EventWithSubscriber e;
      while ((e = queue.poll()) != null) {
        e.subscriber.dispatchEvent(e.event);
      }
    }

    // 核心是这里搞了个抽象,将每个 event 和 subscriber 分组,进入队列,借助 executor 来并行处理
    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;
      }
    }
  }

另外说下 PerThreadQueuedDispatcher 和 LegacyAsyncDispatcher 相比有个特性,前者可以保证 event 的处理是有序的,而后者无法保证。

至此,EventBus 的源码就分析结束了,大家自己看的时候,可以紧抓 SubscriberRegistry 和 Dispatcher 这两条线。