JAVA 注解的基本原理

XML 與 Annotation 對比

XML 長期以來职员,都是JAVA的常用的配置方式图张,效率高垫挨,但是隨著項目越來越龐大韩肝,『XML』的內(nèi)容也越來越復雜,維護成本變高九榔,耦合度也高
Annotation 為了規(guī)避XML的維護成本哀峻,提供便捷性,便于開發(fā)維護帚屉,耦合度也低谜诫,但犧牲了一定效率

追求低耦合就要拋棄高效率,追求效率必然會遇到耦合

注解本質

java.lang.annotation.Annotation 接口中有這么一句話攻旦,用來描述『注解』

The common interface extended by all annotation types
所有的注解類型都繼承自這個普通的接口(Annotation)

這句話有點抽象喻旷,但卻說出了注解的本質尖殃。我們看一個 JDK 內(nèi)置注解的定義:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

這是注解 @Override 的定義耘成,其實它本質上就是:

public interface Override extends Annotation{
}

沒錯瓮具,注解的本質就是一個繼承了 Annotation 接口的接口给僵。有關這一點,你可以去反編譯任意一個注解類锋谐,你會得到結果的

一個注解準確意義上來說遍尺,只不過是一種特殊的注釋而已,如果沒有解析它的代碼涮拗,它可能連注釋都不如

而解析一個類或者方法的注解往往有兩種形式乾戏,一種是編譯期直接的掃描,一種是運行期反射三热。反射的事情我們待會說鼓择,而編譯器的掃描指的是編譯器在對 java 代碼編譯字節(jié)碼的過程中會檢測到某個類或者方法被一些注解修飾,這時它就會對于這些注解進行某些處理就漾。

典型的就是注解 @Override呐能,一旦編譯器檢測到某個方法被修飾了 @Override 注解,編譯器就會檢查當前方法的方法簽名是否真正重寫了父類的某個方法抑堡,也就是比較父類中是否具有一個同樣的方法簽名摆出。

這一種情況只適用于那些編譯器已經(jīng)熟知的注解類,比如 JDK 內(nèi)置的幾個注解首妖,而你自定義的注解偎漫,編譯器是不知道你這個注解的作用的,當然也不知道該如何處理悯搔,往往只是會根據(jù)該注解的作用范圍來選擇是否編譯進字節(jié)碼文件骑丸,僅此而已舌仍。

元注解

元注解 是用于修飾注解的注解妒貌,通常用在注解的定義上,例如

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

這是我們 @Override 注解的定義铸豁,你可以看到其中的 @Target灌曙,@Retention 兩個注解就是我們所謂的 元注解元注解 一般用于指定某個注解生命周期以及作用目標等信息

JAVA 中有以下幾個 元注解

@Target:注解的作用目標
@Retention:注解的生命周期
@Documented:注解是否應當被包含在 JavaDoc 文檔中
@Inherited:是否允許子類繼承該注解

@Target

@Target 用于指明被修飾的注解最終可以作用的目標是誰节芥,也就是指明在刺,你的注解到底是用來修飾方法的?修飾類的头镊?還是用來修飾字段屬性的

@Target 的定義如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

我們可以通過以下的方式來為這個 value 傳值

@Target(value = {ElementType.FIELD})

被這個 @Target 注解修飾的注解將只能作用在成員字段上蚣驼,不能用于修飾方法或者類。其中相艇,ElementType 是一個枚舉類型颖杏,有以下一些值

ElementType.TYPE:允許被修飾的注解作用在類、接口和枚舉上
ElementType.FIELD:允許作用在屬性字段上
ElementType.METHOD:允許作用在方法上
ElementType.PARAMETER:允許作用在方法參數(shù)上
ElementType.CONSTRUCTOR:允許作用在構造器上
ElementType.LOCAL_VARIABLE:允許作用在本地局部變量上
ElementType.ANNOTATION_TYPE:允許作用在注解上
ElementType.PACKAGE:允許作用在包上

@Retention

@Retention 用于指明當前注解的生命周期坛芽,它的基本定義如下

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

同樣的留储,它也有一個 value 屬性

@Retention(value = RetentionPolicy.RUNTIME

這里的 RetentionPolicy 依然是一個枚舉類型翼抠,它有以下幾個枚舉值可取

RetentionPolicy.SOURCE:當前注解編譯期可見,不會寫入 class 文件
RetentionPolicy.CLASS:類加載階段丟棄获讳,會寫入 class 文件
RetentionPolicy.RUNTIME:永久保存阴颖,可以反射獲取

RetentionPolicy.SOURCE

只能在編譯期可見,編譯后會被丟棄

RetentionPolicy.CLASS

被編譯器編譯進 class 文件中丐膝,無論是類或是方法量愧,乃至字段,他們都是有屬性表的帅矗,而 JAVA 虛擬機也定義了幾種注解屬性表用于存儲注解信息侠畔,但是這種可見性不能帶到方法區(qū),類加載時會予以丟棄

RetentionPolicy.RUNTIME

永久存在的可見性损晤。

剩下兩種類型的注解我們?nèi)粘S玫牟欢嗳砉祝脖容^簡單,這里不再詳細的進行介紹了尤勋,你只需要知道他們各自的作用即可喘落。

@Documented

@Documented 注解修飾的注解,當我們執(zhí)行 JavaDoc 文檔打包時會被保存進 doc 文檔最冰,反之將在打包時丟棄瘦棋。

@Inherited

@Inherited 注解修飾的注解是具有可繼承性的,也就說我們的注解修飾了一個類暖哨,而該類的子類將自動繼承父類的該注解赌朋。

JAVA 的內(nèi)置三大注解

除了上述四種元注解外,JDK 還為我們預定義了另外三種注解篇裁,它們是

@Override
@Deprecated
@SuppressWarnings

@Override

@Override 注解想必是大家很熟悉的了沛慢,它的定義如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它沒有任何的屬性,所以并不能存儲任何其他信息达布。它只能作用于方法之上团甲,編譯結束后將被丟棄。

所以你看黍聂,它就是一種典型的『標記式注解』躺苦,僅被編譯器可知,編譯器在對 java 文件進行編譯成字節(jié)碼的過程中产还,一旦檢測到某個方法上被修飾了該注解匹厘,就會去匹對父類中是否具有一個同樣方法簽名的函數(shù),如果不是脐区,自然不能通過編譯愈诚。

@Deprecated

@Deprecated 的基本定義如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

依然是一種『標記式注解』,永久存在,可以修飾所有的類型扰路,作用是尤溜,標記當前的類或者方法或者字段等已經(jīng)不再被推薦使用了,可能下一次的 JDK 版本就會刪除汗唱。

當然宫莱,編譯器并不會強制要求你做什么,只是告訴你 JDK 已經(jīng)不再推薦使用當前的方法或者類了哩罪,建議你使用某個替代者授霸。

@SuppressWarnings

@SuppressWarnings主要用來壓制 java 的警告,它的基本定義如下:

@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

它有一個 value 屬性需要你主動的傳值际插,這個 value 代表一個什么意思呢碘耳,這個 value 代表的就是需要被壓制的警告類型。例如

public static void main(String[] args) {
    Date date = new Date(2018, 7, 11);
}

這么一段代碼框弛,程序啟動時編譯器會報一個警告

Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已過時

而如果我們不希望程序啟動時辛辨,編譯器檢查代碼中過時的方法,就可以使用 @SuppressWarnings 注解并給它的 value 屬性傳入一個參數(shù)值來壓制編譯器的檢查瑟枫。

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
    Date date = new Date(2018, 7, 11);
}

這樣你就會發(fā)現(xiàn)斗搞,編譯器不再檢查 main 方法下是否有過時的方法調用,也就壓制了編譯器對于這種警告的檢查慷妙。

當然僻焚,JAVA 中還有很多的警告類型,他們都會對應一個字符串膝擂,通過設置 value 屬性的值即可壓制對于這一類警告類型的檢查

自定義注解

自定義注解比較簡單虑啤,不多做描述

public @interface CustomName{
}

注解與反射

上述內(nèi)容我們介紹了注解使用上的細節(jié),也簡單提到架馋,「注解的本質就是一個繼承了 Annotation 接口的接口」狞山,現(xiàn)在讓我們從類中反射得到一個注解實例

自定義一個注解類型

@Target(value = { ElementType.FIELD, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Hello {
    String value();
}

這里我們指定了 Hello 這個注解只能修飾字段和方法,并且該注解永久存活绩蜻,以便我們反射獲取

之前我們說過铣墨,虛擬機規(guī)范定義了一系列和注解相關的屬性表室埋,也就是說办绝,無論是字段、方法或是類本身姚淆,如果被注解修飾了孕蝉,就可以被寫進字節(jié)碼文件。屬性表有以下幾種

RuntimeVisibleAnnotations:運行時可見的注解
RuntimeInVisibleAnnotations:運行時不可見的注解
RuntimeVisibleParameterAnnotations:運行時可見的方法參數(shù)注解
RuntimeInVisibleParameterAnnotations:運行時不可見的方法參數(shù)注解
AnnotationDefault:注解類元素的默認值

所以腌逢,對于一個類或者接口來說降淮,Class 類中提供了以下一些方法用于反射注解。

getAnnotation:返回指定的注解
isAnnotationPresent:判定當前元素是否被指定注解修飾
getAnnotations:返回所有的注解
getDeclaredAnnotation:返回本元素的指定注解
getDeclaredAnnotations:返回本元素的所有注解,不包含父類繼承而來的

方法佳鳖、字段中相關反射注解的方法基本是類似的霍殴,我們看如何在類中通過反射得到注解實例

public class Test {
    @Hello("hehe")
    public static void main(String[] args) throws NoSuchMethodException {
        Class klass = Test.class;
        Method method = klass.getMethod("main", String[].class);
        Hello hello = method.getAnnotation(Hello.class);

        System.out.println(hello.value());
    }
}

注解只是一個實現(xiàn)了Annotation接口的接口,故而我們得到的注解實例系吩,一定是一個代理類

總結反射注解工作原理

  1. 我們通過鍵值對的形式可以為注解屬性賦值来庭,像這樣:@Hello(value = "hello")
  2. 用注解修飾某個元素,編譯器將在編譯期掃描每個類或者方法上的注解穿挨,會做一個基本的檢查月弛,你的這個注解是否允許作用在當前位置,最后會將注解信息寫入元素的屬性表
  3. 虛擬機把生命周期在 RUNTIME 的注解取出并通過動態(tài)代理機制生成一個實現(xiàn)注解接口的代理類
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末科盛,一起剝皮案震驚了整個濱河市帽衙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贞绵,老刑警劉巖厉萝,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異榨崩,居然都是意外死亡冀泻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門蜡饵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弹渔,“玉大人,你說我怎么就攤上這事溯祸≈ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵焦辅,是天一觀的道長博杖。 經(jīng)常有香客問我,道長筷登,這世上最難降的妖魔是什么剃根? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮前方,結果婚禮上狈醉,老公的妹妹穿的比我還像新娘。我一直安慰自己惠险,他們只是感情好苗傅,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著班巩,像睡著了一般渣慕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天逊桦,我揣著相機與錄音眨猎,去河邊找鬼。 笑死强经,一個胖子當著我的面吹牛宵呛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夕凝,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宝穗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了码秉?” 一聲冷哼從身側響起逮矛,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎转砖,沒想到半個月后须鼎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡府蔗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年晋控,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姓赤。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡赡译,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出不铆,到底是詐尸還是另有隱情蝌焚,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布誓斥,位于F島的核電站只洒,受9級特大地震影響,放射性物質發(fā)生泄漏劳坑。R本人自食惡果不足惜毕谴,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望距芬。 院中可真熱鬧涝开,春花似錦、人聲如沸蔑穴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽存和。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捐腿,已是汗流浹背纵朋。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茄袖,地道東北人操软。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像宪祥,于是被迫代替她去往敵國和親聂薪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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

  • 以前蝗羊,『XML』是各大框架的青睞者藏澳,它以松耦合的方式完成了框架中幾乎所有的配置,但是隨著項目越來越龐大耀找,『XML』...
    Java大生閱讀 2,375評論 3 96
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,161評論 15 116
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,168評論 0 2
  • 從JDK5開始翔悠,Java增加了Annotation(注解),Annotation是代碼里的特殊標記野芒,這些標記可以在...
    CarlosLynn閱讀 561評論 0 2
  • 我們認識很久很久蓄愁,我一直自認為余生我們會一直走下去。我們有同樣的愛好狞悲,同樣的理想撮抓,同樣的善良,但這個世界總有些不同...
    笑笑_feng閱讀 409評論 0 2