美文网首页开源库
Otto源码解析

Otto源码解析

作者: 夜色流冰 | 来源:发表于2018-06-23 12:35 被阅读14次

    源码传送门:https://github.com/square/otto最近工作不是很忙,就花了半天的时间阅读了Otto的源码,于是就有了这篇博文,自己写下的心得体会,算是个学习笔记。Otto的原理并不难,其源码阅读起来也很容易,其思想原理就是订阅和发布事件,实质是注解+技术反射而实现的观察者模式,但是呢从其实现设计上来看看是是可以琢磨出一些东西出来。
    其使用也很简单,如下所示:

    class DataBean {
        String data = "hello otto";
    }
    
    class BusProvider {
        public static final Bus bus = new Bus();
    }
    
    class Observer {
        public Observer() {
            //注册对象
            BusProvider.bus.register(this);
        }
    
        //消费事件的方法1
        @Subscribe
        public void update0(DataBean dataBean) {
            Log.i("Otto", "接收到的数据0=" + dataBean.data);
        }
    
        //消费事件的方法2
        @Subscribe
        public void update1(DataBean dataBean) {
            Log.i("Otto", "接收到的数据1=" + dataBean.data);
        }
    }
    
    class Observable1 {
        public Observable1() {
            //注册对象
            BusProvider.bus.register(this);
        }
    
        //生产事件第一种方式
        @Produce
        public DataBean notifyObservers() {
            return new DataBean();
        }
    }
    
    class Observable {
        public Observable() {
        }
      //生产事件第二种方式
        public void notifyObservers() {
            BusProvider.bus.post(new DataBean());
        }
    }
    

    可以看出如果想要处理Bus生产的事件,则在相关的类的某个或者某些方法上面添加上@Subscribe注解即可,但是需要在该类初始化的时候调用bus对象的register方法。

    再说生产事件,Otto生产事件的方式有两种,一种是调用Bus的post方法,发送一个事件;第二种就是用@Produce的方式;无论哪种方式,其主要目的就是发送一个对象(可能是数据对象,也可能是其他对象);然后交给系统中标有@Subsribe的方法接收和处理发送的对象。当然使用注解@Produce的时候也需要先调用register方法。这点和post方法不一样。


    下面先说说@Produe和@Subscribe注解是怎么发挥工作的,既然在使用Otto的时候需要添加@Subscribe和@Produce标注(该标注需不需要视情况而定),那么肯定需要某种途径来检索和收集这些注解所标注的方法(也就是Method对象),将收集到的方法用集合存起来。以便在需要的时机从集合中获取和执行这些方法。确切的说用反射的方式执行这些方法,如下:

    //@Produce生成事件:在EventProducer类中
    Object evnent = produceMethod.invoke(targetObj)
    
    //@Subscribe消耗事件:在EventHandler类中
    subScribeMethod.invoke(targetObj, event);
    

    事实上上面的两行代码也就是整个Otto的核心实现原理:produce方法执行后返回一个event对象,然后该对像作为subscribeMehtod的参数去执行。


    即软需要检索和收集注解方法,那么肯定有两个行为:一是检索和收集标注了@Produce的方法集合,另一个是检索和收集@Subscrice方法集合。为此Otto对这两个行为抽象了一出了一个接口HandlerFinder:

    interface HandlerFinder {
     //检索和收集标注了@Producer的method集合
      Map<Class<?>, EventProducer> findAllProducers(Object listener);
     
     //检索和收集标注了@Subscriber的method集合
      Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener);
    }
    

    使用接口的好处就是便于扩展,Otto对此接口提供了默认实现:

    HandlerFinder ANNOTATED = new HandlerFinder() {
        @Override
        public Map<Class<?>, EventProducer> findAllProducers(Object listener) {
          return AnnotatedHandlerFinder.findAllProducers(listener);
        }
    
        @Override
        public Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
          return AnnotatedHandlerFinder.findAllSubscribers(listener);
        }
      };
    

    为了方便客户端使用自己的检索方式,Otto在初始化Bus对象的时候提供了有个重载构造器:

    Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
        this.enforcer =  enforcer;
        this.identifier = identifier;
        //自定义HandlerFinder
        this.handlerFinder = handlerFinder;
      }
    

    这也是面向对象设计理念的很好体现,所以说别看Otto的源码很简单,但是其设计理念还是可以学习和体会,并将此理念应用到自己的应用中去。


    Otto的主要是一个产生事件,一个消费事件,从单一职责来看这也是两个对象,于是乎就有两个HandlerFinder 接口中的两个对象:EventProducer对象(用来产生事件)和EventHandler对象(用来消费事件)。

    上面也说过产生事件和消费事件的核心原理就是method.invoke方法,所以EventProducer和EventHandler的封装的数据就不难理解了:

    class EventHandler {
      /**register的目标对象 */
      private final Object target;
      /**标注了@Subscribe的方法 */
      private final Method method;
    }
    
    class EventProducer {
      /**register的目标对象 */
      private final Object target;
      /**标注了@Produce的方法 */
      private final Method method;
    }
    
    

    既然Otto提供了默认实现,那么就看看其是怎么完成注解方法的检索和收集的,以findAllProducers方法为例:

    //参数listener为目标对象
    static Map<Class<?>, EventProducer> findAllProducers(Object listener) {
      //1、获取目标对象的class对象
      final Class<?> listenerClass = listener.getClass();
      
      //方法的返回结果集合
      Map<Class<?>, EventProducer> handlersInMethod = new HashMap<Class<?>, EventProducer>();
      
     //从缓存中查找:key是@producer的方法返回类型的class,Value为method
      Map<Class<?>, Method> methods = PRODUCERS_CACHE.get(listenerClass);
      //缓存不存在
      if (null == methods) {
        methods = new HashMap<Class<?>, Method>();
        //2、查找配置了@Producer注解的方法
        loadAnnotatedProducerMethods(listenerClass, methods);
      }
      
      //3、遍历所有的@procuder方法
      if (!methods.isEmpty()) {
        for (Map.Entry<Class<?>, Method> e : methods.entrySet()) {
          //形成一个EventProducer对象
          EventProducer producer = new EventProducer(listener, e.getValue());
          handlersInMethod.put(e.getKey(), producer);
        }
      }
    
      return handlersInMethod;
    }
    

    上面的代码逻辑很明白:
    1、获取目标对象的class对象

    2、通过loadAnnotatedProducerMethods来完成@Produce方法的检索,检索的结果填充到Map《class,Method》集合中。
    3、检索完成后,循环步骤2产生的map集合,然后将集合中的一个个method连同目标对象组成一个EventProducer对象,最终放入Map《class,EventProducer》类型的集合中。

    需要注意步骤2和步骤3的map的key的类型,其map的key类型是@Produce方法的返回类型的class对象,比如下面两个方法:

    class Target1{
      @Produce
      public A pA(){return new A()}
    
       @Produce 
       public B pB(){return new B()}
    }
    
    class Target2{
      @Produce
      public C pC(){return new C()}
    }
    

    其Key就是A.class和B.class,所以findAllProducers返回的集合中的元素就是如下:

    这里写图片描述

    上面的的表格其实是步骤2的loadAnnotatedProducerMethods检索后交给步骤3形成的,所以看看loadAnnotatedProducerMethods对@Produce是怎么检索的,该方法最终又调用了loadAnnotatedMethods,本方法在此只关注对@Produce的处理:

    private static void loadAnnotatedMethods(Class<?> listenerClass,
        Map<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods) {
      //循环目标类的方法
      for (Method method : listenerClass.getDeclaredMethods()) {
         //省略部分代码
         
        //处理标有@Subscribe注解的方法
        if (method.isAnnotationPresent(Subscribe.class)) {
             //省略
        //注解@produce的方法
        } else if (method.isAnnotationPresent(Produce.class)) {
          Class<?>[] parameterTypes = method.getParameterTypes();
          //方法不能有参数
          if (parameterTypes.length != 0) {
            throw new IllegalArgumentException("必须无参数");
          }
          //必须有返回类型
          if (method.getReturnType() == Void.class) {
            throw new IllegalArgumentException("必须有返回值");
          }
    
         //返回类型不能是接口
          Class<?> eventType = method.getReturnType();
          if (eventType.isInterface()) {
            throw new IllegalArgumentException("返回不能是接口");
          }
          if (eventType.equals(Void.TYPE)) {
            throw new IllegalArgumentException("Method " + method + " has @Produce annotation but has no return type.");
          }
    
          //必须public修饰
          if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
            throw new IllegalArgumentException("必须public修饰");
          }
    
          //一个类中同一个返回类型只能有一个方法
          if (producerMethods.containsKey(eventType)) {
            throw new IllegalArgumentException("一个类中同一个返回类型只能有一个方法");
          }
          //key为返回类型的class对象,value为方法名
          producerMethods.put(eventType, method);
        }
      }//end for
    
      //缓存起来
      PRODUCERS_CACHE.put(listenerClass, producerMethods);
      SUBSCRIBERS_CACHE.put(listenerClass, subscriberMethods);
    }
    
    

    从代码中可见Otto对@Produce方法做了诸多的限制,详情看阅读上述代码,不做赘述。


    同理对@Subcribe的检索和收集也很简单,代码还是在loadAnnotatedMethods方法中:

    private static void loadAnnotatedMethods(Class<?> listenerClass,
        Map<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods) {
      //循环遍历目标类的方法
      for (Method method : listenerClass.getDeclaredMethods()) {
        
        //处理标有@Subscribe注解的方法
        if (method.isAnnotationPresent(Subscribe.class)) {
          Class<?>[] parameterTypes = method.getParameterTypes();
          //该方法必须含有且只含有一个参数
          if (parameterTypes.length != 1) {
            throw new IllegalArgumentException("必须含有且只含有一个参数");
          }
    
          //该方法的参数不能是接口
          Class<?> eventType = parameterTypes[0];
          if (eventType.isInterface()) {
            throw new IllegalArgumentException("参数不能是接口类型");
          }
    
          //该方法必须为public
          if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
            throw new IllegalArgumentException("该方法必须为public");
          }
         
          //注意key为参数的class类型
          Set<Method> methods = subscriberMethods.get(eventType);
          if (methods == null) {
            methods = new HashSet<Method>();
            subscriberMethods.put(eventType, methods);
          }
          
          methods.add(method);
       
        } else if (method.isAnnotationPresent(Produce.class)) {
        }
      }//end for
    
      //缓存起来
      PRODUCERS_CACHE.put(listenerClass, producerMethods);
      SUBSCRIBERS_CACHE.put(listenerClass, subscriberMethods);
    }
    

    如果loadAnnotatedMethods检索下面代码的话:

    class Target1{
      @Subscribe
      public subA1(A a){}
    
      @Subscribe
      public subA2(A a){}
    
      @Subscribe
      public subA3(A a){}
    
      @Subscribe
      public subB1(B b){}
    
      @Subscribe
      public subB2(B b){}
    
      @Subscribe
      public subB3(B b){}
    }
    

    最总返回的map集合形如:


    这里写图片描述

    通过上面的讲解我们知道,HandlerFinder的接口查找@Subsribe的方法返回的Map《class,EventHandler》集合,所以上面的表格所表示的集合通过findAllSubscribers方法处理后形成了如下表格:

    static Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
        Class<?> listenerClass = listener.getClass();
        Map<Class<?>, Set<EventHandler>> handlersInMethod = new HashMap<Class<?>, Set<EventHandler>>();
    
        //省略部分代码
        if (!methods.isEmpty()) {
          //对上面的map表格做循环
          for (Map.Entry<Class<?>, Set<Method>> e : methods.entrySet()) {
            Set<EventHandler> handlers = new HashSet<EventHandler>();
            for (Method m : e.getValue()) {
              handlers.add(new EventHandler(listener, m));
            }
            handlersInMethod.put(e.getKey(), handlers);
          }
        }
    
        return handlersInMethod;
      }
    

    所形成的map结果如下图:

    这里写图片描述

    上面一直再讲检索扫描,然后到形成集合。那么是什么时候开始开始扫描的呢?就是target对象注册到Bus对象的时候,也就是new Bus().register(target)执行时进行扫描。事实上上面讲的HandlerFinder接口的调用就是在register方法中执行的。具体的可以自行阅读代码。
    对比第一个表格和第三个表格可以发现,一个类中一个类型的事件可以被多个标注@Subscribe方法来处理,也就是说产生事件和消费事件是一对多关系,一个@Produce产生的事件可以被多个@Subsribe来处理,当然@Subsribe消费的事件的类型,必须和@Produce产生的事件类型一样。具体见下文分析。

    那么两种注解方法是怎么通信的呢?也就是说@Produce 方法执行完毕时怎么通知@Subsribe方法的呢,在register方法中有这么一段:

       //遍历Procuder集合的key
        for (Class<?> type : foundProducers.keySet()) {
          //查找key对应的EventProducer对象
          final EventProducer producer = foundProducers.get(type);
          //省略部分代码
         
          //根据key查找能消费key事件的@Subscribe方法集合
          Set<EventHandler> handlers = handlersByType.get(type);
      
          for (EventHandler handler : handlers) {
             dispatchProducerResultToHandler(handler, producer);
          }
          
        }
    
    

    上面的代码很简单,拿事件A来说,先根据A.class找到EventProducer对象,该对象会产生事件A;然后根据A.class找到能消费掉A事件的EventHandler事件集合,交给dispatchProducerResultToHandler方法:

    
      private void dispatchProducerResultToHandler(EventHandler handler, EventProducer producer) {
        //生成事件
        Object event = producer.produceEvent();
        
        //消费事件
        dispatch(event, handler);
      }
    
    
      protected void dispatch(Object event, EventHandler wrapper) {
          wrapper.handleEvent(event);
      }
    
     //EventProducer类中生产事件的方法:
      public Object produceEvent() throws InvocationTargetException {
          //反射方式执行@Produce方法
          return method.invoke(target);
        
      }
    
    //EventHandler类中消费事件的方法
     public void handleEvent(Object event) throws InvocationTargetException {
         //反射凡是执行@Subscribe方法
          method.invoke(target, event);
       
      }
    
    
    

    在此处有一个疑问,事件的生产是在循环中进行的:

     for (EventHandler handler : handlers) {
          dispatchProducerResultToHandler(handler, producer);
       }
    

    也就是说一个@Produce的方法如果对应的@Subscribe方法有多个的话,producer.produceEvent会调用多次,这样的设计意图暂时没想明白为什么,还是说这本身就是一个异常代码,如有知情者,欢迎不吝赐教。为什么不这样调用:

    //确保只生产一次事件
    Object event = producer.produceEvent
    for (EventHandler handler : handlers) {
         handler.handleEvent(event)
     }
    

    到此为止,Otto的基本逻辑已经分析完毕,只有Bus的post方式发送事件的细节就不多讲了。如有不当之处,欢迎批评指正。

    相关文章

      网友评论

        本文标题:Otto源码解析

        本文链接:https://www.haomeiwen.com/subject/djvryftx.html