guava - EventBus源码解析

订阅发布这种设计模式是一种最常见不过的设计模式.而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

首先介绍成员变量

  1. 缓存
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实例都共享该缓存,该缓存可以有效的提高性能

  1. listener容器
private final SetMultimap<Class<?>, EventSubscriber> subscribersByType =
      HashMultimap.create();

subscribersByType是一个特殊的map型容器,该容器一个键可以对应多个值,在EventBus中的作用是一种事件类型可以对象多个EventHandler

  1. 并发锁
private final ReadWriteLock subscribersByTypeLock = new ReentrantReadWriteLock()

SetMultimap不是线程安全的,所以向subscribersByType注册或移除listener,要获取读写锁

  1. 标识当前线程是否进行分发
private final ThreadLocal<Boolean> isDispatching =
    new ThreadLocal<Boolean>() {
  @Override protected Boolean initialValue() {
    return false;
  }
};
  1. 当前线程正在等待分发的listener队列
private final ThreadLocal<Queue<EventWithSubscriber>> eventsToDispatch =
    new ThreadLocal<Queue<EventWithSubscriber>>() {
  @Override protected Queue<EventWithSubscriber> initialValue() {
    return new LinkedList<EventWithSubscriber>();
  }
};
  1. JobHandler发现策略
    线程安全
private final SubscriberFindingStrategy finder = new AnnotatedSubscriberFinder();

从以上所有的变量我们可以知道EventBus是线程安全的

特殊事件类型DeadEvent

在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
}

register注册listener

注册源码如下

public void register(Object object) {
  Multimap<Class<?>, EventSubscriber> methodsInListener =
      finder.findAllSubscribers(object);
    ...
    //省略后续代码
}

首先利用finder的findAllSubscribers注册全部的被@Subscribe注释的方法

public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
  Multimap<Class<?>, 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<Method> getAnnotatedMethodsInternal(Class<?> clazz) {
  //获取clazz对象的全部父类或者接口
  Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes();
  Map<MethodIdentifier, Method> 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写锁然后将刚才发现的全部方法加入容器

post发布事件

public void post(Object event) {
  //从缓存中获取该事件的全部父类和实现接口
  Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
  //设定分发标志为false
  boolean dispatched = false;
  //遍历全部的父类和接口
  for (Class<?> eventType : dispatchTypes) {
    //加上读锁
    subscribersByTypeLock.readLock().lock();
    try {
      Set<EventSubscriber> 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<EventWithSubscriber> 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;
  }
}

原理是利用反射的方式分发每一个事件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值