订阅发布这种设计模式是一种最常见不过的设计模式.而EventBus是对Publisher和Subscriber的一种实现
下面是EventBus的一个Demo
public class Event {
/**
* 事件总线使用该方法向监听者发送事件
*
* @param message
*/
@Subscribe
public void sub1(String message) {
System.out.println("sub1 -> " + message);
}
@Subscribe
public void sub2(File file) {
System.out.println("sub2 -> " + file.getAbsolutePath());
}
@Subscribe
@AllowConcurrentEvents
public void sub3(Integer num) {
System.out.println("received integer -> " + num);
}
}
上述代码中被@Subscribe装饰的方法为EventHandler,EventHandler方法必须有且只能有一个参数,并且EventHandler不能抛出异常,就算抛出了异常,也会被捕获并且打印日志。EventHandler不会被EventBus并发调用,除非EventHandler被AllowConcurrentEvents注释
下面进行测试代码
@Test
public void testSyncEventBus() {
EventBus eventBus = new EventBus();
//注册事件
eventBus.register(new Event());
//触发事件处理
eventBus.post("你好啊");
eventBus.post(3);
}
上面post(“你好啊”)触发的事件只会触发参数为String类型的sub1方法
执行结果
sub1 -> 你好啊
received integer -> 3
private static final LoadingCache<Class>, Set<Class>>> flattenHierarchyCache =
CacheBuilder.newBuilder()
.weakKeys()
.build(new CacheLoader<Class>, Set<Class>>>() {
@SuppressWarnings({"unchecked", "rawtypes"}) // safe cast
@Override
public Set<Class>> load(Class> concreteClass) {
return (Set) TypeToken.of(concreteClass).getTypes().rawTypes();
}
});
该缓存的作用是缓存类所有实现接口和父类,其次该缓存是线程安全的,其次注意这是一个静态final变量,意味着所有的EventBus实例都共享该缓存,该缓存可以有效的提高性能
private final SetMultimap<Class>, EventSubscriber> subscribersByType =
HashMultimap.create();
subscribersByType是一个特殊的map型容器,该容器一个键可以对应多个值,在EventBus中的作用是一种事件类型可以对象多个EventHandler
private final ReadWriteLock subscribersByTypeLock = new ReentrantReadWriteLock()
SetMultimap不是线程安全的,所以向subscribersByType注册或移除listener,要获取读写锁
private final ThreadLocal isDispatching =
new ThreadLocal() {
@Override protected Boolean initialValue() {
return false;
}
};
private final ThreadLocal> eventsToDispatch =
new ThreadLocal>() {
@Override protected Queue initialValue() {
return new LinkedList();
}
};
private final SubscriberFindingStrategy finder = new AnnotatedSubscriberFinder();
从以上所有的变量我们可以知道EventBus是线程安全的
在EventBus中如果一个事件被发布,但是没有EventHandler可以处理该事件,为了便于日志记录和debug所有DeadEvent专门用于标志这种事件
public class DeadEvent {
private final Object source;
private final Object event;
public DeadEvent(Object source, Object event) {
this.source = checkNotNull(source);
this.event = checkNotNull(event);
}
...省略getter
}
注册源码如下
public void register(Object object) {
Multimap, EventSubscriber> methodsInListener =
finder.findAllSubscribers(object);
...
//省略后续代码
}
首先利用finder的findAllSubscribers注册全部的被@Subscribe注释的方法
public Multimap, EventSubscriber> findAllSubscribers(Object listener) {
Multimap, EventSubscriber> methodsInListener = HashMultimap.create();
Class> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class>[] parameterTypes = method.getParameterTypes();
//获取被传输的数据类型
Class> eventType = parameterTypes[0];
//利用方法listener和method创建EventSubscriber对象
EventSubscriber subscriber = makeSubscriber(listener, method);
methodsInListener.put(eventType, subscriber);
}
return methodsInListener;
}
methodsInListener是一个特殊的map型容器,该容器一个键可以对应多个值,在EventBus中一种事件类型可以对象多个listener
首先在缓存中查找被注释的方法
private static ImmutableList<Method> getAnnotatedMethods(Class> clazz) {
···省略try - catch
return subscriberMethodsCache.getUnchecked(clazz);
···
}
如果击穿了缓存层,利用getAnnotatedMethodsInternal获取被@Subscribe注释的全部方法
private static ImmutableList getAnnotatedMethodsInternal(Class> clazz) {
//获取clazz对象的全部父类或者接口
Set extends Class>> supers = TypeToken.of(clazz).getTypes().rawTypes();
Map identifiers = Maps.newHashMap();
for (Class> superClazz : supers) {
for (Method superClazzMethod : superClazz.getMethods()) {
//方法被Subscribe注释
if (superClazzMethod.isAnnotationPresent(Subscribe.class)
//方法不是桥接方法
&& !superClazzMethod.isBridge()) {
//获取方法参数数组
Class>[] parameterTypes = superClazzMethod.getParameterTypes();
//方法参数只能有一个否则抛异常
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Method " + superClazzMethod
+ " has @Subscribe annotation, but requires " + parameterTypes.length
+ " arguments. Event subscriber methods must require a single argument.");
}
//利用method的名字和args生成MethodIdentifier对象
MethodIdentifier ident = new MethodIdentifier(superClazzMethod);
//放入容器之前检验是否曾今放过该方法
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, superClazzMethod);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}
上面的提到的桥接方法与泛型擦除有关,具体在此不解释,register将该对象的全部观察方法注册进容器之后,继续执行如下逻辑
subscribersByTypeLock.writeLock().lock();
try {
subscribersByType.putAll(methodsInListener);
} finally {
subscribersByTypeLock.writeLock().unlock();
}
首先获取subscribersByType写锁然后将刚才发现的全部方法加入容器
public void post(Object event) {
//从缓存中获取该事件的全部父类和实现接口
Set> dispatchTypes = flattenHierarchy(event.getClass());
//设定分发标志为false
boolean dispatched = false;
//遍历全部的父类和接口
for (Class> eventType : dispatchTypes) {
//加上读锁
subscribersByTypeLock.readLock().lock();
try {
Set wrappers = subscribersByType.get(eventType);
if (!wrappers.isEmpty()) {
//wrappers不为空表示该事件需要被分发给listener
dispatched = true;
for (EventSubscriber wrapper : wrappers) {
//进入等待分发队列
enqueueEvent(event, wrapper);
}
}
} finally {
subscribersByTypeLock.readLock().unlock();
}
}
//如果没有相应的listener
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}
上面的逻辑只是收集等待分发的listener队列,真正的事件分发在下面这个方法逻辑里面
void dispatchQueuedEvents() {
//如果当前线程正在分发事件.返回
//这里的目的是将EventHandler注册顺序与分发顺序一致
if (isDispatching.get()) {
return;
}
//设置当前线程正在分发标记
isDispatching.set(true);
try {
//获取listener队列
Queue events = eventsToDispatch.get();
EventWithSubscriber eventWithSubscriber;
while ((eventWithSubscriber = events.poll()) != null) {
//向每一个listener分发事件
dispatch(eventWithSubscriber.event, eventWithSubscriber.subscriber);
}
} finally {
//移除正在分发标记
isDispatching.remove();
//移除等待分发队列
eventsToDispatch.remove();
}
}
dispatch(eventWithSubscriber.event, eventWithSubscriber.subscriber)具体逻辑如下
public void handleEvent(Object event) throws InvocationTargetException {
checkNotNull(event);
try {
method.invoke(target, new Object[] { 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;
}
}
原理是利用反射的方式分发每一个事件