• [置顶] 读源码练内功(一):guava之eventbus


    现在如今眼目下,开源程序库越来越多,程序员们很多时候都不需要自己造轮子,就可以找到称心如意的开源库进行使用。虽然我们在使用各种各样的开源代码时,并不需要知道这些代码是如何实现的。但是了解它们的实现方法,不仅可以提升我们自己本身的编程能力和编程技巧,同时也为我们学习某一特定的技术点提供了可以模仿的例子。


    Guava简介

    如同boost之于c++,guava也几乎成为了java编程中不可或缺的一部分。guava中涵盖了很多有意思的东西,比如在java中使用函数式编程;新的数据结构,如bimap等等。总之,guava让写java程序成为一件更美好的事情。guava中还有很多很多有意思的东西,可以去guava的官方网站上探个究竟。guava

    本文试图通过分析guava的eventbus的源代码,学习如何在java中使用annotation编程。

    EventBus简介

    Publisher-Subscriber这种设计模式在GoF中早就详细的解释。也是一种最常用不过的设计模式。而EventBus则是对于Publisher和Subscriber的一种实现,如果你还在使用JDK中的Observer,则不妨看看TW大大的一片博客《你应该更新的java知识之observer》,使用EventBus替代Observer似乎成了一种必须。

    EventBus使用方法

    MessageScreen.java

    public class MessageScreen {
        @Subscribe
        public void printMessage(String message) {
           System.out.println(message);
        }
    }

    调用代码:

            EventBus eventBus = new EventBus();
            eventBus.register(new MessageScreen());
            eventBus.post("Hello Screen");

    显示结果:

         Hello Screen


    代码解释:

    调用eventBus中得register时,会向eventBus中注册一个Listener。这里的listener就是MessageScreen。

    listener中被@Subscribe标示的方法为EventHandler,当eventBus使用post发布Event时,这个方法就会被调用。对应前面的例子,EventHandler是printMessage这个方法。Event是“Hello Screen”。

    EventHandler中的参数类型为EventType,这里是String。当eventBus使用post时,会更具event的Type不同发送到相应的handler进行处理。

    于是,一切看似神奇的事情都是发生在@Subscribe这个标记上。那Annotation究竟是干什么用得呢?怎么的一个标明了@Subscribe的方法就可以变成了一个subscriber了呢?


    Annotation编程

    Annotation是一种元数据,它提供了关于程序的信息,但是这个信息并不属于被Annotation标注的程序的本身的一部分。它不会影响被标注的程序的执行。

    以上面的MessageListener为例,displayMessage这个方法被标注了@Subscribe,这个标注信息是在EventBus中的register中用到的,而不是在MessageListener中用到的。

    Annotation编程可以分为大致两个部分,第一个部分是annotation定义,第二个部分是获取被annotation标记的代码,然后针对这部分代码进行一些特定的操作。下面就使用EventBus讲解这两个部分的工作是如何完成的。

    定义Annotation:@Subscribe

    Subscribe的定义

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Subscribe {
    }


    @interface表示定义了一个annotation。@Retention和@Target是predefined annotation。

    @Retention表示Subscribe这个Annotation是怎么被保存的。@Retention(RententionPolicy.RUNTIME)说明Subscribe这个annotation在运行时可以被使用。

    @Target表示的是这个Subscribe这个annotation能被标注到哪里。@Target(ElementType.METHOD)说明Subscribe是用于标记方法的。对应一开始EventBus的例子,就是printMessage这个方法。


    获取被Annotation标记的代码:EventBus中如何知道@Subscribe的方法的呢?

    EventBus的代码:

        public void register(Object object) {
            Multimap<Class<?>, EventHandler> methodsInListener =
                    finder.findAllHandlers(object);
            handlersByType.putAll(methodsInListener);
        }

    在EventBus进行register时,会通过一个finder找到register的object中被标注了@Subscribe的方法。并且按照EventType进行分类,放在handlersByType里。这样当EventBus的post新的Event时,就可以根据EventType调用相应的EventHandler。

    AnnotatedHandlerFinder的代码

         public Multimap<Class<?>, EventHandler> findAllHandlers(Object listener) {
             Multimap<Class<?>, EventHandler> methodsInListener = HashMultimap.create();
             Class<?> clazz = listener.getClass();
             for (Method method : getAnnotatedMethods(clazz)) {
                 Class<?>[] parameterTypes = method.getParameterTypes();
                 Class<?> eventType = parameterTypes[0];
                 EventHandler handler = makeHandler(listener, method);
                 methodsInListener.put(eventType, handler);
             }
             return methodsInListener;
         }

    在findAllHandlers这个函数中,将首先调用getAnnotatedMethods得到listener中所有被标记了@Subscribe的方法。然后将listener和被标记了@Subscribe的方法本身放在一个叫EventHandler的数据结构中,同时记录了method的第一个也是唯一一个参数类型作为EventType作为post时按类型分发消息使用。接着再看是如何得到被@Subscribe标记的方法的:

        private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {

                for (Method method : clazz.getMethods()) {
                    if (method.isAnnotationPresent(Subscribe.class)) {
                        ... ...// add to list
                    }
                }
            }

    getAnnotatedMethods首先获得了listener的所有方法。然后再遍历查询是否有方法是否被@Subscribe标示了:method.isAnnotationPresent(Subscribe.class)。如果被@Subscribe标示了,则添加到返回的list中。这其实也告诉了我们,这种方法的另一个好处,一个类中不再只能有一个update作为Observer的方法。而是可以有多个被@Subscribe标示的EventHandler,他们在同一个类时,就通过方法名和EventType进行区分。


    当EventBus中的post被调用时,中间会有一系列的入队列,出队列的操作。最后调用,Eventhandler的handleEvent方法。

    EventHandler.java

    public void handleEvent(Object event) {
            method.invoke(target,new Object[]{ event});
            }

    handleEvent中method是在register中被标记了@Subscribe的方法,第一个参数target就是在register中加入的listener,通过调用method的invoke方法。这样就调用了被@Subscribe的方法。


    总结

    1.Annotation编程在很多地方都有用到,比如大家都再熟悉不过的Junit4中的@Test,Spring中的@Component等等。所以,学习Annotation,说不定可以让我们写出这些使用方便的程序。

    2.在EventBus的完整实现中,其实还包含了其他很多的技术,像多线程、cache以及Guava其他的一些功能。比如getAnnotatedMethods这个方法并不是真正的去查找Annotated Methods,真正的查找是getAnnotatedMethodsInternal这个方法,期间就使用了cache。这里想专注于Annotation本身的使用,就不展开讨论了。

    3.在判断是否含有@Subscribe时,AnnotatedHandlerFinder实际上是检查了所有父类的方法。因为父类本身的Annotation是不会被子类继承的,如果子类重写了父类的方法。那么调用子类的isAnnotationPresent则会返回false。

    4. 如何想要了解更多的关于annotation的细节知识,可以访问下oracle的官方文档:oracle annotation


  • 相关阅读:
    设计模式之外观模式(结构型)
    Oracle merge合并更新函数
    前端自定义搜索框实现
    Easyui学习整理笔记
    Jquery+Eayui实现列表选择功能
    Oracle SQL优化器简介
    设计模式之原型模式(创建型)
    Mysql学习笔记整理手册
    Oracle和Mysql语法异同整理笔记
    Mysql实现树形递归查询
  • 原文地址:https://www.cnblogs.com/java20130722/p/3206825.html
Copyright © 2020-2023  润新知