Java EventBus
1.EventBus是什么?
EventBus是guava中的一个工具,官方解释如下:
EventBus允许组件之间通过发布-订阅进行通信,而不需要组件之间显示的注册。它专门设计为了代替使用显示注册的传统的Java进程内事件分发。它不是通用的发布-订阅系统,也不是用于进程间通信的。
Event | 可能发布到总线的任何对象。 |
---|---|
Subscribing | 向EventBus注册侦听器的行为,以便其处理程序方法将接收事件。 |
Listener | 通过公开处理程序方法希望接收事件的对象。 |
Handler method | EventBus用于传递已发布事件的公共方法。处理程序方法由@Subscribe批注标记。 |
Posting an event | 通过EventBus将事件提供给所有侦听器。 |
优点:简化组件之间的通信。是发布者和订阅之间解耦,同时避免了复杂且容易出错的依赖性和生命周期问题。使代码更加简洁
2.使用
//Example
// Class is typically registered by the container. class EventBusChangeRecorder {
//订阅事件 @Subscribe public void recordCustomerChange(ChangeEvent e) { recordChange(e.getChange()); } } //注册监听类 eventBus.register(new EventBusChangeRecorder()); // much later public void changeCustomer() ChangeEvent event = getChangeEvent();
//发布事件 eventBus.post(event); }
3.源码解析
以下源码来源Guava版本-20.0
3.1事件总线
@Beta public class EventBus { private static final Logger logger = Logger.getLogger(EventBus.class.getName()); private final String identifier; private final Executor executor; private final SubscriberExceptionHandler exceptionHandler; private final SubscriberRegistry subscribers = new SubscriberRegistry(this); private final Dispatcher dispatcher; /** * Creates a new EventBus named "default". */ public EventBus() { this("default"); } /** * Creates a new EventBus with the given {@code identifier}. */ public EventBus(String identifier) { this( identifier, MoreExecutors.directExecutor(), Dispatcher.perThreadDispatchQueue(), LoggingHandler.INSTANCE); } /** * Creates a new EventBus with the given {@link SubscriberExceptionHandler}. */ public EventBus(SubscriberExceptionHandler exceptionHandler) { this( "default", MoreExecutors.directExecutor(), Dispatcher.perThreadDispatchQueue(), exceptionHandler); } EventBus( String identifier, Executor executor, Dispatcher dispatcher, SubscriberExceptionHandler exceptionHandler) { this.identifier = checkNotNull(identifier); this.executor = checkNotNull(executor); this.dispatcher = checkNotNull(dispatcher); this.exceptionHandler = checkNotNull(exceptionHandler); } /** * Returns the identifier for this event bus. */ public final String identifier() { return identifier; } /** * Returns the default executor this event bus uses for dispatching events to subscribers. */ final Executor executor() { return executor; } /** * 处理上下文订阅者抛出的异常 */ void handleSubscriberException(Throwable e, SubscriberExceptionContext context) { checkNotNull(e); checkNotNull(context); try { exceptionHandler.handleException(e, context); } catch (Throwable e2) { // if the handler threw an exception... well, just log it logger.log( Level.SEVERE, String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e), e2); } } /** * 注册所有的订阅者为了接收事件 */ public void register(Object object) { subscribers.register(object); } /** * 注销所有已注册的订阅方法 * 要注册的对象,如果之前没有该对象没有注册过抛出IllegalArgumentException */ 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并重新发布 post(new DeadEvent(this, event)); } } @Override public String toString() { return MoreObjects.toStringHelper(this).addValue(identifier).toString(); } ...省略异常日志处理方法 } }
- subscribers是SubscriberRegistry类型的,实际上EventBus在添加、移除和遍历观察者的时候都会使用该实例的方法,所有的观察者信息也都维护在该实例中.
- executor是事件分发过程中使用到的线程池,可以自己实现; dispatcher是Dispatcher类型的子类,用来在发布事件的时候分发消息给监听者,它有几个默认的实现,分别针对不同的分发方式;
- exceptionHandler是SubscriberExceptionHandler类型的,它用来处理异常信息,在默认的EventBus实现中,会在出现异常的时候打印出log,当然我们也可以定义自己的异常处理策咯。
通过SubscriberRegistry了解如何注册和取消注册以及遍历。我们需要在EventBus中维护几个映射,以便在发布事件的时候找到并通知所有的监听者,首先是事件类型->观察者列表的映射。
EventBus中发布事件是针对各个方法的,我们将一个事件对应的类型信息和方法信息等都维护在一个对象中,在EventBus中就是观察者Subscriber. 然后,通过事件类型映射到观察者列表,当发布事件的时候,只要根据事件类型到列表中寻找所有的观察者并触发监听方法即可。 在SubscriberRegistry中通过如下数据结构来完成这一映射:
/** * 通过事件类型索引所有的注册订阅者 * CopyOnWriteArraySet值使获取事件的所有当前订阅者的不可变快照变得容易且相对轻便,而没有任何锁定。 */ private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers = Maps.newConcurrentMap();
它底层使用了CopyOnWriteArrayList,并对其进行了封装,也就是在基本的集合上面增加了去重的操作。这是一种适用于读多写少场景的集合,在读取数据的时候不会加锁,
写入数据的时候进行加锁,并且会进行一次数组拷贝。
/** * 返回给定监听器的所有订阅者(按其订阅的事件类型分组)。 */ private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) { // 创建一个哈希表 Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create(); //获取监听者的类型 Class<?> clazz = listener.getClass(); //遍历上述方法,并且根据方法和类型参数创建观察者并将其插入到映射表中 for (Method method : getAnnotatedMethods(clazz)) { Class<?>[] parameterTypes = method.getParameterTypes(); //事件的类型 Class<?> eventType = parameterTypes[0]; methodsInListener.put(eventType, Subscriber.create(bus, listener, method)); } return methodsInListener; }
这里注意一下Multimap数据结构,它是Guava中提供的集合结构,与普通的哈希表不同的地方在于,它可以完成一对多操作。这里用来存储事件类型到观察者的一对多映射。
- 调用SubscriberRegistry的register(listener)来执行注册监听器。
- register步骤如下:
EventBus->SubscriberRegistry->ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers 用以维护事件和订阅者的映射。
当新注册监听者的时候,getAnnotatedMethods用反射获取全部的订阅者,为了避免浪费性能,会通过subscriberMethodsCache从缓存中加载。
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) { return subscriberMethodsCache.getUnchecked(clazz); }
//subscriberMethodsCache的定义是:
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); //2 } });
调用SubscriberRegistry中的getAnnotatedMethodsNotCached()方法获取这些监听方法。其实就是使用反射并完成一些校验,并不复杂。
//获取超类class集合 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) { ////遍历超类中的所有定义的方法 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); } } } } return ImmutableList.copyOf(identifiers.values()); }
然后,会遍历上述拿到的方法集合,根据事件的类型(从方法参数得知)和监听者等信息创建一个观察者,并将事件类型-观察者键值对插入到一个一对多映射表中并返回。
/** * 在给定的监听器对象上注册所有订阅者方法。 */ void register(Object listener) { //获取事件类型-观察者映射表 Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener); //遍历上述映射表并将新注册的观察者映射表添加到全局的subscribers中 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); } eventSubscribers.addAll(eventMethodsInListener); } }
post()方法如下:
public void post(Object event) { // 调用SubscriberRegistry的getSubscribers方法获取该事件对应的全部观察者 Iterator<Subscriber> eventSubscribers = this.subscribers.getSubscribers(event); if (eventSubscribers.hasNext()) { // 使用Dispatcher对事件进行分发 this.dispatcher.dispatch(event, eventSubscribers); } else if (!(event instanceof DeadEvent)) { this.post(new DeadEvent(this, event)); } }
post()方法中还是调用SubscriberRegistry中的方法
/** * 获取一个迭代器,该迭代器表示在调用此方法时给定事件的所有订阅者的不变快照。 */ Iterator<Subscriber> getSubscribers(Object event) { // 获取事件类型的所有父类型和自身构成的集合 ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass()); //3 List<Iterator<Subscriber>> subscriberIterators = Lists.newArrayListWithCapacity(eventTypes.size()); // 遍历上述事件类型,并从subscribers中获取所有的观察者列表 for (Class<?> eventType : eventTypes) { CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType); if (eventSubscribers != null) { // eager no-copy snapshot subscriberIterators.add(eventSubscribers.iterator()); } } return Iterators.concat(subscriberIterators.iterator()); }
从EventBus.post()方法可以看出,当我们使用Dispatcher进行事件分发的时候,需要将当前的事件和所有的观察者作为参数传入到方法中。然后,在方法的内部进行分发操作。最终某个监听者的监听方法是使用反射进行触发的,这部分逻辑在Subscriber内部,而Dispatcher是事件分发的方式的策略接口。EventBus中提供了3个默认的Dispatcher实现,分别用于不同场景的事件分发:
- ImmediateDispatcher:直接在当前线程中遍历所有的观察者并进行事件分发;
- LegacyAsyncDispatcher:异步方法,存在两个循环,一先一后,前者用于不断往全局的队列中塞入封装的观察者对象,后者用于不断从队列中取出观察者对象进行事件分发;实际上,EventBus有个字类AsyncEventBus就是用该分发器进行事件分发的。
- PerThreadQueuedDispatcher:这种分发器使用了两个线程局部变量进行控制,当dispatch()方法被调用的时候,会先获取当前线程的观察者队列,并将传入的观察者列表传入到该队列中;然后通过一个布尔类型的线程局部变量,判断当前线程是否正在进行分发操作,如果没有在进行分发操作,就通过遍历上述队列进行事件分发。
final void dispatchEvent(final Object event) { //使用指定的执行器执行任务 executor.execute(()->{ try { //使用反射触发监听方法 invokeSubscriberMethod(event); } catch (InvocationTargetException e) { //使用EventBus内部的SubscriberExceptionHandler处理异常 bus.handleSubscriberException(e.getCause(), context(event)); } } }); }