1. 繼續上一講内容,複習上一講内容我們講到了 Retention以及RetentionPolicy。這兩個都是成對出現的,因為Retention裡面包含了一個屬性value類型為 RetentionPolicy 枚舉類型,它有三個枚舉CLASS、RUNTIME、SOURCE。分别表示注解産生在三種不同的環境下。
2. 下面我們學習一下,如果在RUNTIME情況下,如何利用反射來讀取已經Class中的注解資訊。
定義Annotation時必須設定RetentionPolicy為RUNTIME,也就是可以在VM中讀取Annotation資訊。
1) 定義注解 MyAnnotation.java
2) 定義一個類使用上面定義的注解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(); }
3) 定義一個類利用反射來取得注解中的資訊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!"); } }
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
2) 定義一個類使用這個注解,如下圖所示提示錯誤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(); }
提示錯誤原因是在MyTarget這個注解中我們定義的注解隻能用來修飾方法,是以在修飾類的時候會提示錯誤。修改的方法是将MyTarget.java注解Target的value改為TYPE則可以,如下代碼所示:![]()
JavaSE第七十一講:Target及ElementType詳解
@Target(ElementType.TYPE) public @interface MyTarget { String value(); }
4. 要求為API檔案@Documented
定義一個帶@Documented的注解![]()
JavaSE第七十一講:Target及ElementType詳解 定義一個使用這個注解的類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詳解