基本概念
什麼是注解(Annotation)
Annotation(注解)是JDK5.0及以後版本引入的。它可以用於創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。從某些方面來看,Annotation像一種修飾符一樣,應用於包、類型、構造方法、方法、成員變量、參數以及本地變量的聲明語句中。這些信息被存儲在Annotation的”name=value”結構對中。更通俗的意思是為程序的元素(類、方法、成員變量等)加上更直觀更明了的說明,這些說明信息是與程序的業務邏輯無關,並且是供指定的工具或框架使用的。
什麼是metadata(元數據)
元數據從metadata一詞譯來,就是“關於數據的數據”的意思。
元數據的功能作用有很多,比如:你可能用過Javadoc的注釋自動生成文檔。這就是元數據功能的一種。總的來說,元數據可以用來創建文檔,跟蹤代碼的依賴性,執行編譯時格式檢查,代替已有的配置文件。如果要對於元數據的作用進行分類,目前還沒有明確的定義,不過我們可以根據它所起的作用,大致可分為三類:
編寫文檔:通過代碼裡標識的元數據生成文檔
代碼分析:通過代碼裡標識的元數據對代碼進行分析
編譯檢查:通過代碼裡標識的元數據讓編譯器能實現基本的編譯檢查
在Java中元數據以標簽的形式存在於Java代碼中,元數據標簽的存在並不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或針在運行時知道被運行代碼的描述信息。
綜上所述:
第一,元數據以標簽的形式存在於Java代碼中。
第二,元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。
第三,元數據需要編譯器之外的工具額外的處理用來生成其它的程序部件。
第四,元數據可以只存在於Java源代碼級別,也可以存在於編譯之後的Class文件內部。
注解(Annotation)原理
Annotation其實是一種接口。通過Java的反射機制相關的API來訪問annotation信息。相關類(框架或工具中的類)根據這些信息來決定如何使用該程序元素或改變它們的行為。
Annotation是不會影響程序代碼的執行,無論annotation怎麼變化,代碼都始終如一地執行。
Java語言解釋器在工作時會忽略這些annotation,因此在JVM中這些annotation是“不起作用”的,只能通過配套的工具才能對這些annotation類型的信息進行訪問和處理。
Annotation與Interface的異同:
Annotation類型使用關鍵字@interface而不是interface
這個關鍵字聲明隱含了一個信息:它是繼承了java.lang.annotation.Annotation接口,並非聲明了一個interface
Annotation類型、方法定義是獨特的、受限制的
Annotation類型的方法必須聲明為無參數的、無異常拋出的。這些方法定義了annotation的成員:方法名成為了成員名,而方法返回值成為了成員的類型。方法返回值類型必須為primitive類型、Class類型、枚舉類型、Annotation類型或者由前面類型之一作為元素的一維數組。方法的後面可以使用default和一個默認數值來聲明成員的默認值,null不能作為成員默認值,這與我們在非Annotation類型中定義方法有很大不同。
Annotation類型和它的方法不能使用Annotation類型的參數、成員不能是generic。只有返回值類型是Class的方法可以在Annotation類型中使用generic,因為此方法能夠用類轉換將各種類型轉換為Class。
Annotation類型又與接口有著近似之處
它們可以定義常量、靜態成員類型(比如枚舉類型定義)。Annotation類型也可以如接口一般被實現或者繼承。
應用場合
Annotation一般作為一種輔助途徑,應用在軟件框架或工具中,在這些工具類中根據不同的Annotation注解信息采取不同的處理過程或改變相應程序元素(類、方法、成員變量等)的行為。
例如:JUnit、Struts、Spring等流行工具框架中均廣泛使用了Annotation,使代碼的靈活性大大提高。
Java定義的標准Annotation
從Java5版本開始,自帶了四種標准Annotation類型。
@Override
java.lang.Override是一個標記注解(Marker Annotation)類型,它被用作標注方法。它說明了被標注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這些annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。這個annotation常常在我們試圖覆蓋父類方法而卻又寫錯了方法名時加一個保障性的校驗過程。
@Deprecated
Deprecated也是一種標記注解(Marker Annotation)類型。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標注的程序元素。所以使用這種修飾具有一定的“延續性”,如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋後的類型或者成員並不是被聲明為@Deprecated,但編譯器仍然要報警。
注意:@Deprecated這個Annotation類型和Javadoc中的@deprecated這個Tag是有區別的:前者是java編譯器識別的,而後者是被Javadoc工具所識別用來生成文檔(包含程序成員為什麼已經過時、它應當如何被禁止或者替代的描述)。
@SuppressWarnings
@SuppressWarnings 被用於有選擇的關閉編譯器對類、方法、成員變量、變量初始化的警告。
有時編譯時會提出一些警告,對於這些警告有的隱藏著Bug,有的是無法避免的,對於某些不想看到的警告信息,可以通過這個注解來屏蔽。
SuppressWarnings不是一個標記注解(Marker Annotaion)。它有一個類型為String[]的成員,這個成員的值為被禁止的警告名。對於javac編譯器來講,被-Xlint選項有效的警告名也同樣對@SuppressWarnings有效,同時編譯器忽略掉無法識別的警告名。
Annotation語法允許在Annotation名後跟括號,括號中是使用逗號分割的name=value對用於為Annotation的成員賦值。
代碼:
@SuppressWarnings(value={"unchecked","fallthrough"})
public void lintTrap() {
/* sloppy method body omitted */
}1
在這個例子中SuppressWarnings Annotation類型只定義了一個單一的成員,所以只有一個簡單的value={…}作為name=value對。又由於成員值是一個數組,故使用大括號來聲明數組值。
注意:我們可以在下面的情況中縮寫annotation:當annotation只有單一成員,並成員命名為“value=”。這時可以省去”value=”。比如將上面的SuppressWarnings annotation進行縮寫:
@SuppressWarnings({"unchecked","fallthrough"})
如果SuppressWarnings所有聲明的被禁止警告個數為一個時,可以省去大括號:
@suppressWarnings("unchecked")
SuppressWarnings注解的常見參數值的簡單說明:
deprecation:使用了不贊成使用的類或方法時的警告
unchecked:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型;
fallthrough:當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告
path:在類路徑、源文件路徑等中有不存在的路徑時的警告
serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告;
finally:任何 finally 子句不能正常完成時的警告;
all:關於以上所有情況的警告
Java定義的元注解
元注解的作用就是負責注釋其他注解。Java5.0定義了4個標准的meta-annotation類型,它們被用來提供對其他annotation類型作說明。
Java5.0定義的元注解:
@Target
@Retention
@Documented
@Inherited
這些類型和它們所支持的類在java.lang.annotation包中可以找到。
下面我們看一下每個元注解的作用和相應的參數的使用說明。
@Target
@Target說明了Annotation所修飾的對象范圍:Annotation可被用於packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法,構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類的聲明中使用了target可更加明晰其修飾的目標。
作用:用於描述注解的使用范圍(即:被描述的注解可以用在什麼地方)
取值(ElementType)有:
CONSTRUCTOR:用於描述構造器
FIELD:用於描述域
LOCAL_VARIABLE:用於描述局部變量
METHOD:用於描述方法
PACKAGE:用於描述包
PARAMETER:用於描述參數
TYPE:用於描述類、接口(包括注解類型)或enum聲明
@Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-annotation可以對Annotation的”生命周期”限制。
作用:表示需要在什麼級別保存該注釋信息,用於描述注解的生命周期(即:被描述的注解在什麼范圍內有效)
取值(RetentionPolicy)有:
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:咋運行時有效(即運行時保留)
@Documented
@Documented用於描述其他類型的annotation應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記注解,沒有成員。
@Inherited
@Inherited元注解是一個標記注解,它表示了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
注意:@Inherited annotation類型是被標注過的class的子類所繼承。類並不從它所實現的接口繼承annotation,方法並不從它所重載的方法繼承annotation。
當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,則發射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。
自定義注解(Annotation)
使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義注解時,不能繼承其他的注解或接口。@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。
定義注解格式:
public @interface 注解名 {定義體}
注解參數的可支持數據類型:
所有基本數據類型(int,float,boolean,byte,double,char,long,short)
String類型
Class類型
enum類型
Annotation類型
以上所有類型的數組
自定義注解(Annotation)示例
package org.grape.annotation;
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;
/*
* 作者信息注解
* @author grape
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Author {
String name() default "";
String group() default "";
}1
package org.grape.annotation;
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;
/*
* 類描述注解
* @author grape
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Description {
String value() default "";
}1
package org.grape.annotation;
@Description(value="這是一個有用的工具類")
public class Utility {
@Author(name="jlq",group="grape")
public String work(){
return "work over!";
}
}
提取注解
package org.grape.annotation;
import java.lang.reflect.Method;
public class AnalysisAnnotation {
public static void main(String[] args) {
try {
Class rt_class = Class.forName("org.grape.annotation.Utility");
Method[] methods = rt_class.getMethods();
boolean flag = rt_class.isAnnotationPresent(Description.class);
if (flag) {
Description description = (Description) rt_class
.getAnnotation(Description.class);
System.out.println("Utility's Description---->"
+ description.value());
for (Method method : methods) {
if(method.isAnnotationPresent(Author.class)){
Author author = (Author)method.getAnnotation(Author.class);
System.out.println("Utility's Author---->" + author.name() + " from " + author.group());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
通過此類來獲得與普通Java類Utility.java關聯的信息,即描述和作者。
運行AnalysisAnnotation,輸出結果為:
Utility’s Description—>這是一個有用的工具類
Utility’s Author—>jlq from grape