Java Annotation 理解和運(yùn)用

前言

在Android開發(fā)作業(yè)中接觸到了很多開源框架使用了Java Annotation機(jī)制,我接觸到的就有GreenRobot、Dagger2、AndFix等項目。

那么 Annotation機(jī)制到底是如何發(fā)揮作用的?下面將介紹Annotation的常見類型及基本語法。

從@Override認(rèn)識注解

相信大部分同學(xué)對@Override一點(diǎn)都不陌生,在子類覆蓋超類的方法時,Eclipse等IDE會在方法上自動生成這個注解。

那么來看一下這個注解的語法形式:

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

其中@interface 定義了Override是一個Annotation類型,或者叫元數(shù)據(jù)(meta-data)。
@Target和@Retetion是對Override的注解,稱之為元注解(元數(shù)據(jù)的注解)。

@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也是一個注解類型,在其內(nèi)部定義了方法ElementType[] value();

細(xì)心觀察就會發(fā)現(xiàn)這個方法的返回值就是@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
}

這個枚舉其實(shí)就是定義了注解的適用范圍,在Override注解中,@Target的屬性是ElementType.METHOD,所以O(shè)verride這個注解只能用于注解方法。

而Target注解本身也有一個@Target元注解,這個@Target元注解屬性是ElementType.ANNOTATION_TYPE,也就是說Target注解只能用作元數(shù)據(jù)(注解)的注解,所以叫它元注解。

@Retention

@Target聲明了Override注解只能使用代碼中的方法定義,@Retention注解則定義了注解的保留范圍,如:在源代碼、CLASS文件或運(yùn)行時保留。

超出@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注解用于檢測子類有無實(shí)現(xiàn)超類或接口的抽象方法,所以只在編譯階段檢測語法是否正確就足夠了。

@Documented

@Documented也屬于元注解:

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

如果一個注解定義了@Ducumented,在javadoc API文檔時,被這個注解標(biāo)記的元素在文檔上也會出現(xiàn)該注解,例如:

//自定義一個注解
@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)上出現(xiàn)@Subscribe。

@Inherited

該元注解比較特殊,只對@Target為ElementType.TYPE(類、接口、枚舉)有效,并且只支持類元素。

使用了@Inherited的注解修飾在一個class上,可以保證繼承它的子類也擁有同樣的注解。

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

小結(jié)

通過常見的@Override我們認(rèn)識了一個注解定義的語法,并且認(rèn)識了四個基本元注解@Target、@Retention、@Documented、@Inherited以及它們的功能。
其中@Target用來指定注解可以修飾的源代碼中的元素(構(gòu)造器、方法、字段、包、參數(shù)、局部變量、類或接口),@Retention指定注解保留的范圍(源代碼、class文件、運(yùn)行時),@Documented指定注解是否出現(xiàn)在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或以上類型的數(shù)組。

default關(guān)鍵字可以定義該屬性的默認(rèn)值,如果沒有指定默認(rèn)值在使用注解時必須顯示指定屬性值。

特別說明的是注解不支持像extends語法這樣的繼承。

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

本例將仿照Eventbus使用注解的語法實(shí)現(xiàn)一個超簡易版的Mybus。

//定義運(yùn)行線程枚舉
public enum ThreadMode {
    Posting,Main;
}

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

//定義一個MyBus類用于注冊、注銷觀察者或者發(fā)出通知給觀察者,在發(fā)出通知時會遍歷觀察者集合,找出觀察者中被@Subscribe 注解的方法,判斷通知事件的類型與@Subscribe 注解方法的參數(shù)類型是否匹配,然后根據(jù)ThreadMode交給觀察者在主線程或子線程處理。
public class MyBus {

    LinkedList<Object> mObjs = new LinkedList<Object>();

    
    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參數(shù)類型:"+event.getClass().getCanonicalName());
        Iterator<Object> iterator = mObjs.iterator();
        while (iterator.hasNext()) {
            final Object obj = iterator.next();
            Class<? extends Object> 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()+"的參數(shù)類型:"+pmClass.getCanonicalName());
                        if (pmClass.equals(event.getClass())) {
                            // 判斷參數(shù)的類型是post參數(shù)類型的超類或接口或相等
                            // 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);
    }

}

測試結(jié)果:

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

可以看到發(fā)出不同的事件A和B,最后根據(jù)觀察者A和B中方法的注解找到方法處理,由注解中的ThreadMode屬性指定在哪個線程執(zhí)行處理方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容