`
beneo
  • 浏览: 55327 次
  • 性别: Icon_minigender_1
  • 来自: 希伯來
社区版块
存档分类
最新评论

深入理解EventBus的设计思想

    博客分类:
  • java
阅读更多

凌弃同学已经介绍了EventBus的使用方式

​如何使用——三步走:

​1、定义一个observer,并加入@Subscribe作为消息回调函数;

2、将observer注册到EventBus;EventBus.register(this);

​3、消息投递: eventBus.post(logTo);

本文将深入EventBus的源代码,和大家一起深入研究EventBus的让人惊叹的设计思路。由于作者水平有限,无法面面俱到,希望大家先读读EventBusExplained

注:Guava的版本

 

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>15.0</version>
    </dependency>

 

 

准备工作

为了方便大家理解Coder的思路,有一些名词或约定先解释一下:

  1. 在observer类(比如:例子中的EventBusChangeRecorder)里面,@Subscribe所annotate的method,有且只有一个参数。因为EventBus#post(Object)方法只有一个参数咯,比如

     // Class is typically registered by the container.
     class EventBusChangeRecorder {
         // Subscribe annotation,并且只有一个 ChangeEvent 方法参数
         @Subscribe public void recordCustomerChange(ChangeEvent e) {
             recordChange(e.getChange());
         }
     }
     

     

  2. 通过method和observer的instance来定义一个EventSubscriber,请看源码

    SubscriberFindingStrategy#findAllSubscribers(Object)
     

     

  3. 在一个observer类里面,可以定义多个@Subscribe,根据method.getParameterTypes()[0]来缓存参数的类型——EventTypeSet<EventSubscriber>

    //所谓SetMultimap,就是Map<Class<?>, Set<EventSubscriber>>
     Set<EventSubscriber>>private final SetMultimap<Class<?>, EventSubscriber> subscribersByType = HashMultimap.create();
     

     

  4. @Subscribe所annotate的method的参数,不能支持泛型。因为在运行的时候,因为Type Erasure导致拿不到"真正"的parameterType,举个例子

    public class GenericClass<T> {                // 1
         private List<T> list;                     // 2
         private Map<String, T> map;               // 3
         public <U> U genericMethod(Map<T, U> m) { // 4
             return null;
         }
     } 

     

    上面代码里,带有注释的行里的泛型信息在运行时都还能获取到,原则是源码里写了什么运行时就能得到什么。针对1的GenericClass,运行时通过Class.getTypeParameters()方法得到的数组可以获取那个“T”;同理,2的T、3的java.lang.String与T、4的T与U都可以获得。源码文本里写的是什么运行时就能得到什么;像是T、U等在运行时的实际类型是获取不到的。

设计思路

Register/Unregister

在99.99%的使用场景中,是不会在runtime的时候去register/unregister某个observer的,在spring的环境,也是在init的时候做register/unregister。不过做framework就必须要考虑这0.01%的使用场景。在runtime的时候去register/unregister,最重要的就是线程安全问题:如果我在unregister某个observer的时候,正好调用EventSubscriber,会因为异常,导致Event不能送达到其它的observer上。所以在register/unregister的方法实现里面,都加入了ReadWriteLock,register/unregister的时候用writeLock,post的时候用readLock

public void register(Object object) {
    // Map<Class<?>, Collection<EventSubscriber>>结构
    Multimap<Class<?>, EventSubscriber> methodsInListener =
    finder.findAllSubscribers(object);
    subscribersByTypeLock.writeLock().lock();
    try {
        // subscribersByType是一个Map<Class<?>, Set<EventSubscriber>>结构
        subscribersByType.putAll(methodsInListener);
    } finally {
        subscribersByTypeLock.writeLock().unlock();
    }
}

 

 

其次,在SubscriberFindingStrategy#findAllSubscribers的时候有也用到了Cache,原理与下面要研究的Post的Cache一模一样

 

Post

EventBus#post的实现真的非常amazing,我们先从最初的设计思路开始,一步一步来。

最简单的想法就是,通过post传入一个event对象,这个eventgetClass作为key,通过subscribersByType来获取EventSubscriberSet,再调用EventSubscriber#handleEvent完成method#invoke

这样的思路没有什么问题,不过EventBus的作者想得更多更远:

  1. Post Everything

    可以是任意的object,只要subscribersByType有这个Key

    Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass())
     

     

  2. Cache

    毕竟post的Event的class是有限的,所以我们可以在classLoader下缓存flattenHierarchy的输入和输出,正如:

    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();
             }
     });
     

     

    注:static不是JVM下的全局共享,只是在classloader下面共享

  3. WeakReference

    也许你也注意到了,flattenHierarchyCache的Key(EventType)是一个WeakReference,这样做的目的就是GC友好。比方说你在runtime的时候,unregister了一个observer,这时候subscribersByType就不再Strong Reference这个EventTypeflattenHierarchyCache也会在minor gc的时候回收内存。

  4. ThreadLocal

    EventBus里面最Amazing的实现,在EventBus里面使用了ThreadLocal的地方有两处

     /** queues of events for the current thread to dispatch */
     private final ThreadLocal<Queue<EventWithSubscriber>> eventsToDispatch = new ThreadLocal<Queue<EventWithSubscriber>>() {
         @Override protected Queue<EventWithSubscriber> initialValue() {
             return new LinkedList<EventWithSubscriber>();
         }
     };
    
     /** true if the current thread is currently dispatching an event */
     private final ThreadLocal<Boolean> isDispatching = new ThreadLocal<Boolean>() {
         @Override protected Boolean initialValue() {
             return false;
         }
     };
     

     

    这样巧妙的设计,有三个目的:
    1. 解决嵌套问题。比方说一个observer有两个方法@Subscribe,其中一个方法的实现里面bus.post(SECOND);,为了避免已经处理过的Event再次被处理,所以需要isDispatching,下面是一个嵌套的例子。

      public class ReentrantEventsHater {
           boolean ready = true;
           List<Object> eventsReceived = Lists.newArrayList();
      
           @Subscribe
           public void listenForStrings(String event) {
               eventsReceived.add(event);
               ready = false;
               try {
                   bus.post(SECOND);
               } finally {
                   ready = true;
               }
           }
      
           @Subscribe
           public void listenForDoubles(Double event) {
                 assertTrue("I received an event when I wasn't ready!", ready);
                 eventsReceived.add(event);
           }
         }
       

       

    2. eventsToDispatch是一个queue,在enqueueEvent(请结合源码 EventBus#enqueueEvent)的时候调用,queue的使用够减少读锁的占用时间

    3. eventsToDispatchdispatchQueuedEvents通过ThreadLocal能够独立成为方法,方便了AsyncEventBusOverride

  5. ConcurrentLinkedQueue vs LinkedBlockingQueue

    得益于EventBus的巧妙设计,AsyncEventBus的实现就容易很多,不过笔者也发现了一个很有意思的地方。JavaDoc里面都标识了

    BlockingQueue implementations are designed to be used primarily for producer-consumer queues

    那么为什么要选用ConcurrentLinkedQueue而不是LinkedBlockingQueue呢?

     /** the queue of events is shared across all threads */
     private final ConcurrentLinkedQueue<EventWithSubscriber> eventsToDispatch = new ConcurrentLinkedQueue<EventWithSubscriber>();
    
     protected void dispatchQueuedEvents() {
         while (true) {
             EventWithSubscriber eventWithSubscriber = eventsToDispatch.poll();
             if (eventWithSubscriber == null) {
                 break;
             }
         dispatch(eventWithSubscriber.event, eventWithSubscriber.subscriber);
         }
     }
     

     

    简单来说,ConcurrentLinkedQueue是无锁的,没有synchronized,也没有Lock.lock,依靠CAS保证并发,同时,也不提供阻塞方法put()take(),速度上面肯定无锁的会更快一些,吞吐量更高一些(都是纳秒的差距)。再加上这里只有一个Publisher,多个ConsumerCousumer的消费速度又几乎是0,所以我个人觉得用啥都没啥区别。。。

总结

Guava真的是神器,希望读者看完本文后,能够对Guava产生兴趣。

分享到:
评论
1 楼 kalman03 2013-12-03  
顶坐拥4w女神而不倒!

相关推荐

    自己设计的轻量级 EventBus 框架,对想要了解EventBus的设计思想有一定的参考价值

    EventBus 是一种在 Android 应用程序中广泛使用的发布/订阅事件总线库,它简化了组件间的通信,使得组件间解耦。本项目是一个基于自己设计的轻量级 EventBus 框架,旨在帮助开发者深入理解 EventBu

    EventBus多版本+源码

    在这个压缩包中,包含了 EventBus 的两个不同版本:EventBus 2.4 和 simple_eventbus,以及 EventBus 的源码(EventBus-master.zip),这为我们深入理解 EventBus 的工作原理提供了便利。 EventBus 2.4 是 EventBus...

    EventBus 思想拆解

    这个思想拆解将深入探讨 EventBus 的核心原理、设计模式以及如何在实际开发中有效利用它。 一、观察者模式 观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有...

    eventbus demo

    EventBus 的核心思想是解耦,它允许不同组件通过发布事件来通知其他组件,接收者通过订阅这些事件来响应。下面我们将深入探讨 EventBus 的使用、优点以及如何在实际项目中应用。 首先,EventBus 的安装非常简单。在...

    Android进阶效率开发-eventbus总线事件

    首先,理解EventBus的基本概念。EventBus是一个轻量级的事件总线框架,它的核心思想是发布/订阅模式。发布者通过发送事件(Event),订阅者通过订阅这些事件来接收并处理。相比于传统的通信方式,EventBus具有以下...

    EventBusDemo.zip

    EventBusDemo.zip是一个示例项目,它以EventBus库为切入点,深入讲解了APT(Annotation Processing Tool)技术的运用,并且介绍了如何使用javaPoet库来生成Java代码。EventBus是一个发布/订阅事件总线库,它简化了...

    EventBus-master

    这个 "EventBus-master" 压缩包包含了 EventBus 的源代码,对于开发者来说,尤其是对 EventBus 感兴趣或者想要深入理解其工作原理的初学者,这是一个很好的学习资源。 EventBus 的核心思想是通过发布/订阅模式来...

    eventbus-2.1.0-beta-1.jar

    《EventBus 框架详解——基于 eventbus-2.1.0-beta-1.jar 的实践与探索》 EventBus 是一个轻量级的...通过深入理解其内部机制,开发者可以更好地利用 EventBus 提升代码的可维护性和可扩展性,同时降低项目复杂度。

    安卓Android源码——EventBus-master.zip

    EventBus 的核心思想是发布/订阅模式,它将事件的发布者和接收者(订阅者)进行解耦。发布者只需要发送事件,而订阅者通过订阅感兴趣的事件类型来接收事件,无需直接引用对方。 2. **注解驱动编程** 在 EventBus ...

    C#版本EventBus事件总线实例源码_(0610).rar.rar

    通过研究这个实例,我们可以深入理解C#中的事件机制以及如何构建一个实用的事件总线系统,这对于开发可维护、可扩展的大型C#项目至关重要。 总之,事件总线模式在C#中具有广泛的应用,它简化了组件间的通信,提高了...

    C#版本EventBus事件总线实例源码--值得下载

    通过深入研究和理解这个实例,开发者可以更好地掌握C#中的事件驱动编程,并将其应用于实际项目中,提高代码的可维护性和扩展性。 总的来说,事件总线模式在C#中是一种强大的工具,它简化了组件间的通信,增强了系统...

    C#版本EventBus事件总线实例源码.rar

    在软件开发中,事件总线(EventBus)是一种设计模式,用于在组件之间解耦通信,使得组件无需直接引用对方即可传递消息。C#作为一种广泛应用于Windows桌面应用、游戏开发、Web服务等领域的编程语言,也提供了实现...

    js代码-eventBus

    EventBus的核心思想是发布/订阅模式,即组件通过发布事件来传递消息,其他组件则可以订阅这些事件并作出响应。 `main.js` 文件很可能包含了实现EventBus的代码。在JavaScript中,我们可以使用简单的对象或者类来...

    MyEventBus:仿照EventBus原理手写简易版,加深对源码的理解-易

    本项目"MyEventBus"则是作者为了深入理解 EventBus 的工作原理而编写的简易版本。通过亲手实现,可以更直观地了解事件总线的内部机制,为后续阅读和分析 EventBus 源码打下基础。 1. **发布/订阅模式** 发布/订阅...

    C#版本EventBus事件总线实例源码__0525.rar

    本资料包“C#版本EventBus事件总线实例源码__0525.rar”提供了一个具体的C#实现事件总线的示例,有助于深入理解这一概念。 首先,事件总线的核心是发布/订阅模式。在这种模式中,发布者无需知道订阅者的具体身份,...

    C#版本EventBus事件总线实例源码(0515).rar

    本压缩包文件“C#版本EventBus事件总线实例源码(0515).rar”提供了一个具体的C#实现示例,让我们深入探讨这个概念及其应用。 事件总线的核心思想是将发布事件和订阅事件的过程分离,使得发布者无需知道订阅者是谁,...

    android-eventbus

    在这个主题中,我们将深入探讨 EventBus 的工作原理、如何使用以及它在实际项目中的优势。 1. **什么是 EventBus?** EventBus 是一个轻量级的消息总线库,它允许应用程序组件之间通过发布和订阅事件来解耦通信。...

    设计模式 java 参考模式

    设计模式是软件工程中的一种重要思想,它是在特定情境下为了解决常见问题而形成的一套最佳实践。Java作为一款广泛应用的面向对象编程语言,设计模式的掌握对于开发者来说至关重要。以下将根据提供的文件名,深入讲解...

    Android应用源码之Oschina客户端-IT计算机-毕业设计.zip

    在源码中,我们可以看到典型的MVP(Model-View-Presenter)设计模式的运用,这是一种将业务逻辑、用户界面和数据模型分离的设计思想。Model负责数据的获取和处理,Presenter作为桥梁,处理View与Model之间的交互,而...

Global site tag (gtag.js) - Google Analytics