Java Annotation 理解和运用

前言

在Android开发作业中接触到了很多开源框架使用了Java Annotation机制,我接触到的就有GreenRobot、Dagger2、AndFix等项目。

那么 Annotation机制到底是如何发挥作用的?下面将介绍Annotation的常见类型及基本语法。

从@Override认识注解

相信大部分同学对@Override一点都不陌生,在子类覆盖超类的方法时,Eclipse等IDE会在方法上自动生成这个注解。

那么来看一下这个注解的语法形式:

package java.lang;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

其中@interface 定义了Override是一个Annotation类型,或者叫元数据(meta-data)。
@Target和@Retetion是对Override的注解,称之为元注解(元数据的注解)。

@Target

再来看下@Target的定义:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

首先Target也是一个注解类型,在其内部定义了方法ElementType[] value();

细心观察就会发现这个方法的返回值就是@Target(ElementType.METHOD)中的ElementType.METHOD,也就是注解的属性,是一个ElementType枚举。

再来看ElementType的定义:

package java.lang.annotation;
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

这个枚举其实就是定义了注解的适用范围,在Override注解中,@Target的属性是ElementType.METHOD,所以Override这个注解只能用于注解方法。

而Target注解本身也有一个@Target元注解,这个@Target元注解属性是ElementType.ANNOTATION_TYPE,也就是说Target注解只能用作元数据(注解)的注解,所以叫它元注解。

@Retention

@Target声明了Override注解只能使用代码中的方法定义,@Retention注解则定义了注解的保留范围,如:在源代码、CLASS文件或运行时保留。

超出@Retention定义的属性,注解将被丢弃。

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

如果像Override注解中的@Retention定义RetentionPolicy .SOURCE属性,那么生成CLASS文件时不会在方法上见到@Override。由于Override注解用于检测子类有无实现超类或接口的抽象方法,所以只在编译阶段检测语法是否正确就足够了。

@Documented

@Documented也属于元注解:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

如果一个注解定义了@Ducumented,在javadoc API文档时,被这个注解标记的元素在文档上也会出现该注解,例如:

//自定义一个注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.Main;
}

//使用自定义的注解
public class A {
    @Subscribe
    public void a(EventA a){
        System.out.println("tid "+Thread.currentThread().getId()+" run A.a() ,event name is "+a.name);
    }
}

在javadoc生成的文档中可见:

@Subscribe
public void a(EventA a)

而不在Subsribe注解上添加@Documented则不会在方法a(EventA a)上出现@Subscribe。

@Inherited

该元注解比较特殊,只对@Target为ElementType.TYPE(类、接口、枚举)有效,并且只支持类元素。

使用了@Inherited的注解修饰在一个class上,可以保证继承它的子类也拥有同样的注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

小结

通过常见的@Override我们认识了一个注解定义的语法,并且认识了四个基本元注解@Target、@Retention、@Documented、@Inherited以及它们的功能。
其中@Target用来指定注解可以修饰的源代码中的元素(构造器、方法、字段、包、参数、局部变量、类或接口),@Retention指定注解保留的范围(源代码、class文件、运行时),@Documented指定注解是否出现在javadoc生成的API文档中的具体的元素上,@Inherited指定注解是否可以被子类继承。

自定义注解

使用@interface可以定义一个注解,形如:

@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.Main;
}

在本例中threadMode()返回的是注解的一个属性,其返回值可以是所有基本类型、String、Class、enum、Annotation或以上类型的数组。

default关键字可以定义该属性的默认值,如果没有指定默认值在使用注解时必须显示指定属性值。

特别说明的是注解不支持像extends语法这样的继承。

自定义注解以及反射使用注解的例子

本例将仿照Eventbus使用注解的语法实现一个超简易版的Mybus。

//定义运行线程枚举
public enum ThreadMode {
    Posting,Main;
}

//定义注解用于反射识别观察者方法
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.Main;
}

//定义一个MyBus类用于注册、注销观察者或者发出通知给观察者,在发出通知时会遍历观察者集合,找出观察者中被@Subscribe 注解的方法,判断通知事件的类型与@Subscribe 注解方法的参数类型是否匹配,然后根据ThreadMode交给观察者在主线程或子线程处理。
public class MyBus {

    LinkedList mObjs = new LinkedList();

    
    public void register(Object o) {
        mObjs.add(o);
    }

    public void unregister(Object o) {
        mObjs.remove(o);
    }

    public void post(final Object event) {
        // System.out.println("post参数类型:"+event.getClass().getCanonicalName());
        Iterator iterator = mObjs.iterator();
        while (iterator.hasNext()) {
            final Object obj = iterator.next();
            Class cl = obj.getClass();
            // System.out.println("遍历类 "+cl.getCanonicalName());
            for (final Method method : cl.getDeclaredMethods()) {
                Subscribe subscribe = method.getAnnotation(Subscribe.class);
                if (subscribe != null) {
                    // System.out.println("找到注解的方法 "+method.getName());
                    if (method.getParameterCount() == 1) {
                        Class pmClass = (method.getParameterTypes())[0];
                        // System.out.println(method.getName()+"的参数类型:"+pmClass.getCanonicalName());
                        if (pmClass.equals(event.getClass())) {
                            // 判断参数的类型是post参数类型的超类或接口或相等
                            // System.out.println(method.getName()+" 是合法的");
                            try {
                                if (subscribe.threadMode() == ThreadMode.Main) {
                                    method.invoke(obj, event);
                                }else{
                                    new Thread(){
                                        public void run() {
                                            try {
                                                method.invoke(obj, event);
                                            } catch (IllegalAccessException
                                                    | IllegalArgumentException
                                                    | InvocationTargetException e) {
                                                // TODO Auto-generated catch block
                                                e.printStackTrace();
                                            }
                                        };
                                    }.start();
                                }
                            } catch (IllegalAccessException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            } catch (IllegalArgumentException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }

}


//事件A
public class EventA {
    public String name;
}

//事件B
public class EventB {
    public String name;
}

//观察者A
public class A {
    @Subscribe
    public void a(EventA a){
        System.out.println("tid "+Thread.currentThread().getId()+" run A.a() ,event name is "+a.name);
    }
}

//观察者B
public class B {
    @Subscribe(threadMode=ThreadMode.Posting)
    public void b(EventB e){
        System.out.println("tid "+Thread.currentThread().getId()+" run B.b(),event name is "+e.name);
    }
}

//测试
public class Main {

    public static void main(String[] args) {
        A a= new A();
        B b=new B();
        MyBus bus = new MyBus();
        bus.register(a);
        bus.register(b);
        
        EventA eventA = new EventA();
        eventA.name="eventA";
        
        EventB eventB = new EventB();
        eventB.name = "eventB";
        bus.post(eventA);
        bus.post(eventB);
    }

}

测试结果:

tid 1 run A.a() ,event name is eventA
tid 10 run B.b(),event name is eventB

可以看到发出不同的事件A和B,最后根据观察者A和B中方法的注解找到方法处理,由注解中的ThreadMode属性指定在哪个线程执行处理方法。

你可能感兴趣的:(Java Annotation 理解和运用)