1贱傀、注解的作用
Annotation(注解)是JDK 5.0引入的特性惨撇,它的基本作用就是修飾編程元素。
注解相當(dāng)于一種標(biāo)記府寒,在程序中加了注解就等于為程序打上了某種標(biāo)記魁衙。編譯器报腔、開發(fā)工具或其他程序可以用反射來了解該類,有什么樣的標(biāo)記剖淀,就去干相應(yīng)的事纯蛾。
注解的語法比較簡單,除了@符號的使用之外纵隔,與Java固有語法一致翻诉。JDK 5.0內(nèi)置了三種標(biāo)準(zhǔn)注解:
-
@Override
:表示當(dāng)前的方法定義將覆蓋父類中的方法。 -
@Deprecated
:表示當(dāng)前元素不推薦使用巨朦,是被棄用的代碼米丘。 -
@SuppressWarnings
:忽略編譯器發(fā)出的警告信息。
注解有以下幾個(gè)作用:
- 生成文檔糊啡。這是最常見的拄查,也是java 最早提供的注解。常用的有
@see
棚蓄、@param
堕扶、@return
等 - 跟蹤代碼依賴性,替代配置文件的功能梭依。比如Spring從 2.5版本 開始支持基于注解的配置∩运悖現(xiàn)在的框架基本都使用了注解來減少配置文件的數(shù)量。
- 在編譯時(shí)進(jìn)行格式檢查役拴。如
@override
放在方法前糊探,如果這個(gè)方法并不是覆蓋了父類方法,則編譯時(shí)就能檢查出來河闰。
2科平、元注解
Java 5.0提供了四種元注解——負(fù)責(zé)注解其他注解。
2.1 @Target
@Target
說明了Annotation所修飾的對象范圍:Annotation可被用于 packages姜性、types(類瞪慧、接口、枚舉部念、Annotation類型)弃酌、類型成員(方法、構(gòu)造方法儡炼、成員變量妓湘、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量乌询、catch參數(shù))多柑。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標(biāo)。
作用:用于描述注解的使用范圍楣责,即該注解可以用在什么地方竣灌。
取值(ElementType枚舉類)有:
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述變量
- LOCAL_VARIABLE:用于描述局部變量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述參數(shù)
- TYPE:用于描述類、接口(包括注解類型) 或enum聲明
2.2 @Retention
@Retention
定義了該注解被保留的時(shí)間長短:某些Annotation僅出現(xiàn)在源代碼中秆麸,而被編譯器丟棄初嘹;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機(jī)忽略沮趣,而另一些在class被裝載時(shí)將被讀韧头场(請注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)房铭。使用@Retention
可以限制注解的“生命周期”驻龟。
作用:用于描述注解的生命周期,即注解在什么范圍內(nèi)有效缸匪。
取值(RetentionPoicy枚舉類)有:
- SOURCE:在源文件中有效
- CLASS:在class文件中有效
- RUNTIME:在運(yùn)行時(shí)有效
2.3 @Documented
@Documented
是一種標(biāo)記性注解翁狐,被其標(biāo)記的注解會被包含在javadoc文檔中。
2.4 @Inherited
@Inherited
也是一種標(biāo)記性注解凌蔬,被其標(biāo)記的注解會允許子類繼承露懒。如果一個(gè)使用了@Inherited
修飾的annotation類型被用于一個(gè)class,則該注解將被用于該class的子類砂心。
需要注意的是懈词,被@Inheriten
標(biāo)記的注解只會被class的子類所繼承,類并不從它所實(shí)現(xiàn)的接口繼承annotation辩诞,方法也不從它所重載的方法繼承annotation坎弯。
如果我們使用java.lang.reflect
去查詢一個(gè)@Inherited
類型的annotation時(shí),反射代碼將檢查class和其父類译暂,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn)抠忘,或者到達(dá)類繼承結(jié)構(gòu)的頂層。
3秧秉、自定義注解
自定義注解的格式為:
public @interface 注解名 {定義體}
例如:
@Target(value = { ElementType.FIELD })
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
int code() default -1;
String message() default "";
}
@interface
用來聲明一個(gè)注解褐桌,自動(dòng)繼承了java.lang.annotation.Annotation
接口,在自定義注解時(shí)象迎,不能繼承其他的注解或接口荧嵌。
定義體中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù),方法只能用public或默認(rèn)(default)兩種訪問權(quán)修飾砾淌,方法的名稱就是參數(shù)的名稱啦撮,返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class汪厨、String赃春、enum)〗俾遥可以通過default來聲明參數(shù)的默認(rèn)值织中。
注解參數(shù)支持?jǐn)?shù)據(jù)類型:
所有基本數(shù)據(jù)類型(byte,int,short.long,double,float,boolean,char)
- String類型
- Class類型
- Enum類型
- Annotation類型
- 以上所有類型的數(shù)組
注解元素必須有確定的值锥涕,要么在定義注解的默認(rèn)值中指定,要么在使用注解時(shí)指定狭吼,非基本類型的注解元素的值不可為null层坠。因此, 使用空字符串或0作為默認(rèn)值是一種常用的做法。這個(gè)約束使得處理器很難判斷一個(gè)元素是否缺失刁笙,因?yàn)槊總€(gè)注解的聲明中破花,所有元素都存在,并且都具有相應(yīng)的值疲吸,為了繞開這個(gè)約束座每,我們只能定義一些特殊的值,例如空字符串或者負(fù)數(shù)摘悴,一次表示某個(gè)元素不存在峭梳,在定義注解時(shí),這已經(jīng)成為一個(gè)習(xí)慣用法烦租。
如果成員名稱是value
延赌,在賦值過程中可以簡寫;如果成員類型為數(shù)組叉橱,但是只賦值一個(gè)元素挫以,則也可以簡寫。上面的例子可以簡寫為:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
int code() default -1;
String message() default "";
}
上例自定義了名為@NotNull
的注解窃祝,作用對象為field
掐松,其目的是對類變量進(jìn)行非空檢驗(yàn)》嘈。可是大磺,到底怎么怎么利用它來進(jìn)行檢驗(yàn)?zāi)兀?/p>
使用注解最主要的部分在于對注解的處理,那么就會涉及到注解處理器探膊。從原理上講杠愧,注解處理器就是通過反射機(jī)制獲取被檢查字段上的注解信息,然后根據(jù)注解元素的值進(jìn)行特定的處理逞壁。
比如流济,在某網(wǎng)站注冊用戶,用戶輸入的信息會被封裝成一個(gè)User
對象:
public class User {
@NotNull(message = "用戶昵稱不能為空")
private String userName;
@NotNull(message = "密碼不能為空")
private String password;
private Integer age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
要求用戶昵稱與密碼不能為空腌闯,因此userName
與password
字段被@NotNull
注解绳瘟。
后臺程序接收到User
對象后,可以通過反射機(jī)制探知哪些字段被@NotNull
注解了姿骏,如果全部滿足要求再進(jìn)行下一步業(yè)務(wù)邏輯糖声;反之,返回錯(cuò)誤頁面。
public class UserTest {
public static boolean testNotNull(User user) throws IllegalArgumentException, IllegalAccessException {
Class<?> clazz = user.getClass(); // 獲取User的Class對象
Field[] userFields = clazz.getDeclaredFields(); // 獲取User的所有Field對象
for (Field field : userFields) {
field.setAccessible(true); // User中的Field都是private的蘸泻,所以要先setAccessible
NotNull notNullAnnotation = field.getAnnotation(NotNull.class); // 獲取Field的NotNull注解對象
if (notNullAnnotation != null) { // 如果該Field被NotNull注解了琉苇,那么字段不能為空
if (field.get(user) == null) {
System.out.println(notNullAnnotation.message());
return false;
}
}
}
return true;
}
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
User user = new User();
user.setUserName("chenlongfei");
testNotNull(user);
}
}
由于只填寫了昵稱而沒填密碼,不能通過驗(yàn)證蟋恬,控制臺打印出:
密碼不能為空
而該信息正是在@NotNull
注解password
字段時(shí)由message
定義的翁潘。