开源库

Otto源码解析

2018-06-23  本文已影响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方式发送事件的细节就不多讲了。如有不当之处,欢迎批评指正。

上一篇下一篇

猜你喜欢

热点阅读