Java 注解

對于注解不了解的犀呼,可以看一下這篇文章:
IOC依賴注入

Java 注解(Annotation)又稱 Java 標注,是 JDK5.0 引入的一種注釋機制磁浇。 注解是元數據的一種形式,提供有關于程序但不屬于程序本身的數據略号。注解對它們注解的代碼的操作沒有直接影響缝裤。

了解注解前先看看元數據的概念

1.元數據概念

元數據是關于數據的數據肉渴。在編程語言上下文中房蝉,元數據是添加到程序元素如方法僚匆、字段、類和包上的額外信息搭幻。對數據進行說明描述的數據咧擂。

2.元數據的作用

一般來說,元數據可以用于創(chuàng)建文檔(根據程序元素上的注釋創(chuàng)建文檔)粗卜,跟蹤代碼中的依賴性(可聲明方法是重載屋确,依賴父類的方法)纳击,執(zhí)行編譯時檢查(可聲明是否編譯期檢測)续扔,代碼分析。
如下:
1) 編寫文檔:通過代碼里標識的元數據生成文檔  
2)代碼分析:通過代碼里標識的元數據對代碼進行分析  
3)編譯檢查:通過代碼里標識的元數據讓編譯器能實現基本的編譯檢查

關于注解(Annotation)的作用焕数,其實就是上述元數據的作用纱昧。

注意:Annotation 能被用來為程序元素(類、方法堡赔、成員變量等)設置元素據识脆。Annotaion 不影響程序代碼的執(zhí)行,無論增加善已、刪除 Annotation灼捂,代碼都始終如一地執(zhí)行。如果希望讓程序中的 Annotation 起一定的作用换团,只有通過解析工具或編譯工具對 Annotation 中的信息進行解析和處理悉稠。

注解聲明

聲明一個注解類型

Java 中所有的注解,默認實現 Annotation 接口:

package java.lang.annotation;
public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

與聲明一個Class不同的是艘包,注解的聲明使用 @interface 關鍵字的猛。一個注解的聲明如下:

public @interface Study{ }

元注解

在定義注解時,注解類也能夠使用其他的注解聲明想虎。對注解類型進行注解的注解類卦尊,我們稱之為 meta-annotation(元注解)。一般的舌厨,我們在定義自定義注解時岂却,需要指定的元注解有兩個:

另外還有 @Documented 與 @Inherited 元注解,前者用于被 javadoc 工具提取成文檔,后者表示允許子類繼承父類中定義的注解淌友。

1. @Target

在定義注解類時候煌恢,需要標記個 Target 注解,里面?zhèn)魅胍粋€ ElementType 枚舉類震庭,這個注解是為了標明我們定義的注解在什么地方使用瑰抵。

public enum ElementType {
    // 在class上面打標記
    TYPE,
    // 在成員變量打標記
    FIELD,
    // 在方法打標記
    METHOD,
    // 在參數打標記
    PARAMETER,
    // 在構造方法打標記
    CONSTRUCTOR,
    // 在本地變量打標記
    LOCAL_VARIABLE,
    // 在注解類打標記
    ANNOTATION_TYPE,
}
  • ElementType.ANNOTATION_TYPE 可以應用于注解類型。
  • ElementType.CONSTRUCTOR 可以應用于構造函數器联。
  • ElementType.FIELD 可以應用于字段或屬性二汛。
  • ElementType.LOCAL_VARIABLE 可以應用于局部變量。
  • ElementType.METHOD 可以應用于方法級注解拨拓。
  • ElementType.PACKAGE 可以應用于包聲明肴颊。
  • ElementType.PARAMETER 可以應用于方法的參數。
  • ElementType.TYPE 可以應用于類的任何元素渣磷。

比如我們標記了一個ElementType.Type婿着,那么這個注解就只能用在在class上面,用在成員變量上面就會報錯醋界。

2. @Retention
public enum RetentionPolicy {
    // 注解只保留在源文件竟宋,當Java文件編譯成class文件的時候,注解被遺棄形纺;
    SOURCE,
    // 注解被保留到class文件丘侠,但jvm加載class文件時候被遺棄,這是默認的生命周期逐样;
    CLASS,
    // 注解不僅被保存到class文件中蜗字,jvm加載class文件之后,仍然存在脂新;
    RUNTIME
}

注解指定標記注解的存儲方式:

  • RetentionPolicy.SOURCE - 標記的注解僅保留在源碼級別中挪捕,并被編譯器忽略。
  • RetentionPolicy.CLASS - 標記的注解在編譯時由編譯器保留争便,但 Java 虛擬機(JVM)會忽略级零。
  • RetentionPolicy.RUNTIME - 標記的注解由 JVM 保留,因此運行時環(huán)境可以使用它始花。

@Retention 三個值中 SOURCE < CLASS < RUNTIME妄讯,即 CLASS 包含了SOURCE,RUNTIME 包含 SOURCE酷宵、CLASS亥贸。下文會介紹他們不同的應用場景。

可以看到除了RUNTIME浇垦,其他兩個都最終會被拋棄炕置,所以我們要實現功能性代碼的時候,就需要使用RUNTIME這個類。

例子:

//@Target(ElementType.TYPE) 只能在類上標記該注解 
@Target({ElementType.TYPE,ElementType.FIELD}) // 允許在類與類屬性上標記該注解 
@Retention(RetentionPolicy.SOURCE) //注解保留在源碼中
public @interface Study { 
}

注解類型元素

在上文元注解中朴摊,允許在使用注解時傳遞參數默垄。我們也能讓自定義注解的主體包含 annotation type element (注解 類型元素) 聲明,它們看起來很像方法甚纲,可以定義可選的默認值口锭。

@Target({ElementType.TYPE,ElementType.FIELD}) 
@Retention(RetentionPolicy.SOURCE) 
public @interface Study { 
    String value(); // 無默認值 
    int age() default 1; // 有默認值 
}

注意:在使用注解時,如果定義的注解中的類型元素無默認值介杆,則必須進行傳值鹃操。

@Study("帥") // 如果只存在 value 元素需要傳值的情況,則可以省略:元素名= 
@Study(value="帥",age = 2)
int i;

注解應用場景

按照 @Retention 元注解定義的注解存儲方式春哨,注解可以被在三種場景下使用:

1. SOURCE

RetentionPolicy.SOURCE 荆隘,作用于源碼級別的注解,可提供給 IDE 語法檢查赴背、APT 等場景使用椰拒。

在類中使用 SOURCE 級別的注解,其編譯之后的 class 中會被丟棄凰荚。

IDE語法檢查
在 Android 開發(fā)中燃观, support-annotations 與 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定義如下:

@Retention(SOURCE) //源碼級別注解 
@Target({ANNOTATION_TYPE}) 
public @interface IntDef { 
    int[] value() default {}; 
    boolean flag() default false;
    boolean open() default false; 
}

Java 中 Enum(枚舉)的實質是特殊單例的靜態(tài)成員變量浇揩,在運行期所有枚舉類作為單例仪壮,全部加載到內存中憨颠。比常量多5到10倍的內存占用胳徽。

此注解的意義在于能夠取代枚舉,實現如方法入參限制爽彤。

如:我們定義方法 test 养盗,此方法接收參數 teacher 需要在:Lance、Alvin 中選擇一個适篙。如果使用枚舉能夠實現為:

public enum Teacher{ 
    LANCE,
    ALVIN 
}
public void test(Teacher teacher) {
}

而現在為了進行內存優(yōu)化往核,我們現在不再使用枚舉,則方法定義為:

public static final int LANCE = 1;
public static final int ALVIN = 2;
public void test(int teacher) {
}

然而此時嚷节,調用 test 方法由于采用基本數據類型 int聂儒,將無法進行類型限定。此時使用 @IntDef 增加自定義注解:

public static final int LANCE = 1; 
public static final int ALVIN = 2; 

@IntDef(value = {LANCE, ALVIN}) //限定為LANCE硫痰,ALVIN 
@Target(ElementType.PARAMETER) //作用于參數的注解
@Retention(RetentionPolicy.SOURCE) //源碼級別注解
public @interface Teacher { 

}

public void test(@Teacher int teacher) {
 
}

此時衩婚,我們再去調用 test 方法,如果傳遞的參數不是 LANCE 或者 ALVIN 則會顯示 Inspection 警告(編譯不會報錯)效斑。

可以修改此類語法檢查級別:

以上注解均為 SOURCE 級別非春,本身 IDEA/AS 就是由Java開發(fā)的,工具實現了對Java語法的檢查,借助注解能對被注解的特定語法進行額外檢查奇昙。

APT注解處理器

APT全稱為:"Anotation Processor Tools"护侮,意為注解處理器。顧名思義储耐,其用于處理注解羊初。編寫好的 Java 源文件,需要經過 javac 的編譯什湘,翻譯為虛擬機能夠加載解析的字節(jié)碼 Class 文件凳忙。注解處理器是 javac 自帶的一個工具,用來在編譯時期掃描處理注解信息禽炬。你可以為某些注解注冊自己的注解處理器涧卵。 注冊的注解處理器由 javac 調起,并將注解信息傳遞給注解處理器進行處理腹尖。

注解處理器是對注解應用最為廣泛的場景柳恐。在 Glide、EventBus3热幔、Butterknifer乐设、Tinker、ARouter 等等常用框架中都有注解處理器的身影绎巨。這些框架中對注解的定義并不是 SOURCE 級別近尚,更多的是 CLASS 級別,別忘了:CLASS 包含了 SOURCE场勤,RUNTIME 包含 SOURCE戈锻、CLASS。

注解處理器中process方法為什么會執(zhí)行多次和媳?

// 注解處理器:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

    Messager messager = processingEnv.getMessager();
    messager.printMessage(Diagnostic.Kind.NOTE,"oooooooooooo"+"1111111111111111111"+System.currentTimeMillis());
    return true;
}

java編譯過程:詞法分析格遭、語法分析、填充符號表留瞳、注解處理器處理注解拒迅、語義分析、解語法糖她倘、生成字節(jié)碼璧微。

注解處理器可以增刪改抽象語法樹的任意元素。執(zhí)行到注解處理器硬梁,都會重新執(zhí)行詞法分析前硫、語法分析、填充符號表步靶溜,直到注解處理器不再對語法樹進行修改為止开瞭,每一次的循環(huán)過程都稱為一次Round懒震。

process第一個參數set集合是要處理的注解集合,如果這個集合為null了嗤详,就不需要處理了个扰。寫代碼就這樣去做。
if(!set.isEmpty()){
//…執(zhí)行處理
}
apt自身的功能很簡單葱色,就起到一個觸發(fā)器的作用递宅。

2. CLASS

定義為 CLASS 的注解,會保留在 class 文件中苍狰,但是會被虛擬機忽略(即無法在運行期反射獲取注解)办龄。此時完全符合此種注解的應用場景為字節(jié)碼操作。如:AspectJ淋昭、熱修復 Roubust 中應用此場景俐填。

所謂字節(jié)碼操作即為,直接修改字節(jié)碼 Class 文件以達到修改代碼執(zhí)行邏輯的目的翔忽。在程序中有多處需要進行是否登錄的判斷英融。

如果我們使用普通的編程方式,需要在代碼中進行 if-else 的判斷歇式,也許存在十個判斷點驶悟,則需要在每個判斷點加入此項判斷。此時材失,我們可以借助AOP(面向切面)編程思想痕鳍,將程序中所有功能點劃分為: 需要登錄 與 無需登錄兩種類型,即兩個切面龙巨。對于切面的區(qū)分即可采用注解笼呆。

//Java源碼 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS) 
public @interface Login { 
}

@Login 
public void jumpA(){ 
  startActivity(new Intent(this,AActivity.class)); 
}

public void jumpB(){ 
  startActivity(new Intent(this,BActivity.class));
}

在上訴代碼中, jumpA 方法需要具備登錄身份恭应。而 Login 注解的定義被設置為 CLASS 抄邀。因此我們能夠在該類所編譯的字節(jié)碼中獲得到方法注解 Login 耘眨。在操作字節(jié)碼時昼榛,就能夠根據方法是否具備該注解來修改class中該方法的內容加入 if-else 的代碼段:

//Class字節(jié)碼 
@Login 
public void jumpA() { 
  if (this.isLogin) { 
     this.startActivity(new Intent(this, LoginActivity.class)); 
  } else { 
     this.startActivity(new Intent(this, AActivity.class));
  } 
}

public void jumpB() { 
  startActivity(new Intent(this,BActivity.class)); 
}

注解能夠設置類型元素(參數),結合參數能實現更為豐富的場景剔难,如:運行期權限判定等胆屿。

字節(jié)碼增強技術相當于是一把打開運行時JVM的鑰匙,利用它可以動態(tài)地對運行中的程序做修改偶宫,也可以跟蹤JVM運行中程序的狀態(tài)非迹。此外,我們平時使用的動態(tài)代理纯趋、AOP也與字節(jié)碼增強密切相關憎兽,它們實質上還是利用各種手段生成或修改符合規(guī)范的字節(jié)碼文件冷离。綜上所述,掌握字節(jié)碼增強后可以高效地定位并快速修復一些棘手的問題(如線上性能問題纯命、方法出現不可控的出入參需要緊急加日志等問題)西剥,也可以在開發(fā)中減少冗余代碼,大大提高開發(fā)效率亿汞。
字節(jié)碼增強技術探索

3. RUNTIME

注解保留至運行期瞭空,意味著我們能夠在運行期間結合反射技術獲取注解中的所有信息。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末疗我,一起剝皮案震驚了整個濱河市咆畏,隨后出現的幾起案子,更是在濱河造成了極大的恐慌吴裤,老刑警劉巖旧找,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異麦牺,居然都是意外死亡钦讳,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門枕面,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愿卒,“玉大人,你說我怎么就攤上這事潮秘∏砜” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵枕荞,是天一觀的道長柜候。 經常有香客問我,道長躏精,這世上最難降的妖魔是什么渣刷? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮矗烛,結果婚禮上辅柴,老公的妹妹穿的比我還像新娘。我一直安慰自己瞭吃,他們只是感情好碌嘀,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歪架,像睡著了一般股冗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上和蚪,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天止状,我揣著相機與錄音烹棉,去河邊找鬼。 笑死怯疤,一個胖子當著我的面吹牛峦耘,可吹牛的內容都是我干的。 我是一名探鬼主播旅薄,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辅髓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了少梁?” 一聲冷哼從身側響起洛口,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凯沪,沒想到半個月后第焰,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡妨马,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年挺举,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烘跺。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡湘纵,死狀恐怖,靈堂內的尸體忽然破棺而出滤淳,到底是詐尸還是另有隱情梧喷,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布脖咐,位于F島的核電站铺敌,受9級特大地震影響,放射性物質發(fā)生泄漏屁擅。R本人自食惡果不足惜偿凭,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望派歌。 院中可真熱鬧弯囊,春花似錦、人聲如沸硝皂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稽物。三九已至,卻和暖如春折欠,著一層夾襖步出監(jiān)牢的瞬間贝或,已是汗流浹背吼过。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咪奖,地道東北人盗忱。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像羊赵,于是被迫代替她去往敵國和親趟佃。 傳聞我的和親對象是個殘疾皇子想罕,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內容