對于注解不了解的犀呼,可以看一下這篇文章:
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
注解保留至運行期瞭空,意味著我們能夠在運行期間結合反射技術獲取注解中的所有信息。