注解-學(xué)習(xí)筆記

一沉御、注解

從JDK 5 開始屿讽,Java 新增注解,注解是代碼里的特殊標記吠裆,這些標記可以在編譯伐谈、類加載脏里、運行時被讀取挠日,并執(zhí)行相應(yīng)的處理。

通過使用注解助赞,開發(fā)人員可以在不改變原有邏輯的情況下效斑,在源文件中嵌入一些補充的信息非春。代碼分析工具柱徙、開發(fā)工具和部署工具可以通過這些補充信息進行驗證缓屠、處理或者進行部署。

1.1 注解分類

注解分為標準注解和元注解护侮。

1.1.1 標準注解

標準注解有以下 4 種敌完。
? @Override:對覆蓋超類中的方法進行標記,如果被標記的方法并沒有實際覆蓋超類中的方法羊初,則編譯器會發(fā)出錯誤警告滨溉。
? @Deprecated:對不鼓勵使用或者已過時的方法添加注解什湘,當(dāng)編程人員使用這些方法時,將會在編譯時顯示提示信息晦攒。
? @SuppressWarnings:選擇性地取消特定代碼段中的警告闽撤。
? @SafeVarargs:JDK 7 新增,用來聲明使用了可變長度參數(shù)的方法脯颜,其在與泛型類一起使用時不會出現(xiàn)類型安全問題哟旗。

1.1.2. 元注解

元注解用來注解其他注解,從而創(chuàng)建新的注解栋操。元注解有以下幾種闸餐。

? @Targe:注解所修飾的對象范圍。
? @Inherited:表示注解可以被繼承矾芙。
? @Documented:表示這個注解應(yīng)該被 JavaDoc 工具記錄舍沙。
? @Retention:用來聲明注解的保留策略。
? @Repeatable:JDK 8 新增剔宪,允許一個注解在同一聲明類型(類拂铡、屬性或方法)上多次使用。

其中@Targe 注解取值是一個 ElementType 類型的數(shù)組葱绒,其中有以下幾種取值和媳,對應(yīng)不同的對象范圍。
? ElementType.TYPE:能修飾類哈街、接口或枚舉類型留瞳。
? ElementType.FIELD:能修飾成員變量。
? ElementType.METHOD:能修飾方法骚秦。
? ElementType.PARAMETER:能修飾參數(shù)她倘。
? ElementType.CONSTRUCTOR:能修飾構(gòu)造方法。
? ElementType.LOCAL_VARIABLE:能修飾局部變量作箍。
? ElementType.ANNOTATION_TYPE:能修飾注解硬梁。
? ElementType.PACKAGE:能修飾包。
? ElementType.TYPE_PARAMETER:類型參數(shù)聲明胞得。
? ElementType.TYPE_USE:使用類型荧止。

其中@Retention 注解有 3 種類型,分別表示不同級別的保留策略阶剑。
? RetentionPolicy.SOURCE:源碼級注解跃巡。注解信息只會保留在.java 源碼中,源碼在編譯后牧愁,注解信息被丟棄素邪,不會保留在.class 中。
? RetentionPolicy.CLASS:編譯時注解猪半。注解信息會保留在.java 源碼以及.class 中兔朦。當(dāng)運行 Java 程序時偷线,JVM 會丟棄該注解信息,不會保留在 JVM 中沽甥。
? RetentionPolicy.RUNTIME:運行時注解声邦。當(dāng)運行 Java 程序時,JVM 也會保留該注解信息摆舟,可以通過反射獲取該注解信息翔忽。

1.2 定義注解

1.2.1 基本定義

定義新的注解類型使用@interface 關(guān)鍵字,這與定義一個接口很像盏檐,如下所示:

public @interface AnnotationCreateMan {

}

定義完注解后歇式,就可以在程序中使用該注解:

@AnnotationCreateMan
public class AnnotationTest {
}

1.2.2 定義成員變量

注解只有成員變量,沒有方法胡野。注解的成員變量在注解定義中以“無形參的方法”形式來聲明材失,其“方法名”定義了該成員變量的名字,其返回值定義了該成員變量的類型:

public @interface AnnotationCreateMan {
    String name();

    int age();
}

上面的代碼定義了兩個成員變量硫豆,這兩個成員變量以方法的形式來定義龙巨。定義了成員變量后,使用該注解時就應(yīng)該為該注解的成員變量指定值:

@AnnotationCreateMan(name = "Tom", age = 35)
public class AnnotationTest {
    @AnnotationCreateMan(name = "Jim", age = 22)
    public void fighting() {

    }
}

也可以在定義注解的成員變量時熊响,使用 default 關(guān)鍵字為其指定默認值旨别,如下所示:

public @interface AnnotationCreateMan {
    String name() default "Jack";

    int age() default 25;
}

因為注解定義了默認值,所以使用時可以不為這些成員變量指定值汗茄,而是直接使用默認值:

@AnnotationCreateMan(name = "Tom", age = 35)
public class AnnotationTest {
    @AnnotationCreateMan
    public void fight() {

    }
}

1.2.3 定義運行時注解

可以用@Retention 來設(shè)定注解的保留策略秸弛,這 3 個策略的生命周期長度為 SOURCE < CLASS < RUNTIME 。生命周期短的能起作用的地方洪碳,生命周期長的一定也能起作用递览。一般如果需要在運行時去動態(tài)獲取注解信息,那只能用 RetentionPolicy.RUNTIME瞳腌;如果要在編譯時進行一些預(yù)處理操作绞铃,比如生成一些輔助代碼,就用 RetentionPolicy.CLASS嫂侍;如果只是做一些檢查性的操作儿捧,比如 @Override 和 @SuppressWarnings,則可選用 RetentionPolicy.SOURCE挑宠。當(dāng)
設(shè)定為 RetentionPolicy.RUNTIME 時菲盾,這個注解就是運行時注解,如下所示:

@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationCreateMan {
    String name() default "Jack";

    int age() default 25;
}

1.2.4 定義編譯時注解

同樣地痹栖,如果將@Retention 的保留策略設(shè)定為 RetentionPolicy.CLASS亿汞,這個注解就是編譯時注解瞭空,如下所示:

@Retention(RetentionPolicy.CLASS)
public @interface AnnotationCreateMan {
    String name() default "Jack";

    int age() default 25;
}

1.3 注解處理器

如果沒有處理注解的工具揪阿,那么注解也不會有什么大的作用疗我。對于不同的注解有不同的注解處理器。雖然注解處理器的編寫會千變?nèi)f化南捂,但是其也有處理標準吴裤,比如:針對運行時注解會采用反射機制處理,針對編譯時注解會采用 AbstractProcessor 來處理溺健。

1.3.1 運行時注解處理器

處理運行時注解需要用到反射機制麦牺。首先我們要定義運行時注解,如下所示:

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
    String value() default ""鞭缭;
}

上面的代碼是 Retrofit 中定義的@GET 注解剖膳。其定義了@Target(METHOD),這等效于@Target(ElementType.METHOD)岭辣,意味著 GET 注解應(yīng)用于方法吱晒。接下來應(yīng)用該注解,如下所示:

public class AnnotationTest {
    @GET(value = "http://ip.taobao.com/59.108.54.3")
    public String getIpMsg() {
        return "";
    }

    @GET(value = "http://ip.taobao.com/")
    public String getIp() {
        return "";
    }
}

上面的代碼為@GET 的成員變量賦值沦童。接下來寫一個簡單的注解處理器仑濒,如下所示:

public class AnnotationProcessor {
    private static final String TAG = "AnnotationProcessor";

    public static void initAnnotation() {
        Method[] methods = AnnotationTest.class.getDeclaredMethods();
        for (Method m : methods) {
            GET get = m.getAnnotation(GET.class);
            Log.v(TAG, get.value());
        }
    }
}

上面的代碼用到了兩個反射方法:getDeclaredMethods 和 getAnnotation,它們都屬于AnnotatedElement 接口偷遗,Class墩瞳、Method 和 Filed 等類都實現(xiàn)了該接口。調(diào)用 getAnnotation 方法返回指定類型的注解對象氏豌,也就是 GET喉酌。最后調(diào)用 GET 的 value 方法返回從 GET 對象中提取元素的值。

調(diào)用注解處理器:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        AnnotationProcessor.initAnnotation();
    }
}

輸出結(jié)果為:

注解輸出結(jié)果

1.3.2 編譯時注解處理器

處理編譯時注解的步驟稍微有點多泵喘,首先要定義注解瞭吃。
(1)定義注解
這里首先在項目中新建一個 Java Library 來專門存放注解,這個 Library 名為 annotations涣旨。接下來定義注解歪架,如下所示:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default 1;
}

上面代碼中定義的注解類似于 ButterKnife 的@BindView 注解。
(2)編寫注解處理器
我們在項目中再新建一個 Java Library 來存放注解處理器霹陡,這個 Library 名為 processor和蚪。我們來配置 processor 庫的 build.gradle:

apply plugin: 'java'
dependencies {
 compile fileTree(include: ['*.jar'], dir: 'libs')
 compile project(':annotations')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

接下來編寫注解處理器 ClassProcessor,它繼承 AbstractProcessor烹棉,如下所示:

public class ClassProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //TODO 處理注解
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<String>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

process 方法的實現(xiàn)會在后文講到攒霹,這里首先分別介紹這 4 個方法的作用。
? init:被注解處理工具調(diào)用浆洗,并輸入 ProcessingEnviroment 參數(shù)催束。ProcessingEnviroment提供很多有用的工具類,比如 Elements伏社、TypesFiler 和 Messager 等抠刺。
? process:相當(dāng)于每個處理器的主函數(shù) main()塔淤,在這里寫你的掃描、評估和處理注解的代碼速妖,以及生成 Java 文件高蜂。輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素罕容。
? getSupportedAnnotationTypes:這是必須指定的方法备恤,指定這個注解處理器是注冊給哪個注解的。注意锦秒,它的返回值是一個字符串的集合露泊,包含本處理器想要處理的注解類型的合法全稱。
? getSupportedSourceVersion :用來指定你使用的 Java 版本旅择,通常這里返回SourceVersion.latestSupported()滤淳。 在Java 7 以后,也可以使用注解來代替getSupportedAnnotationTypes 方法getSupportedSourceVersion方法砌左,如下所示:

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.lzw.annotations.BindView")
public class ClassProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}

但是考慮到 Android 兼容性的問題脖咐,這里不建議采用這種注解的方式。接下來編寫還未實現(xiàn)的 process 方法汇歹,如下所示:

    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Messager messager = processingEnv.getMessager();
        for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
            if (element.getKind() == ElementKind.FIELD) {
                messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:"
                        + element.toString());
            }
        }
        return true;
    }

這里用到 Messager 的 printMessage 方法來打印出注解修飾的成員變量的名稱屁擅,這個名稱會在 Android Studio 的 Gradle Console 窗口中打印出來。

(3)注冊注解處理器
為了能使用注解處理器产弹,需要用一個服務(wù)文件來注冊它∨筛瑁現(xiàn)在我們就來創(chuàng)建這個服務(wù)文件。

  • 首先在 processor 庫的 main 目錄下新建 resources 資源文件夾
  • 接下來在 resources 中再建立META-INF/services 目錄文件夾痰哨。
  • 最后在 META-INF/services 中創(chuàng)建 javax.annotation.processing.Processor 文件胶果,這個文件中的內(nèi)容是注解處理器的名稱。
    這里我們的 javax.annotation.processing.Processor 文件的內(nèi)容為 com.lzw.annotationprocessor.ClassProcessor斤斧。

整個項目的目錄結(jié)構(gòu)如圖 9-1 所示


注解demo項目結(jié)構(gòu)

如果你覺得前面創(chuàng)建服務(wù)文件的步驟比較麻煩早抠,也可以使用 Google 開源的 AutoService,它用來自動生成 METAINF/services/javax.annotation.processing.Processor 文件撬讽。首先我們添加該開源庫蕊连,可以在 File→Project Structure 搜索“auto-service”查找該庫并添加,如下圖所示游昼。也可以在 processor 的 build.gradle 中直接添加如下代碼:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(":annotations")
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
}

最后在注解處理器 ClassProcessor 中添加@AutoService(Processor.class)就可以了:

@AutoService(Processor.class)
public class ClassProcessor extends AbstractProcessor {
...
}

(4)應(yīng)用注解
接下來在我們的主工程項目(app)中引用注解甘苍。首先要在主工程項目的 build.graldle 中引用 annotations 和 processor 這兩個庫:

dependencies {
...
    implementation project(':annotations')
    annotationProcessor project(':annotationProcessor')
}

接下來在 MainActivity 中應(yīng)用注解,如下所示:

public class MainActivity extends AppCompatActivity {

    //編譯時注解
    @BindView(value = 111)
    TextView tv_show_name;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //運行時注解
        AnnotationProcessor.initAnnotation();
    }
}

最后烘豌,我們先 Clean Project 再 Make Project载庭,在 Gradle Console 窗口中打印的結(jié)果如圖所示:

編譯時注解結(jié)果

可以發(fā)現(xiàn)編譯時會打印出@BindView 注解修飾的成員變量名:tv_show_name。

(5)使用 android-apt 插件(這個問題目前還未解決)
我們的主工程項目(app)中引用了 processor 庫,但注解處理器只在編譯處理期間需要用到囚聚,編譯處理完后就沒有實際作用了靖榕,而主工程項目添加了這個庫會引入很多不必要的文件。為了處理這個問題我們需要引入插件 android-apt靡挥。它主要有兩個作用:
? 僅僅在編譯時期去依賴注解處理器所在的函數(shù)庫并進行工作序矩,但不會打包到 APK 中鸯绿。
? 為注解處理器生成的代碼設(shè)置好路徑跋破,以便 Android Studio 能夠找到它。
接下來介紹如何使用它瓶蝴。首先需要在整個工程(Progect)的 build.gradle 中添加如下語句:

buildscript {
 ...
 dependencies {
 ...
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
 } }

接下來在主工程項目(app)的 build.gradle 中以 apt 的方式引入注解處理器 processor毒返,如下所示:

...
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
 //compile project(':processor')
 apt project(':processor')
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舷手,隨后出現(xiàn)的幾起案子拧簸,更是在濱河造成了極大的恐慌,老刑警劉巖男窟,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盆赤,死亡現(xiàn)場離奇詭異,居然都是意外死亡歉眷,警方通過查閱死者的電腦和手機牺六,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汗捡,“玉大人淑际,你說我怎么就攤上這事∩茸。” “怎么了春缕?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長艘蹋。 經(jīng)常有香客問我锄贼,道長,這世上最難降的妖魔是什么女阀? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任咱娶,我火速辦了婚禮,結(jié)果婚禮上强品,老公的妹妹穿的比我還像新娘膘侮。我一直安慰自己,他們只是感情好的榛,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布琼了。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雕薪。 梳的紋絲不亂的頭發(fā)上昧诱,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音所袁,去河邊找鬼盏档。 笑死,一個胖子當(dāng)著我的面吹牛燥爷,可吹牛的內(nèi)容都是我干的蜈亩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼前翎,長吁一口氣:“原來是場噩夢啊……” “哼稚配!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起港华,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤道川,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后立宜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冒萄,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年橙数,在試婚紗的時候發(fā)現(xiàn)自己被綠了尊流。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡商模,死狀恐怖奠旺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情施流,我是刑警寧澤响疚,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站瞪醋,受9級特大地震影響忿晕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜银受,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一践盼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宾巍,春花似錦咕幻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锣吼。三九已至,卻和暖如春蓝厌,著一層夾襖步出監(jiān)牢的瞬間玄叠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工拓提, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留读恃,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓代态,卻偏偏與公主長得像寺惫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胆数,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

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

  • 什么是注解注解分類注解作用分類 元注解 Java內(nèi)置注解 自定義注解自定義注解實現(xiàn)及使用編譯時注解注解處理器注解處...
    Mr槑閱讀 1,071評論 0 3
  • 元注解: 元注解的作用就是負責(zé)注解其他注解肌蜻。Java5.0定義了4個標準的meta-annotation類型互墓,它們...
    badcyc閱讀 383評論 0 1
  • 我們平常寫Java代碼必尼,對其中的注解并不是很陌生,比如說寫繼承關(guān)系的時候經(jīng)常用到@Override來修飾方法篡撵。但是...
    于曉飛93閱讀 709評論 1 6
  • 一判莉、元注解 @interface是一種自定義的注解類型,他可以由四種元注解修飾育谬,分別是@Target券盅、@Reten...
    南通小窩頭閱讀 297評論 0 0
  • 學(xué)習(xí)書籍 JAVA編程思想(第四版)第20章 java SE5中引入的新特性之一,并在java.lang中內(nèi)置了幾...
    yangc91閱讀 222評論 0 1