Java注解簡介
Annotation 中文譯過來就是注解、标釋的意思,在 Java 中注解是一個很重要的知識點,但經常還是有點讓新手不容易了解。
我個人認為,比較糟糕的技術文檔主要特征之一就是:用專業名詞來介紹專業名詞。 比如:
Java 注解用于為 Java 代碼提供中繼資料。作為中繼資料,注解不直接影響你的代碼執行,但也有一些類型的注解實際上可以用于這一目的。Java 注解是從 Java5 開始添加到 Java 的。這是大多數網站上對于 Java 注解,解釋确實正确,但是說實在話,我第一次學習的時候,頭腦一片空白。這什麼跟什麼啊?聽了像沒有聽一樣。因為概念太過于抽象,是以初學者實在是比較吃力才能夠了解,然後随着自己開發過程中不斷地強化練習,才會慢慢對它形成正确的認識。
我在寫這篇文章的時候,我就在思考。如何讓自己或者讓讀者能夠比較直覺地認識注解這個概念?是要去官方文檔上翻譯說明嗎?我馬上否定了這個答案。
後來,我想到了一樣東西————墨水,墨水可以揮發、可以有不同的顔色,用來解釋注解正好。
不過,我繼續發散思維後,想到了一樣東西能夠更好地代替墨水,那就是印章。印章可以沾上不同的墨水或者印泥,可以定制印章的文字或者圖案,如果願意它也可以被戳到你任何想戳的物體表面。
但是,我再繼續發散思維後,又想到一樣東西能夠更好地代替印章,那就是标簽。标簽是一張便利紙,标簽上的内容可以自由定義。常見的如貨架上的商品價格标簽、圖書館中的書本編碼标簽、實驗室中化學材料的名稱類别标簽等等。
并且,往抽象地說,标簽并不一定是一張紙,它可以是對人和事物的屬性評價。也就是說,标簽具備對于抽象事物的解釋。

是以,基于如此,我完成了自我的知識認知更新,我決定用标簽來解釋注解。
注解如同标簽
之前某新聞用戶端的評論有蓋樓的習慣,于是 “喬布斯重新定義了手機、羅永浩重新定義了傻X” 就經常極為工整地出現在了評論樓層中,并且廣大網友在相當長的一段時間内對于這種行為樂此不疲。這其實就是等同于貼标簽的行為。在某些網友眼中,羅永浩就成了傻X的代名詞。
廣大網友給羅永浩貼了一個名為“傻x”的标簽,他們并不真正了解羅永浩,不知道他當教師、砸冰箱、辦部落格的壯舉,但是因為“傻x”這樣的标簽存在,這有助于他們直接快速地對羅永浩這個人做出評價,然後基于此,羅永浩就可以成為茶餘飯後的談資,這就是标簽的力量。
而在網絡的另一邊,老羅靠他的人格魅力自然收獲一大批忠實的擁泵,他們對于老羅貼的又是另一種标簽。
老羅還是老羅,但是由于人們對于它貼上的标簽不同,是以造成對于他的看法大相徑庭,不喜歡他的人整天在網絡上評論抨擊嘲諷,而崇拜欣賞他的人則會願意掙錢購買錘子手機的釋出會門票。
我無意于評價這兩種行為,我再引個例子。
《奇葩說》是近年網絡上非常火熱的辯論節目,其中辯手陳銘被另外一個辯手馬薇薇攻擊說是————“站在宇宙中心呼喚愛”,然後貼上了一個大大的标簽————“雞湯男”,自此以後,觀衆再看到陳銘的時候,首先映入腦海中便是“雞湯男”三個大字,其實本身而言陳銘非常優秀,為人師表、作風正派、談吐舉止得體,但是在網絡中,因為娛樂至上的環境所緻,人們更願意以娛樂的心态來認知一切,于是“雞湯男”就如陳銘自己所說成了一個撕不了的标簽。
我們可以抽象概括一下,标簽是對事物行為的某些角度的評價與解釋。
到這裡,終于可以引出本文的主角注解了。
初學者可以這樣了解注解:想像代碼具有生命,注解就是對于代碼中某些鮮活個體的貼上去的一張标簽。簡化來講,注解如同一張标簽。
在未開始學習任何注解具體文法而言,你可以把注解看成一張标簽。這有助于你快速地了解它的大緻作用。如果初學者在學習過程有大腦放空的時候,請不要慌張,對自己說:
注解,标簽。注解,标簽。
什麼是注解?
對于很多初次接觸的開發者來說應該都有這個疑問?Annontation是Java5開始引入的新特征,中文名稱叫注解。它提供了一種安全的類似注釋的機制,用來将任何的資訊或中繼資料(metadata)與程式元素(類、方法、成員變量等)進行關聯。為程式的元素(類、方法、成員變量)加上更直覺更明了的說明,這些說明資訊是與程式的業務邏輯無關,并且供指定的工具或架構使用。Annontation像一種修飾符一樣,應用于包、類型、構造方法、方法、成員變量、參數及本地變量的聲明語句中。
Java注解是附加在代碼中的一些元資訊,用于一些工具在編譯、運作時進行解析和使用,起到說明、配置的功能。注解不會也不能影響代碼的實際邏輯,僅僅起到輔助性的作用。包含在 java.lang.annotation 包中。
注解的用處:
1、生成文檔。這是最常見的,也是java 最早提供的注解。常用的有@param @return 等 2、跟蹤代碼依賴性,實作替代配置檔案功能。比如Dagger 2依賴注入,未來java開發,将大量注解配置,具有很大用處; 3、在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法并不是覆寫了超類方法,則編譯時就能檢查出。
注解的原理:
注解本質是一個繼承了Annotation的特殊接口,其具體實作類是Java運作時生成的動态代理類。而我們通過反射擷取注解時,傳回的是Java運作時生成的動态代理對象$Proxy1。通過代理對象調用自定義注解(接口)的方法,會最終調用AnnotationInvocationHandler的invoke方法。該方法會從memberValues這個Map中索引出對應的值。而memberValues的來源是Java常量池。
元注解:
java.lang.annotation提供了四種元注解,專門注解其他的注解(在自定義注解的時候,需要使用到元注解):@Documented –注解是否将包含在JavaDoc中 @Retention –什麼時候使用該注解 @Target –注解用于什麼地方 @Inherited – 是否允許子類繼承該注解
1.)@Retention– 定義該注解的生命周期
●
2.)Target – 表示該注解用于什麼地方。預設值為任何元素,表示該注解用于什麼地方。可用的ElementType參數包括
●
3.)@Documented–一個簡單的Annotations标記注解,表示是否将注解資訊添加在java文檔中。
4.)@Inherited – 定義該注釋和子類的關系 @Inherited 元注解是一個标記注解,@Inherited闡述了某個被标注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation将被用于該class的子類。
JDK裡的注解
JDK 内置注解 先來看幾個 Java 内置的注解,讓大家熱熱身。
@Override 示範
class
@Deprecated 示範 class Parent {
/**
@SuppressWarnings 示範 class Parent {
// 因為定義的 name 沒有使用,那麼編譯器就會有警告,這時候使用此注解可以屏蔽掉警告
@FunctionalInterface 示範 @FunctionalInterface interface Func { void run(); }
注解處理器實戰
注解處理器 注解處理器才是使用注解整個流程中最重要的一步了。所有在代碼中出現的注解,它到底起了什麼作用,都是在注解處理器中定義好的。概念:注解本身并不會對程式的編譯方式産生影響,而是注解處理器起的作用;注解處理器能夠通過在運作時使用反射擷取在程式代碼中的使用的注解資訊,進而實作一些額外功能。前提是我們自定義的注解使用的是 RetentionPolicy.RUNTIME 修飾的。這也是我們在開發中使用頻率很高的一種方式。
我們先來了解下如何通過在運作時使用反射擷取在程式中的使用的注解資訊。如下類注解和方法注解。
類注解 Class aClass = ApiController.class; Annotation[] annotations = aClass.getAnnotations();
for
此部分内容可參考: 通過反射擷取注解資訊
注解處理器實戰 接下來我通過在公司中的一個實戰改編來示範一下注解處理器的真實使用場景。需求: 網站背景接口隻能是年齡大于 18 歲的才能通路,否則不能通路 前置準備: 定義注解(這裡使用上文的完整注解),使用注解(這裡使用上文中使用注解的例子) 接下來要做的事情: 寫一個切面,攔截浏覽器通路帶注解的接口,取出注解資訊,判斷年齡來确定是否可以繼續通路。
在 dispatcher-servlet.xml 檔案中定義 aop 切面
<aop:config>
切面類處理邏輯即注解處理器代碼如
@Component
注解的擷取方式
類注解
你可以在運作期通路類,方法或者變量的注解資訊,下是一個通路類注解的例子:
Class
你還可以像下面這樣指定通路一個類的注解:
Class
方法注解
下面是一個方法注解的例子:
public
你可以像這樣通路方法注解:
Method
你可以像這樣通路指定的方法注解:
Method
參數注解
方法參數也可以添加注解,就像下面這樣:
public
你可以通過 Method對象來通路方法參數注解:
Method
需要注意的是 Method.getParameterAnnotations()方法傳回一個注解類型的二維數組,每一個方法的參數包含一個注解數組。
變量注解
下面是一個變量注解的例子:
public
你可以像這樣來通路變量的注解:
Field
你可以像這樣通路指定的變量注解:
Field
Java注解相關面試題
什麼是注解?他們的典型用例是什麼?
注解是綁定到程式源代碼元素的中繼資料,對運作代碼的操作沒有影響。
他們的典型用例是:
- 編譯器的資訊 - 使用注解,編譯器可以檢測錯誤或抑制警告
- 編譯時和部署時處理 - 軟體工具可以處理注解并生成代碼,配置檔案等。
- 運作時處理 - 可以在運作時檢查注解以自定義程式的行為
描述标準庫中一些有用的注解。
java.lang和java.lang.annotation包中有幾個注解,更常見的包括但不限于此:
- @Override -标記方法是否覆寫超類中聲明的元素。如果它無法正确覆寫該方法,編譯器将發出錯誤
- @Deprecated - 表示該元素已棄用且不應使用。如果程式使用标有此批注的方法,類或字段,編譯器将發出警告
- @SuppressWarnings - 告訴編譯器禁止特定警告。在與泛型出現之前編寫的遺留代碼接口時最常用的
- @FunctionalInterface - 在Java 8中引入,表明類型聲明是一個功能接口,可以使用Lambda Expression提供其實作
可以從注解方法聲明傳回哪些對象類型?
傳回類型必須是基本類型,String,Class,Enum或數組類型之一。否則,編譯器将抛出錯誤。
這是一個成功遵循此原則的示例代碼:
enum
下一個示例将無法編譯,因為Object不是有效的傳回類型:
public
哪些程式元素可以注解?
注解可以應用于整個源代碼的多個位置。它們可以應用于類,構造函數和字段的聲明:
@SimpleAnnotation
方法及其參數:
@SimpleAnnotation
局部變量,包括循環和資源變量:
@SimpleAnnotation
其他注解類型:
@SimpleAnnotation
甚至包,通過package-info.java檔案:
@PackageAnnotation
從Java 8開始,它們也可以應用于類型的使用。為此,注解必須指定值為ElementType.USE的@Target注解:
@Target
現在,注解可以應用于類執行個體建立:
new
類型轉換:
aString
接口中:
public
抛出異常上:
void
有沒有辦法限制可以應用注解的元素?
有,@ Target注解可用于此目的。如果我們嘗試在不适用的上下文中使用注解,編譯器将發出錯誤。
以下是僅将@SimpleAnnotation批注的用法限制為字段聲明的示例:
@Target
如果我們想讓它适用于更多的上下文,我們可以傳遞多個常量:
@Target
我們甚至可以制作一個注解,是以它不能用于注解任何東西。當聲明的類型僅用作複雜注解中的成員類型時,這可能會派上用場:
@Target
什麼是元注解?
元注解适用于其他注解的注解。
所有未使用@Target标記或使用它标記但包含ANNOTATION_TYPE常量的注解也是元注解:
@Target
下面的代碼會編譯嗎?
@Target
不能。如果在@Target注解中多次出現相同的枚舉常量,那麼這是一個編譯時錯誤。
删除重複常量将使代碼成功編譯:
@Target