EventBus 3.0 从入门到精通——EventBus 3.0 是如何应用注解的?

文章索引:
EventBus 3.0 从入门到精通——初识EventBus
EventBus 3.0 从入门到精通——EventBus的应用场景
EventBus 3.0 从入门到精通——使用详解(一)
EventBus 3.0 从入门到精通——使用详解(二)

接下来我们从技术层面拆解EventBus,首先EventBus3.0与之前最大的不同就是可以使用注解,也就是@Subscriber 注解那EventBus是怎样应用注解的呢?别急,我们先回顾一下java语言的注解相关知识。

什么是注解

注解早在J2SE1.5就被引入到Java中,主要提供一种机制,这种机制允许程序员在编写代码的同时可以直接编写元数据。

这个定义也比较晦涩,我们把它拆开来看。注解是在java 1.5版本中引入的一个新特性,它可以使得在编写java代码的时候可以同时编写 元数据 这个东西。我理解元数据就是注解所修饰的类、方法、属性等的一些固有属性或者说附加属性。比如@Override 这个注解它修饰的方法会附加一个属性表明这个方法是重写了父类的方法,所以父类就一定会有和这个方法声明一样的方法存在。jvm在编译的时候会校验子类和父类之间是或否存在这样的关系,如果没有编译就会报错,这样可以避免一些不必要的错误。

注解的分类

注解可以分为:java內建注解(元注解)、自定义注解。

Java提供了三种内建注解:

  1. @Override——当我们想要复写父类中的方法时,我们需要使用该注解去告知编译器我们想要复写这个方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。

  2. @Deprecated——当我们希望编译器知道某一方法不建议使用时,我们应该使用这个注解。Java在javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。

  3. @SuppressWarnings——这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。

以上几种內建注解很常见就不在赘述了,重点看一下自定义注解。

创建Java自定义注解

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
    public enum Priority {LOW, MEDIUM, HIGH}

    public enum Status {STARTED, NOT_STARTED}

    String author() default "no_one";

    Priority priority() default Priority.LOW;

    Status status() default Status.NOT_STARTED;
}

上面是一个自定义注解的例子,声明一个注解和声明一个接口很像,只是在interface关键字前面多了一个@,并且在注解上加上java的元注解用来修饰自定义注解的附加属性。

上面有四种类型的元注解:

  1. @Documented —— 指明拥有这个注解的元素可以被javadoc工具文档化。

  2. @Target——指明该类型的注解可以注解的程序元素的范围。该元注解的取值可以为TYPE(类),METHOD(方法),CONSTRUCTOR(构造器),FIELD(属性)等。如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。

  3. @Inherited——指明该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。

  4. @Retention——指明了该Annotation被保留的时间长短。RetentionPolicy取值为SOURCE(在编译成字节码时丢弃),CLASS(类加载时丢弃),RUNTIME(运行时保留)。

我们也可以为注解声明方法和属性。上面例子中声明了两个枚举,和3个方法(author()、priority()、status())。
我们看一下使用方法。

    @Todo(priority = Todo.Priority.MEDIUM, author = "mumu", status = Todo.Status.STARTED)
    public void hello() {

    }

对照两段代码片段,我们可以看到,枚举的使用和普通代码没有什么区别。有区别的地方是方法,方法的返回值类型就是使用注解时的接收入参的类型,方法的使用方式是“方法名 = 返回值”通过这样的方式可以设置注解的方法返回值,看起来很方便。

消费器

好了我们知道注解的声明和使用了,那到底怎样获取注解的信息呢?答案是通过反射
上面讲声明自定义注解的时候说到了元注解@Retention的几种类型,一般我们自定义注解的时候@Retention会指定为RUNTIME因为我们这样我们在运行时就可以通过反射的方式获取我们想要的信息了。

    public static void main(String[] args) {
        Class testClass = Test.class;
        for(Method method : testClass.getMethods()) {
            Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);
            if(todoAnnotation != null) {
                System.out.println(" Method Name : " + method.getName());
                System.out.println(" Author : " + todoAnnotation.author());
                System.out.println(" Priority : " + todoAnnotation.priority());
                System.out.println(" Status : " + todoAnnotation.status());
            }
        }
    }

上面代码片段就是一个利用反射获取信息的例子。相信有一定java编程知识的同学都有举一反三的能力。
以上全当是复习一下注解的知识点,接下来我们看看EventBus是怎样使用注解的。

EventBus的注解@Subscriber

我们先跟着EventBus的工作流程看一下:
EventBus 3.0 从入门到精通——EventBus 3.0 是如何应用注解的?_第1张图片

以上是一个简化的EventBus工作流程,EventBus中我们最常用的是@Subscriber注解,这个注解在以上的流程中使用在定义事件消费者这个步骤,使用方法参见EventBus 3.0 从入门到精通——使用详解(一),我们现在主要关心@Subscriber注解的使用。
先看一下@Subscriber注解的源码:

@Documented //可以被javaDoc扫描到
@Retention(RetentionPolicy.RUNTIME)//运行时保留(这样EventBus才能获取到注解的信息)
@Target({ElementType.METHOD})//修饰的是方法
public @interface Subscribe {
    //线程模式默认是POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;

    /** 粘性事件设置。默认是false */
    boolean sticky() default false;

    /** 设置订阅优先级 */
    int priority() default 0;
}

上面是@Subscriber注解的源码,我加了一些简单注释。通过上面对注解的回顾我们可以很清楚的知道@Subcriber的作用是获取三个元数据(线程模式、是否是粘性事件、优先级)。在EventBus注册事件消费者的时候(register())会扫描消费者对象是否有带有@Subscriber注解的方法。

    public void register(Object subscriber) {
        Class subscriberClass = subscriber.getClass();
        List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

以上是EventBus的register方法的源码,我们可以看到EventBus使用subscriberMethodFinder.findSubscriberMethods(subscriberClass); 方法来扫描注册的消费者对象是否有@Subscriber注解,那我们在看看findSubscriberMethods方法是怎样扫描的。

    List findSubscriberMethods(Class subscriberClass) {
        List subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

可以看到其实findSubscriberMethods方法并没有直接扫描,而是先查看METHOD_CACHE(ps:METHOD_CACHE是一个存储所有EventBus消费者的ConcurrentHashMap变量)中是否已经存在了这个消费者,这个机制我想应该是防止重复注册的,如果已经有这个对象了,就直接返回既节省性能有防止重复。如果没有重复会先判断ignoreGeneratedIndex的值(ignoreGeneratedIndex强制使用反射默认是false),默认会执行findUsingInfo() 方法找出所有带有@Subscriber注解的方法信息如果订阅者没有@Subscriber注解注释的方法还会抛出没有订阅者的异常(ps:这点要注意,注册订阅的类中必须有@Subscriber注解注释的订阅者)。之后会将订阅者信息保存到METHOD_CACHE中,并返回订阅者信息。

以上就是EventBus中的@Subscriber注解的相关信息,内容不是很多,但是还是能学到很多东西。比如订阅者类必须要有一个订阅者^_^。

你可能感兴趣的:(EventBus)