简介
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 有三种实现,分别是
- ImmediateDispatcher,立即处理,串行处理
- PerThreadQueuedDispatcher,每个线程一个队列,每个队列的任务串行处理
- 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 的差别有两个
- 让用户从外部传入 Executor,这是为并发执行观察者做基础,guava 默认使用的 Executor 是串行的;
- 指定 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 这两条线。