參考網(wǎng)址:
《秒懂锦聊,Java 注解 (Annotation)你可以這樣學》
《Java注解基本原理》
《注解Annotation實現(xiàn)原理與自定義注解例子》
《框架開發(fā)之Java注解的妙用》
一. 注解基本介紹
1.1 什么是注解?
什么是注解嗽冒?嚴謹?shù)膩碚f礁扮,注解提供了一種安全的類似注釋的機制知举,用來將任何的信息或元數(shù)據(jù)(metadata)與程序元素(類、方法太伊、成員變量等)進行關(guān)聯(lián)雇锡。為程序的元素(類、方法僚焦、成員變量)加上更直觀的說明锰提,這些說明信息是與程序的業(yè)務邏輯無關(guān),并且供指定的工具或框架使用芳悲。Annontation像一種修飾符一樣欲账,應用于包、類型芭概、構(gòu)造方法、方法惩嘉、成員變量罢洲、參數(shù)及本地變量的聲明語句中。
Java 注解是附加在代碼中的一些元信息文黎,用于一些工具在編譯惹苗、運行時進行解析和使用,起到說明耸峭、配置的功能桩蓉。注解不會也不能影響代碼的實際邏輯,僅僅起到輔助性的作用劳闹。注解包含在 java.lang.annotation 包中院究。
具體定義如下:
注解 (Annotation),也叫元數(shù)據(jù)本涕。一種代碼級別的說明业汰。它是 JDK1.5 及以后版本引入的一個特性,與類菩颖、接口样漆、枚舉是在同一個層次。它可以聲明在包晦闰、類放祟、字段鳍怨、方法、局部變量跪妥、方法參數(shù)等的前面鞋喇,用來對這些元素進行說明,注釋骗奖。
——摘自百度百科
上面的說明雖然嚴謹确徙,但比較難懂。筆者認為《秒懂执桌,Java 注解 (Annotation)你可以這樣學》一文中鄙皇,作者 frank909 大佬的解釋十分親民:可以完全將注解當做生活中我們對人對物貼的標簽。
拿筆者最喜歡的一部動畫電影來打個比方吧:《Zootopia》仰挣“橐荩《Zootopia》整個電影將動物們擬人化,性格各異膘壶。不管是兔子错蝴,狐貍,羚羊颓芭,豹子等等顷锰,每個動物都有一張固有標簽:兔子乖巧,狐貍狡黠亡问,羚羊溫順官紫,豹子兇猛。
但它們又有著自己真實的性格:想當警察的兔子州藕,狡黠卻不失善良的狐貍束世,披著狼皮的腹黑羚羊,吃著甜甜圈有少女心的豹子床玻。
《Zootopia》這個電影的內(nèi)核是在講毁涉,我們要試圖沖破外界對自己所貼的標簽的限制。但在這里筆者要稍微的當一下杠精锈死,吹一下標簽的作用:貼標簽是較為精準的了解一個事物的最高效率方法贫堰。瘋狂動物城中的動物們,外界對他們的第一印象馅精,往往都是直接引用了該物種性格的固有標簽严嗜。同樣的在 Java 中,注解的作用就是告訴開發(fā)人員洲敢,被注解的內(nèi)容是用來做什么的漫玄,換句話說,注解就是 Java 代碼的標簽。
在 Java 中睦优,給代碼貼合適的標簽是很重要的渗常,它很大程度的提高了效率。雖然寫代碼的時候開發(fā)人員也可以致敬《Zootopia》主旨汗盘,嘗試突破標簽的限制(比如給實現(xiàn)了 @Controller 功能的代碼加了 @Service 注解)皱碘,但筆者不保證寫下這樣代碼開發(fā)人員的后續(xù)人身安全,太睿智的人肯定是要被針對的……
1.2 注解的作用
- 能夠讀懂別人寫的代碼(尤其是框架相關(guān)的代碼)隐孽;
- 實現(xiàn)替代配置文件的功能癌椿。比如可能原本需要很多配置文件以及很多邏輯才能實現(xiàn)的內(nèi)容,如果使用合理的注解菱阵,就可以使用一個或多個注解來實現(xiàn)相同的功能踢俄。這樣就使得代碼更加清晰和整潔;
-
編譯時進行格式檢查晴及。
- 如 @Override 注解放在方法前都办,如果該方法不是覆蓋了某個超類方法,編譯的時候編譯器就能檢查出來虑稼。
-
裝逼琳钉。
- 做技術(shù)的怎么可以沒有一點用技術(shù)吹牛逼的心理呢?如果會在合適的地方恰好的使用注解或者自定義注解的話蛛倦,老板肯定會雙手送你 666 的歌懒。當然筆者現(xiàn)在只是初學而已,距離用技術(shù)吹牛逼的道路還遠溯壶。
1.3 注解的原理
注解本質(zhì)是一個繼承了 Annotation 的特殊接口歼培,其具體實現(xiàn)類是 Java 運行時生成的動態(tài)代理類。而我們通過反射獲取注解時茸塞,返回的是 Java 運行時生成的動態(tài)代理對象 $Proxy1。通過代理對象調(diào)用自定義注解(接口)的方法查剖,會最終調(diào)用 AnnotationInvocationHandler 的 invoke 方法钾虐。該方法會從 memberValues 這個 Map 中索引出對應的值。而 memberValues 的來源是 Java 常量池笋庄。
——摘自《注解Annotation實現(xiàn)原理與自定義注解例子》
這里涉及的內(nèi)容比較深入效扫,筆者目前不能理解。先貼上來直砂,以后慢慢來吧菌仁。
二. 元注解
元注解是可以注解到注解上的注解,或者說元注解是一種基本注解静暂,但是它能夠應用到其它的注解上面济丘。或者可以理解為:元注解也是一張標簽,但是它是一張?zhí)厥獾臉撕災∶裕?strong>作用和目的就是給其他普通的標簽進行解釋說明的疟赊。
基本的元標簽有 @Retention, @Documented, @Target, @Inherited 四種(后來到了 Java 8 又加入了 @Repeatable)。
2.1 @Retention
@Retention 定義了該注解的生命周期峡碉。當 @Retention 應用到一個注解上的時候近哟,作用就是說明這個注解的存活時間。
-
RetentionPolicy.SOURCE: 注解只在源碼階段保留鲫寄,在編譯器完整編譯之后吉执,它將被丟棄忽視;
- 例:@Override, @SuppressWarnings
- RetentionPolicy.CLASS: 注解只被保留到編譯進行的時候地来,它并不會被加載到 JVM 中戳玫;
- RetentionPolicy.RUNTIME: 注解可以保留到程序運行的時候,它會被加載進入到 JVM 中靠抑,所以在程序運行時可以獲取到它們量九;筆者接觸到大部分的注解都是 RUNTIME 的生命周期。
以 SpringMVC 中的 @Service 的源碼為例:
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
String value() default "";
}
這里 @Service 擁有 @Retention(RetentionPolicy.RUNTIME) 注解颂碧,所以在程序運行時可以捕獲到它們荠列。
2.2 @Target
@Target 表示該注解用于什么地方,可以理解為:當一個注解被 @Target 注解時载城,這個注解就被限定了運用的場景肌似。可以使用的 ElementType 參數(shù):
- ElementType.CONSTRUCTOR: 對構(gòu)造方法進行注解诉瓦;
- ElementType.ANNOTATION_TYPE: 對注解進行注解川队;
- ElementType.FIELD: 對屬性、成員變量睬澡、成員對象(包括 enum 實例)進行注解固额;
- ElementType.LOCAL_VARIABLE: 對局部變量進行注解;
- ElementType.METHOD: 對方法進行注解煞聪;
- ElementType.PACKAGE: 對包進行注解斗躏;
- ElementType.PARAMETER: 對描述參數(shù)進行注解;
- ElementType.TYPE: 對類昔脯、接口啄糙、枚舉進行注解;
如上面的 @Service 所示云稚,@Service 的 @Target 注解值為 ElementType.TYPE隧饼,即 @Service 只能用于修飾類。
2.3 @Documented
@Documented 是一個簡單的標記注解静陈,表示是否將注解信息添加在 Java 文檔燕雁,即 Javadoc 中。
2.4 @Inherited
Inherited 是指繼承,@Inherited 定義了一個注釋與子類的關(guān)系贵白。如果一個超類帶有 @Inherited 注解率拒,那么對于該超類,它的子類如果沒有被任何注解應用的話禁荒,那么這個子類就繼承了超類的注解猬膨。
用《秒懂,Java 注解 (Annotation)你可以這樣學》一文中的例程與解釋來說明:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test 被 @Inherited 修飾呛伴,之后類 A 被 Test 注解勃痴,類 B 繼承 A,類 B 也擁有 Test 這個注解∪瓤担可以這樣理解:
老子非常有錢沛申,所以人們給他貼了一張標簽叫做富豪。
老子的兒子長大后姐军,只要沒有和老子斷絕父子關(guān)系铁材,雖然別人沒有給他貼標簽,但是他自然也是富豪奕锌。
老子的孫子長大了著觉,自然也是富豪。
這就是人們口中戲稱的富一代惊暴,富二代饼丘,富三代。雖然叫法不同辽话,好像好多個標簽肄鸽,但其實事情的本質(zhì)也就是他們有一張共同的標簽,也就是老子身上的那張富豪的標簽油啤。
2.5 @Repeatable
@Repeatable 是 Java 8 中加入的典徘,是指可重復的意思。通常使用 @Repeatable 的時候指注解的值可以同時取多個益咬。依舊用《秒懂烂斋,Java 注解 (Annotation)你可以這樣學》一文中的例程與解釋來說明:一個人既是程序員,又是產(chǎn)品經(jīng)理础废,同時也是畫家。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person {
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan {
...
}
上面的代碼通過 @Repeatable 定義了 Person罕模,而 @Repeatable 后面括號的類相當于一個容器注解评腺。容器注解就是用來存放其它注解的地方,它本身也是一個注解淑掌。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
Class<? extends Annotation> value();
}
上面是 @Repeatable 的源碼蒿讥。按照規(guī)定,如果使前面的 Persons 里面可以重復調(diào)用某個注解,則 Persons 必須有一個 value 的屬性芋绸,且屬性類型必須為被 @Repeatable 注解的 Person媒殉。
三. 注解的屬性
注解的屬性也叫做成員變量。注解只有成員變量摔敛,沒有方法廷蓉。注解的成員變量在注解的定義中以無形參的方法形式來聲明,其方法名定義了該成員變量的名字马昙,其返回值定義了該成員變量的類型桃犬。以下面的例程為例:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coder {
int id();
String name();
String language();
String company();
}
上面假設定義了一個名為 @Coder 的注解,該注解有 id, name, language, company 三個屬性行楞。使用的時候攒暇,我們應該對其賦值。賦值的方式類似于 key="value" 的方式進行子房,屬性之間用 "," 隔開:
@Coder(id = 10086, name = "GRQ", language = "JAVA", company = "cetc")
public class coderGRQ() {
}
此外形用,注解可以有默認值,需要用 default 關(guān)鍵字指定证杭。例如上例:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coder {
public int id() default -1;
public String name() default "GRQ";
public String language() default "C++";
public String company() default "China_Company";
}
如果:
@Coder
public class coderXX {}
由于在 @Coder 注解中設置了默認值田度,所以就不需要再 @Coder 后面的括號里進行賦值了。
此外躯砰,如果注解內(nèi)只有一個名為 value 的屬性時每币,應用該屬性時可以將值直接寫到括號內(nèi),不用寫 value = "..."琢歇。例如:
public @interface language {
String value();
}
那么下面兩種聲明是相同的:
// 第一種聲明
@language("JAVA")
int coderA;
// 第二種聲明
@language(value = "JAVA")
int coderA;
四. 常用注解
Java 中自帶且常用的幾種注解有 @Override, @Deprecated, @SuppresWarninngs, @SafeVarargs兰怠。
@Override 是一個標記類型注解,用于提示子類要復寫父類中被 @Override 修飾的方法李茫,它說明了被標注的方法重載了父類的方法揭保,起到了斷言的作用。如果我們使用了這種注解在一個沒有覆蓋父類方法的方法時魄宏,java編譯器將以一個編譯錯誤來警示秸侣。
@Deprecated 也是一個標記類型注解,用于標記過時的元素宠互。比如如果開發(fā)人員正在調(diào)用一個過時的方法味榛、類或成員變量時,可以用該注解進行標注予跌。
@SuppressWarnings 并不是一個標記類型注解搏色,它可以阻止警告的提示。它有一個類型為 String[] 的成員券册,其值為被禁止的警告名频轿。
@SafeVarargs 是一個參數(shù)安全類型注解垂涯。它的目的是提醒開發(fā)人員,不要用參數(shù)做一些不安全的操作航邢。它的存在會阻止編譯器產(chǎn)生 unchecked 的警告耕赘。例如對于可變長度參數(shù),如果和泛型一起使用膳殷,會產(chǎn)生比較多的編譯器警告操骡。如下面的方法:
public static <T> T useVarargs(T... args) {
return args.length > 0 ? args[0] : null;
}
如果參數(shù)傳遞的是不可具體化的類型(類似于 List<String> 的泛型類型),每調(diào)用一次該方法秽之,都會產(chǎn)生警告信息当娱。如果希望禁止這個警告信息,可以使用 @SuppressWarnings("unchecked") 注解進行聲明考榨。同時在 Java 7 版本之后的 @SafeVarargs 注解針對 "unchecked" 警告進行了屏蔽跨细,我們也可以用 @SafeVarargs 獲得 @SuppressWarnings("unchecked") 同樣的效果。
五. 自定義注解
此處參考《注解Annotation實現(xiàn)原理與自定義注解例子》的原理介紹和水果例程河质。
自定義注解類編寫的規(guī)則:
- 注解類型定義為 @interface冀惭,所有的注解會自動繼承 java.lang.Annotation 這一接口,而且不能再去繼承其他的類或接口掀鹅;
- 參數(shù)成員只能用 public 或 default 兩個關(guān)鍵字修飾散休;
- 參數(shù)成員只能用基本類型:byte, short, char, int, long, float, double, boolean,以及 String, Enum, Class, Annotations 等數(shù)據(jù)類型乐尊,以及這些類型的數(shù)組戚丸;
- 要獲取類方法和字段的注解信息,必須通過 Java 的反射技術(shù)扔嵌;
- 注解也可以不定義成員變量限府,但這樣的注解沒有什么卵用;
- 自定義注解需要使用元注解進行編寫痢缎;
以水果與水果供應商為例:
水果名稱注解 FruitName.java:
package com.grq.FruitAnnotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果名稱注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
水果顏色注解 FruitColor.java:
package com.grq.FruitAnnotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果顏色注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色枚舉
*/
public enum Color{ BLUE,RED,GREEN};
/**
* 顏色屬性
*/
Color fruitColor() default Color.GREEN;
}
水果供應者注解 FruitProvider.java:
package com.grq.FruitAnnotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果供應者注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應商編號
*/
public int id() default -1;
/**
* 供應商名稱
*/
public String name() default "";
/**
* 供應商地址
*/
public String address() default "";
}
注解處理器 FruitInfoUtil.java:
package com.grq.FruitAnnotation;
import java.lang.reflect.Field;
/**
* 注解處理器
*/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){
String strFruitName=" 水果名稱:";
String strFruitColor=" 水果顏色:";
String strFruitProvicer="供應商信息:";
Field[] fields = clazz.getDeclaredFields();
for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供應商編號:"+fruitProvider.id()+" 供應商名稱:"+fruitProvider.name()+" 供應商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
蘋果 Apple.java:
package com.grq.FruitAnnotation;
/**
* 注解使用
*/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor = FruitColor.Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陜西紅富士集團",address="陜西省西安市延安路89號紅富士大廈")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName(){
System.out.println("水果的名字是:蘋果");
}
}
測試輸出水果信息 FruitTestAnnotation:
package com.grq.FruitAnnotation;
public class TestFruitAnnotation {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
運行后的測試結(jié)果為:
水果名稱:Apple
水果顏色:RED
供應商編號:1 供應商名稱:陜西紅富士集團 供應商地址:陜西省西安市延安路89號紅富士大廈
后記
這段時間雖然在 SpringMVC 中用注解用的飛起胁勺,各種 @RequestMapping, @Service, @Controller 等注解信手拈來,但還是不了解它的運作原理到底是什么樣的独旷。尤其是在框架中署穗,大量運用到了注解與反射操作,所以以后也會認真了解一下如 Spring 框架中注解的運行原理嵌洼,想必這無論是對理解框架案疲,還是對理解注解本身,都會有很大的幫助麻养。