1. 程式人生 > >JAVA反射_註解

JAVA反射_註解

概念及作用

Java註解,從名字上看是註釋,解釋。但功能卻不僅僅是註釋那麼簡單。
註解(Annotation)即元資料,就是原始碼的元資料
註解(Annotation)為我們在程式碼中新增資訊提供了一種形式化的方法,使我們可以在稍後某個時刻方便地使用這些資料(通過解析註解來使用這些資料).

註解(Annotation)是一種應用於類、方法、引數、變數、構造器及包宣告中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元資料的一種工具。


1.作用

* 生成文件
* 跟蹤程式碼依賴性,實現替代配置檔案功能,減少配置。如Spring中的一些註解
* 在編譯時進行格式檢查,如@Override等
* 每當你建立描述符性質的類或者介面時,一旦其中包含重複性的工作,就可以考慮使用註解來簡化與自動化該過程。


包java.lang.annotation中包含所有定義自定義註解所需用到的原註解和介面。如介面java.lang.annotation.Annotation是所有註解繼承的介面,並且是自動繼承,不需要定義時指定,類似於所有類都自動繼承Object。
該包同時定義了四個元註解,Documented,Inherited,Target(作用範圍,方法,屬性,構造方法等),Retention(生命範圍,原始碼,class,runtime)。
(元註解:自定義註解的時候用到的,也就是自定義註解的註解,也就是自定義註解的註釋,解釋)


@Target

@Target說明了所修飾註解的範圍:該註解可被用於 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的“生命週期”限制。
作用:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
取值(RetentionPoicy)有:

型別 用途 說明
SOURCE 在原始檔中有效 僅出現在原始碼中,而被編譯器丟棄
CLASS 在原始檔中有效 被編譯在class檔案中
RUNTIME 在執行時有效 編譯在class檔案中

@Documented

作用:將註解包含在javadoc中,它代表著此註解會被javadoc工具提取成文件。


@Inherited

作用:允許子類繼承父類中的註解

自定義註解

格式:

public @interface 註解名{
  定義體
}

註解引數的可支援資料型別:
所有基本資料型別(int,float,double,boolean,byte,char,long,short)
String 型別
Class型別
enum型別
Annotation型別
以上所有型別的陣列


規則:

* 修飾符只能是public 或預設(default)
* 引數成員只能用基本型別byte,short,int,long,float,double,boolean八種基本型別和
String,Enum,Class,annotations及這些型別的陣列
如果只有一個引數成員,最好將名稱設為”value”
註解元素必須有確定的值,可以在註解中定義預設值,也可以使用註解時指定,非基本型別的值不可為null,常使用空字串或0作預設值
在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字串或負值


最簡單的例子:

public @interface TestA {
//這裡定義了一個空的註解,它能幹什麼呢。我也不知道,但它能用。
}

在下面這個程式使用它:

@TestA    //使用了類註解
public class UserAnnotation {
    
    @TestA //使用了類成員註解
    private Integer age;
    
    @TestA //使用了構造方法註解
    public UserAnnotation(){
        
    }
    @TestA //使用了類方法註解
    public void a(){
        @TestA //使用了局部變數註解
        Map m = new HashMap(0);
    }
    
    public void b(@TestA Integer a){ //使用了方法引數註解
        
    }
}


這個註解也太簡單了吧,好像什麼資訊也不能傳遞。別急下面就來一步步完善它,也該四位元註解依次開始上場了。

@Target(ElementType.TYPE)
public @interface TestA {
 
}
測試類那邊立馬出現了一堆錯誤,除了類註解。我想到這,聰明的你立刻明白了這個元註解的意義了。是不是想當然的偷起懶來了?難道還有意外?細心的朋友應該發現了,我們的測試類少了一個屬性沒用,就是ElemenetType.PACKAGE。在我們的註解加上這個屬性的元註解後,我們測試程式的元註解全部陣亡,不對,還有一個沒加呢,好加上。package 包,想當然是載入 package 前面。即

@TestA package com.king.annotation;
 
什麼,也報錯?這就搞不明白了,不加在這加哪去呢。我也不知道了,不過這是編譯錯誤,我們的IDE將錯誤給我們指出了,就是

  Package annotations must be in file package-info.java ,E文雖然不好,但這個簡單的還是難不倒的,package註解必須定義在package-info.java中。package-info 又是什麼東西,百度..。

OK,到此,@Target元註解就全部完成了。

第二個元註解: @Retention 引數 RetentionPolicy。有了前面的經驗這個註解理解起來就簡單多了,並且幸運的是這個註解還沒有特殊的屬性值。
簡單演示下如何使用:

@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
 
}
第三和第四個元註解就不再舉例了。

下面我們還是繼續來深入的探討下註解的使用。上面的例子都非常簡單,註解連屬性都沒有。
OK,下面我們就來定義一個有屬性的註解,並在例子程式中獲取到註解中定義的值。


開始之前講下定義屬性的規則:

@interface用來宣告一個註解,其中的每一個方法實際上是聲明瞭一個配置引數。
方法的名稱就是引數的名稱,返回值型別就是引數的型別(參考上面)。
可以通過default來宣告引數的預設值。
程式碼:

/*
* 自定義註解 TestA
* 為方便測試:註解目標為類方法,屬性及構造方法
* 註解中含有三個元素 id ,name和 gid;
* id元素 有預設值 0
*/
@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
String name();
int id() default 0;
Class<Long> gid();
}

下面改下我們的測試類:

@TestA(name="type",gid=Long.class) //類成員註解
public class UserAnnotation {
	@TestA(name="param",id=1,gid=Long.class) //類成員註解
	private Integer age;

	@TestA (name="construct",id=2,gid=Long.class)//構造方法註解
	public UserAnnotation(){
	}

	@TestA(name="public method",id=3,gid=Long.class) //類方法註解
	public void a(){
		Map<String,String> m = new HashMap<String,String>(0);
	}

	@TestA(name="protected method",id=4,gid=Long.class) //類方法註解
	protected void b(){
		Map<String,String> m = new HashMap<String,String>(0);
	}

	@TestA(name="private method",id=5,gid=Long.class) //類方法註解
	private void c(){
		Map<String,String> m = new HashMap<String,String>(0);
	}
	public void b(Integer a){
	}
}


下面到了最重要的一步了,就是如何讀取我們在類中定義的註解。只要讀取出來了使用的話就簡單了。
JAVA既然增加了註解,肯定就增加了相關讀取的API。
在java.lang.reflect包中新增了AnnotatedElement介面,JDK原始碼如下:
public interface AnnotatedElement {
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Annotation[] getAnnotations();
Annotation[] getDeclaredAnnotations();
}

isAnnotationPresent:判斷是否標註了指定註解
getAnnotation:獲取指定註解,沒有則返回null
getAnnotations:獲取所有註解,包括繼承自基類的,沒有則返回長度為0的陣列
getDeclaredAnnotations:獲取自身顯式標明的所有註解,沒有則返回長度為0的陣列

public class ParseAnnotation {
 
/**
* 簡單打印出UserAnnotation 類中所使用到的類註解
* 該方法只打印了 Type 型別的註解
* @throws ClassNotFoundException
*/
public static void parseTypeAnnotation() throws ClassNotFoundException {
	Class clazz = Class.forName("com.tmser.annotation.UserAnnotation");
	Annotation[] annotations = clazz.getAnnotations();
	for (Annotation annotation : annotations) {
		TestA testA = (TestA)annotation;
		System.out.println("id= \""+testA.id()+"\"; name= \""+testA.name()+"\"; gid = "+testA.gid());
	}
}
/**
* 簡單打印出UserAnnotation 類中所使用到的方法註解
* 該方法只打印了 Method 型別的註解
* @throws ClassNotFoundException
*/
public static void parseMethodAnnotation(){
	Method[] methods = UserAnnotation.class.getDeclaredMethods();
	for (Method method : methods) {
		/*
		* 判斷方法中是否有指定註解型別的註解
		*/
		boolean hasAnnotation = method.isAnnotationPresent(TestA.class);
		if (hasAnnotation) {
			/*
			* 根據註解型別返回方法的指定型別註解
			*/
			TestA annotation = method.getAnnotation(TestA.class);
			System.out.println("method = " + method.getName()
			+ " ; id = " + annotation.id() + " ; description = "
			+ annotation.name() + "; gid= "+annotation.gid());
		}
	}
}
/**
* 簡單打印出UserAnnotation 類中所使用到的方法註解
* 該方法只打印了 Method 型別的註解
* @throws ClassNotFoundException
*/
public static void parseConstructAnnotation(){
	Constructor[] constructors = UserAnnotation.class.getConstructors();
	for (Constructor constructor : constructors) {
		/*
		* 判斷構造方法中是否有指定註解型別的註解
		*/
		boolean hasAnnotation = constructor.isAnnotationPresent(TestA.class);
		if (hasAnnotation) {
			/*
			* 根據註解型別返回方法的指定型別註解
			*/
			TestA annotation =(TestA) constructor.getAnnotation(TestA.class);
			System.out.println("constructor = " + constructor.getName()
			+ " ; id = " + annotation.id() + " ; description = "
			+ annotation.name() + "; gid= "+annotation.gid());
		}
	}
}

public static void main(String[] args) throws ClassNotFoundException {
	parseTypeAnnotation();
	parseMethodAnnotation();
	parseConstructAnnotation();
}
}