天天看點

JavaSE第七十一講:Target及ElementType詳解

1. 繼續上一講内容,複習上一講内容我們講到了 Retention以及RetentionPolicy。這兩個都是成對出現的,因為Retention裡面包含了一個屬性value類型為 RetentionPolicy 枚舉類型,它有三個枚舉CLASS、RUNTIME、SOURCE。分别表示注解産生在三種不同的環境下。

2. 下面我們學習一下,如果在RUNTIME情況下,如何利用反射來讀取已經Class中的注解資訊。

定義Annotation時必須設定RetentionPolicy為RUNTIME,也就是可以在VM中讀取Annotation資訊。

1) 定義注解 MyAnnotation.java

package com.ahuier.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/*
 * 使用Retention修飾這個注解,
 * 告訴這個注解在使用的時候即可以用到.Class檔案,又能在運作過程中通過反射的方式讀取出來
 * 這個注解用在Mytest類 和 它裡面的方法output()方法上
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String hello() default "ahuier";
	String world();
}
           
2) 定義一個類使用上面定義的注解
package com.ahuier.annotation;

@MyAnnotation(hello = "beijing", world = "shanghai")
public class MyTest {
	
	@MyAnnotation(hello = "tianjin", world = "shangdi")
	@Deprecated
	@SuppressWarnings("unchecked")       //一個方法可以有多個注解所修飾
	public void output(){
		System.out.println("output something!");
	}
}
           
3) 定義一個類利用反射來取得注解中的資訊
package com.ahuier.annotation;

import java.lang.reflect.Method;

/*
 * 通過反射擷取類MyTest中output()方法上所有的資訊,并且把資訊顯示出來
 */
public class MyReflection {
	public static void main(String[] args) throws Exception{
		MyTest myTest = new MyTest();
		Class<MyTest> c = MyTest.class;
		
		/*
		 * 通過反射擷取method對象,由于Method實作了AnnotatedElement接口,是以它可以調用擷取注解資訊的API來獲得注解資訊。
		 */
		Method method = c.getMethod("output", new Class[]{}); 
		
		/*
		 * 檢視JDk Doc 文檔,在Method類的父類AccessibleObject類中isAnnotationPresent()
		 * 判斷某一個方法上面是否存在某一個注解
		 */
		if(method.isAnnotationPresent(MyAnnotation.class)){
			method.invoke(myTest, new Object[]{}); //如果存在這個注解,則去調用MyTest類中output()方法
		}				
	}
}
           

編譯執行結果:

output something!

【說明】:以上輸出方法内容,是因為我們在定義注解的時候,将注解資訊 Retention 設定為“RUNTIME”

為了更好的了解注解資訊三個枚舉的不同,我們将注解 MyAnnotation.java 中的注解 Retention 設定為"CLASS" 和 "SOURCE" 如下代碼所示:

@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
	String hello() default "ahuier";
	String world();
}
           
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
	String hello() default "ahuier";
	String world();
}
           

編譯執行結果都沒輸出任何内容,因為設定為這兩個類型的枚舉值之後,它們就不會在JVM運作期保留這些注解資訊,是以即便通過反射方式也是無法獲得他們的資訊的,也就是在執行 MyReflection.java這個類的 if(method.isAnnotationPresent(MyAnnotation.class))時候,它傳回false,不執行裡面的内容了。

通過上面一個例子我們可以知道使用RUNTIME之後,是可以通過反射的方式調用到被注解修飾的方法,現在我們可以通過它們的一些API來獲得注解的一些某一個方法上面特定的注解資訊。

對上面第三個代碼 MyReflection.java 繼續寫下去如下:

package com.ahuier.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/*
 * 通過反射擷取類MyTest中output()方法上所有的資訊,并且把資訊顯示出來
 */
public class MyReflection {
	public static void main(String[] args) throws Exception{
		MyTest myTest = new MyTest();
		Class<MyTest> c = MyTest.class;
		
		/*
		 * 通過反射擷取method對象,由于Method實作了AnnotatedElement接口,是以它可以調用擷取注解資訊的API來獲得注解資訊。
		 */
		Method method = c.getMethod("output", new Class[]{}); 
		
		/*
		 * 檢視JDk Doc 文檔,在Method類的父類AccessibleObject類中isAnnotationPresent()
		 * 判斷某一個方法上面是否存在某一個注解
		 */
		if(method.isAnnotationPresent(MyAnnotation.class)){
			method.invoke(myTest, new Object[]{}); //如果存在這個注解,則去調用MyTest類中output()方法
			/*
			 * 檢視JDK Doc中Method類的public <T extends Annotation> T getAnnotation(Class<T> annotationClass)方法。
			 * 如果注解存在,則放回這個注解,否則傳回為空
			 */
			MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
			String hello = myAnnotation.hello(); //擷取 MyAnnotation中的 hello()注解資訊
			String world = myAnnotation.world(); //擷取 MyAnnotation中的 world()注解資訊
			System.out.println(hello + ", " + world);
		}
		/*
		 * 檢視JDK Doc文檔中 public Annotation[] getAnnotations() 
		 * 放回所有在特定元素上的注解,如果這個元素上沒有注解,則傳回一個數組長度為0的數組,而非傳回空的數組,是以不需要判斷數組是否為空,直接周遊數組
		 */
		Annotation[] annotations = method.getAnnotations();
		for(Annotation annotation : annotations){
			
			/*
			 * 檢視JDK文檔Annotation接口的 Class<? extends Annotation> annotationType()
			 * 傳回它所對應的Class對象
			 * 以下輸出:
			 * com.ahuier.annotation.MyAnnotation 
			 * java.lang.Deprecated
			 * 原因是MyTest中的output()上面的定義的三個注解中有兩個注解RetentionPolicy為“RUNTIME”,如果是“CLASS” 和 "SOURCE",則得不到的
			 */
			System.out.println(annotation.annotationType().getName()); 
		}

	}
}
           

編譯執行結果:

output something!

tianjin, shangdi

com.ahuier.annotation.MyAnnotation

java.lang.Deprecated

3. 下面我們學習Target,限定annotation使用對象@Target,如下圖所示:

JavaSE第七十一講:Target及ElementType詳解

這邊的@Target與Retention有異曲同工之妙,Retention搭配RetentionPolicy來指定它的枚舉值是什麼。而Target也搭配ElementType表示這注解可以修飾哪些目标。

檢視JDK Doc文檔Target,它有一個value,是ElementType 枚舉類型,檢視ElementTyped,它的枚舉值如下所示:

JavaSE第七十一講:Target及ElementType詳解

ANNOTATION_TYPE: 注解隻能修飾注解,不能修飾其他的東西

CONSTRUCTOR: 注解隻能修飾構造方法

FIELD: 注解隻能修飾屬性(成員變量)

LOCAL_VARIABLE: 注解隻能修飾局部變量

METHOD: 注解隻能修飾方法

PACKAGE: 注解隻能修飾包

PARAMETER: 注解隻能修飾方法的參數

TYPE: 注解隻能修飾類、接口、枚舉

如下圖所示:

JavaSE第七十一講:Target及ElementType詳解

下面我們來通過例子學習上面的内容:

1) 定義一個注解MyTarget.java

package com.ahuier.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/*
 * @Target(ElementType.METHOD)表示這個注解隻能用來修飾方法
 */
@Target(ElementType.METHOD)
public @interface MyTarget {
	String value();
}
           
2) 定義一個類使用這個注解,如下圖所示提示錯誤
JavaSE第七十一講:Target及ElementType詳解
提示錯誤原因是在MyTarget這個注解中我們定義的注解隻能用來修飾方法,是以在修飾類的時候會提示錯誤。修改的方法是将MyTarget.java注解Target的value改為TYPE則可以,如下代碼所示:
@Target(ElementType.TYPE)
public @interface MyTarget {
	String value();
}
           

4. 要求為API檔案@Documented

JavaSE第七十一講:Target及ElementType詳解
定義一個帶@Documented的注解
package com.ahuier.annotation;

import java.lang.annotation.Documented;

/*
 * @Documented 表示帶@Documented修飾的東西會添加到生成文檔的API中去
 */

@Documented
public @interface DocumentedAnnotation {
	String hello();
}
           
定義一個使用這個注解的類
package com.ahuier.annotation;

public class DocumentedTest {
	
	@DocumentedAnnotation(hello = "welcome")
	public void method(){
		System.out.println("hello world");
	}
}
           

編譯生成Java DOC文檔:Eclipse --> Project --> Generate Javadoc --> 選擇需要生成doc的包

生成完成打開生成API文檔,檢視我們定義的method()方法如下圖所示,已經被标注了注解資訊了。

JavaSE第七十一講:Target及ElementType詳解

5. 子類是否繼承父類@Inherited,如下圖所示:

JavaSE第七十一講:Target及ElementType詳解
到此為止,我們的注解已經全部講完了,在實際開發中,我們一般很少自己去定義注解,但是通過這些内容我們可以明白注解的本源是什麼,可以在以後的實際開發中更好的學習好注解。