Java 注解知识总结
引言
本博客總結(jié)自《Java 編程思想》第 20 章。
一、什么是注解
注解是 Java 5 引入的一種通過反射機(jī)制實(shí)現(xiàn)的語法特性,開發(fā)者可以通過在類、域、方法等元素前面標(biāo)記一個(gè)“標(biāo)簽”達(dá)到對(duì)程序的源碼、類信息或運(yùn)行時(shí)進(jìn)行某種說明或處理的效果,盡可能地簡(jiǎn)化代碼,從而使程序開發(fā)更高效。但需要注意的是,編譯器要確保在其構(gòu)造路徑上,必須有對(duì)應(yīng)注解的定義。
Java 中在 1.5 之初內(nèi)置了三個(gè)標(biāo)準(zhǔn)注解,@Deprecated、@Override 、@SupressWarning 。我們經(jīng)常會(huì)在程序的各個(gè)角落看到它們。
以@SupressWarning為例,
package java.lang;import java.lang.annotation.*; import static java.lang.annotation.ElementType.*;@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings {String[] value(); }它一般用于去除不恰當(dāng)?shù)木幾g警告,俗稱“報(bào)黃”。隨著Java 慢慢的發(fā)展,也逐漸引入了更多的注解,比如在 Java 8 伴隨著 Lambda表達(dá)式的加入,而一同入住 Java 大家庭的 @FunctionalInterface 注解:
package java.lang;import java.lang.annotation.*;@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface {}?二、如何聲明注解
注解的定義非常類似接口,不同的是,像上面的例子中,我們要在 interface 關(guān)鍵字前面加 “@”,以此來聲明這是一個(gè)注解。
除此之外,Java 提供了四個(gè)元注解:
@Target @Retention @Documented @Inherited其中,@Target 、@Retention 在定義注解時(shí),一般情況下都是必選項(xiàng)。
元注解專職負(fù)責(zé)注解其他注解。
@Target :表示該注解可以用于什么地方。需要給它傳入一個(gè)ElementType 枚舉對(duì)象,常用選項(xiàng)有:
?? ?CONSTRUCTOR : 構(gòu)造器聲明
?? ?FIELD : 域聲明(包括enum實(shí)例)
?? ?LOCAL_VARIABLE : 局部變量聲明
?? ?METHOD : 方法聲明
?? ?PACKAGE : 包聲明
?? ?PARAMETER : 參數(shù)聲明
?? ?TYPE : 類、接口(包括注解聲明)、enum 聲明
@Retention :表示需要在什么級(jí)別保存該注解。需要傳入一個(gè)?RetentionPolicy ,可選值:
?? ?SOURCE : 注解將被編譯器丟棄。
?? ?CLASS : 注解在class文件中使用,但會(huì)被 JVM 丟棄。
?? ?RUNTIME : vm將會(huì)在運(yùn)行期間也保留該注解,因此可以通過反射機(jī)制讀取注解的信息。
@Documented : 將此注解包含在javadoc 中。
@Inherited : 允許子類繼承父類的注解。?
注解定義示例:
package com.mht.demo.注解;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest {public int id();public String description() default "no description"; }可以看到,注解的確非常像接口,同時(shí),和其他任何 Java 接口一樣,注解也將會(huì)被編譯成為一個(gè) class 文件。
@MyTest 用到了兩個(gè)元注解:@Target 、@Retention ,如上所示,@MyTest 只能用于方法上,如果希望自己的注解既可以用于方法上,也可以用于類上,那么可以這樣寫:
@Target({ ElementType.METHOD, ElementType.TYPE })@Retention用于定義你的注解應(yīng)用在什么級(jí)別,源代碼(SOURCE)、類文件(CLASS)、運(yùn)行時(shí)(RUNTIME)。
注解中往往包含一些元素,比如上例中的 id 、description 。
在分析和處理注解時(shí),程序或工具可以利用這些值,它們看起來像接口中的抽象方法,唯一不同的是我們可以為這些元素指定默認(rèn)值。對(duì)于沒有任何元素的注解如 @FunctionalInterface 被稱為標(biāo)記注解。
編譯器會(huì)對(duì)注解中的元素進(jìn)行類型檢查,因此,將這些元素與數(shù)據(jù)庫相關(guān)聯(lián)是安全的。注解元素可用的類型有一定的限制,它不允許任何包裝類型。被允許的注解元素類型有:
1、8大基本類型
2、String
3、Class
4、enum
5、Annotion : 嵌套注解,非常有用的技巧
6、以上類型的數(shù)組
如果使用了除上述幾種以外的其他類型,那么編譯器就會(huì)報(bào)錯(cuò)。 注意,注解的元素值永遠(yuǎn)不能為null,要么在聲明元素之初設(shè)置默認(rèn)值,要么就在使用注解時(shí)添加該元素值,而且必須是不為 null 的值(如果希望注解中的某個(gè)元素是必填項(xiàng),那么在聲明時(shí)就可以不為其指定默認(rèn)值)。因此,注解處理器無法通過null 來判斷元素是否缺失。為了繞開這個(gè)限制,一般會(huì)通過 自己定義特殊值來判斷元素是否存在,比如 -1 或 ""。
default 關(guān)鍵字來定義元素的默認(rèn)值,在使用該注解時(shí),如果沒有給出元素的值 那么注解處理器就會(huì)使用此元素的默認(rèn)值。
三、注解的使用與自定義注解處理器
以 @MyTest 為例,我們來看看注解如何使用,以及如何處理這個(gè)注解。
public class SomeService {@MyTest(id = 1, description = "Hello Annotation! Hello 2020 !")public void testMyTestFeature() {System.out.println("這是testMyTestFeature()方法!");} }我們定義了一個(gè)類,聲明了一個(gè)方法,并為其標(biāo)記我們的 @MyTest 注解。
接下來我們來實(shí)現(xiàn)一個(gè)注解處理器?MyTestProcessor :
/*** '@MyTest'注解處理器* @author mht**/ public class MyTestProcessor {public static void processMyTest(Class<?> clz) {Method[] declaredMethods = clz.getDeclaredMethods();// getAnnotation() 方法會(huì)返回指定類型的注解,如果沒有,則返回nullMyTest myTest = declaredMethods[0].getAnnotation(MyTest.class);// 這里一般都會(huì)判斷獲取到的注解是否為空if (myTest != null) {System.out.println("找到標(biāo)記注解:id:" + myTest.id() + ", 描述:" + myTest.description());}}public static void main(String[] args) {processMyTest(SomeService.class);} }執(zhí)行 main 方法,測(cè)試輸出結(jié)果:
找到標(biāo)記注解:id:1, 描述:Hello Annotation! Hello 2020 !注解處理器雖然名字聽起來很專業(yè),但實(shí)際上,我們并不需要為我們處理注解的類或方法繼承或?qū)崿F(xiàn)什么。正如第一節(jié)開始所說的,注解是一種通過反射機(jī)制來實(shí)現(xiàn)的特性,我們可以通過 Class 對(duì)象來獲取我們想要的注解。
像上面的代碼有點(diǎn)過于簡(jiǎn)單了,一般情況下,我們可能會(huì)為一個(gè)目標(biāo)添加多個(gè)注解,因此一般的處理注解的思路就是:
1、獲取類信息(Class 對(duì)象可以直接獲取類上的注解對(duì)象或注解對(duì)象數(shù)組)
2、通過?getDeclaredMethods() 等方法,獲取目標(biāo)信息(有時(shí)也有可能是 類或域)
3、通過?getAnnotation(Class<T> annotationClass)、getAnnotations() 等方法,獲取一個(gè)或多個(gè)待處理的注解對(duì)象。
4、通過注解對(duì)象,獲取內(nèi)部元素,根據(jù)其值進(jìn)行邏輯處理。
這是一個(gè)一般的注解處理思路,許多框架中的注解處理往往比較復(fù)雜,且經(jīng)常需要配合遍歷來處理多個(gè)類信息,多個(gè)注解的情況。
值得注意的是,注解往往都是被動(dòng)的處理,它不能主動(dòng)發(fā)出某種信號(hào)傳遞給注解處理器,也就是說,我們必須主動(dòng)找到這些注解或?qū)y帶他們的類傳入注解處理器。
某些框架在批量處理注解的時(shí)候,就必須為注解處理器指定一個(gè)盡可能小的路徑范圍,以此來掃描該路徑下的類信息。
Mybatis 中的 @MapperScan 就是一個(gè)很好的例證,如果不為其指定一個(gè)掃描路徑,Mybatis 框架就可能必須從 classpath 的根路徑找起,這會(huì)非常影響框架處理效率。
綜上就是關(guān)于 注解的總結(jié)和思考,歡迎文末留言。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Java 注解知识总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot整合Redis——
- 下一篇: Effective Java(一)———