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

前言

在Android開(kāi)發(fā)作業(yè)中接觸到了很多開(kāi)源框架使用了Java Annotation機(jī)制闰蛔,我接觸到的就有GreenRobot、Dagger2图柏、AndFix等項(xiàng)目序六。

那么 Annotation機(jī)制到底是如何發(fā)揮作用的?下面將介紹Annotation的常見(jiàn)類(lèi)型及基本語(yǔ)法蚤吹。

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

相信大部分同學(xué)對(duì)@Override一點(diǎn)都不陌生例诀,在子類(lèi)覆蓋超類(lèi)的方法時(shí),Eclipse等IDE會(huì)在方法上自動(dòng)生成這個(gè)注解裁着。

那么來(lái)看一下這個(gè)注解的語(yǔ)法形式:

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

其中@interface 定義了Override是一個(gè)Annotation類(lèi)型繁涂,或者叫元數(shù)據(jù)(meta-data)。
@Target和@Retetion是對(duì)Override的注解二驰,稱之為元注解(元數(shù)據(jù)的注解)扔罪。

@Target

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

細(xì)心觀察就會(huì)發(fā)現(xiàn)這個(gè)方法的返回值就是@Target(ElementType.METHOD)中的ElementType.METHOD,也就是注解的屬性桶雀,是一個(gè)ElementType枚舉矿酵。

再來(lái)看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
}

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

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

@Retention

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

超出@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文件時(shí)不會(huì)在方法上見(jiàn)到@Override胧华。由于Override注解用于檢測(cè)子類(lèi)有無(wú)實(shí)現(xiàn)超類(lèi)或接口的抽象方法寄症,所以只在編譯階段檢測(cè)語(yǔ)法是否正確就足夠了。

@Documented

@Documented也屬于元注解:

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

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

//自定義一個(gè)注解
@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生成的文檔中可見(jiàn):

@Subscribe
public void a(EventA a)

而不在Subsribe注解上添加@Documented則不會(huì)在方法a(EventA a)上出現(xiàn)@Subscribe悲没。

@Inherited

該元注解比較特殊篮迎,只對(duì)@Target為ElementType.TYPE(類(lèi)、接口示姿、枚舉)有效甜橱,并且只支持類(lèi)元素。

使用了@Inherited的注解修飾在一個(gè)class上栈戳,可以保證繼承它的子類(lèi)也擁有同樣的注解岂傲。

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

小結(jié)

通過(guò)常見(jiàn)的@Override我們認(rèn)識(shí)了一個(gè)注解定義的語(yǔ)法,并且認(rèn)識(shí)了四個(gè)基本元注解@Target荧琼、@Retention譬胎、@Documented、@Inherited以及它們的功能命锄。
其中@Target用來(lái)指定注解可以修飾的源代碼中的元素(構(gòu)造器堰乔、方法、字段脐恩、包镐侯、參數(shù)驶冒、局部變量、類(lèi)或接口)崇猫,@Retention指定注解保留的范圍(源代碼诅炉、class文件屋厘、運(yùn)行時(shí))汗洒,@Documented指定注解是否出現(xiàn)在javadoc生成的API文檔中的具體的元素上,@Inherited指定注解是否可以被子類(lèi)繼承瞻凤。

自定義注解

使用@interface可以定義一個(gè)注解鲫构,形如:

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

在本例中threadMode()返回的是注解的一個(gè)屬性结笨,其返回值可以是所有基本類(lèi)型湿镀、String、Class勉痴、enum、Annotation或以上類(lèi)型的數(shù)組瀑罗。

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

特別說(shuō)明的是注解不支持像extends語(yǔ)法這樣的繼承摧玫。

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

本例將仿照Eventbus使用注解的語(yǔ)法實(shí)現(xiàn)一個(gè)超簡(jiǎn)易版的Mybus绑青。

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

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

//定義一個(gè)MyBus類(lèi)用于注冊(cè)闸婴、注銷(xiāo)觀察者或者發(fā)出通知給觀察者,在發(fā)出通知時(shí)會(huì)遍歷觀察者集合邪乍,找出觀察者中被@Subscribe 注解的方法溺欧,判斷通知事件的類(lèi)型與@Subscribe 注解方法的參數(shù)類(lèi)型是否匹配,然后根據(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ù)類(lèi)型:"+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("遍歷類(lèi) "+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ù)類(lèi)型:"+pmClass.getCanonicalName());
                        if (pmClass.equals(event.getClass())) {
                            // 判斷參數(shù)的類(lèi)型是post參數(shù)類(lèi)型的超類(lèi)或接口或相等
                            // 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);
    }
}

//測(cè)試
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);
    }

}

測(cè)試結(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屬性指定在哪個(gè)線程執(zhí)行處理方法钥勋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辆苔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驻啤,老刑警劉巖菲驴,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骑冗,死亡現(xiàn)場(chǎng)離奇詭異赊瞬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)巧涧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)磁携,“玉大人褒侧,你說(shuō)我怎么就攤上這事谊迄∶乒” “怎么了歪脏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵婿失,是天一觀的道長(zhǎng)豪硅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)懒浮,這世上最難降的妖魔是什么砚著? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任次伶,我火速辦了婚禮,結(jié)果婚禮上稽穆,老公的妹妹穿的比我還像新娘冠王。我一直安慰自己,他們只是感情好舌镶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布柱彻。 她就那樣靜靜地躺著,像睡著了一般餐胀。 火紅的嫁衣襯著肌膚如雪绒疗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天骂澄,我揣著相機(jī)與錄音,去河邊找鬼惕虑。 笑死坟冲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溃蔫。 我是一名探鬼主播健提,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伟叛!你這毒婦竟也來(lái)了私痹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤统刮,失蹤者是張志新(化名)和其女友劉穎紊遵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體侥蒙,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暗膜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鞭衩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片学搜。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖论衍,靈堂內(nèi)的尸體忽然破棺而出瑞佩,到底是詐尸還是另有隱情,我是刑警寧澤坯台,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布炬丸,位于F島的核電站,受9級(jí)特大地震影響捂人,放射性物質(zhì)發(fā)生泄漏御雕。R本人自食惡果不足惜矢沿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酸纲。 院中可真熱鬧捣鲸,春花似錦、人聲如沸闽坡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疾嗅。三九已至外厂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間代承,已是汗流浹背汁蝶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留论悴,地道東北人掖棉。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膀估,于是被迫代替她去往敵國(guó)和親幔亥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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