Java-Java5.0註解全面解讀
概述
Java5.0註解可以看做Javadoc和Xdoclet標簽的延伸和發展,在Java5.0中可以自定義這些標簽,並通過Java語言的反射機制獲取類中標註的註解,完成特定的功能。
註解是代碼的附屬信息,它遵循一個基本的原則:註解不能直接幹擾程序代碼的運行,無論增加或者刪除註解,代碼都能正常運行。
Java語言解釋器會忽略這些註解,而由第三方工具負責對註解進行處理。 第三方工具可以利用代碼中的註解間接控制程序代碼的運行,它們通過Java反射機制讀取註解的信息,並根據這些信息更改目標程序的邏輯。
元註解Meta-annotation
元註解的作用就是負責註解其他註解。
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)有: @Target(ElementType.XXX)取值如下
- –
TYPE
:類、接口、註解類、Enum聲明處,相應的註解稱為類型註解 - –
FIELD
:類成員變量或者常量聲明處,相應的註解被稱為域值註解 - –
METHOD
:方法處聲明,相應的註解稱為方法註解 - –
PARAMETER
:參數聲明處,相應的註解稱為參數註解 - –
CONSTRUCTOR
:構造函數聲明處,相應的註解稱為構造函數註解 - –
LOCAL_VARIABLE
:局部變量聲明處,相應的註解稱為局域比納涼註解 - –
PACKAGE
:包聲明處,相應的註解被稱為包註解
舉例:
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; /** * * @Description: 註解@DataSource既可以加在方法上,也可以加在接口或者接口的實現類上 * * * @author: Mr.Yang * @date: 2017年7月24日 下午9:59:29 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { // 和配置文件中 dynamicDatasourceMap中的key保持一致 public static String PR_RB = "dataSourcePR"; public static String DR_RB = "dataSourceDR"; public static String PR_CC = "dataSourceCC"; /** * * * @Title: name * * @Description: 如果僅標註@DataSource 默認為PR_RB數據庫實例 * * @return * * @return: String */ String name() default DataSource.PR_RB; }
@Retention
@Retention定義瞭該Annotation被保留的時間長短.
某些Annotation僅出現在源代碼中,而被編譯器丟棄;
而另一些卻被編譯在class文件中,編譯在class文件中的Annotation可能會被虛擬機忽略,
而另一些在class被裝載時將被讀取(並不影響class的執行,因為Annotation與class在使用上是被分離的)。
使用這個meta-Annotation可以對 Annotation的“生命周期”限制。
作用:表示需要在什麼級別保存該註釋信息,用於描述註解的生命周期(即被描述的註解在什麼范圍內有效)
Retention meta-annotation類型有唯一的value作為成員,它的取值來自java.lang.annotation.RetentionPolicy的枚舉類型值。
取值(RetentionPoicy)有:
- SOURCE:在源文件中有效(即源文件保留),單對應的字節碼文件將不再保留
- CLASS:在class文件中有效(即class保留),但類傢愛妻加載字節碼文件時不會將註解加載到JVM中,即運行期不能獲取註解信息
- RUNTIME:在運行時有效(即運行時保留),註解信息在目標類加載到JVM後依然保留,在運行期可以通過反射機制讀取類中的註解信息
比如
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { ....... }
@Documented
@Documented用於描述其它類型的annotation應該被作為被標註的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { ....... }
@Inherited
@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類型被發現,或者到達類繼承結構的頂層。
自定義註解
使用@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類型裡面的參數設定規則:
第一,隻能用public或默認(default)這兩個訪問權修飾.例如,String value();這裡把方法設為defaul默認類型;
第二,參數成員隻能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.例如,String value();這裡的參數成員就為String;
第三,如果隻有一個參數成員,最好把參數名稱設為”value”,後加小括號.
實例
代碼已托管到Github—> https://github.com/yangshangwei/SpringMaster
通常情況下,第三方工具不但負責處理特定的註解,其本身還提供瞭這些註解的定義。 事實上,定義註解類本身並不困難,Java提供瞭定義註解的語法,如上所述。
編寫註解類
我們著手來編寫一個簡單的註解類
package com.xgj.aop.spring.advisor.anno; 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; /** * * * @ClassName: NeedTest * * @Description: 自定義註解 * * @author: Mr.Yang * * @date: 2017年8月23日 下午4:50:11 */ // 聲明註解的保留期限 @Retention(RetentionPolicy.RUNTIME) // 聲明可以使用該註解的目標類型 @Target(ElementType.METHOD) // 可以被javadoc此類的工具文檔化 @Documented public @interface NeedTestAnnotation { // 定義註解 // 聲明註解成員 boolean value() default false; }
Java新語法規定使用@interface修飾符定義註解類,一個註解可以擁有多個成員,成員聲明和接口方法聲明類似,這裡僅僅定義瞭一個成員。
成員聲明以下幾點限制:
- 成員以無入參,無拋出異常的方式聲明, 以下方式是不合法的 比如 boolean value(String xx)、boolean value()throws Exception等是非法的
- 可以通過default成員指定一個默認值,比如 String level() defalut “LOW_LEVEL”, int high() default 2 是合法的,當然也可以不指定默認值。
- 成員類型是受限制的,成員隻能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.例如,String value();這裡的參數成員就為String; 而 List foo() 、ForumServer value()是不合法的。
如果註解隻有一個成員,成員名建議為value().
在使用時可以忽略成員和賦值號(=),比如 @NeedTestAnnotation(true).
當註解類擁有多個成員時,如果僅僅對value成員進行賦值,則也可不使用賦值號。
如果同時對多個成員進行賦值,這必須使用賦值號,比如 @DecalreParents(value=”XXX”,defaultImpl=XXX).
註解類可以沒有成員,沒有成員的註解稱為標註註解,解釋程序以標識註解存在與否進行相應的處理
此外,所有的註解都隱式繼承與java.lang.annotation.Annotation,但註解不允許顯示繼承其他的接口。
使用註解
我們在業務類中,使用NeedTestAnnotation註解,標註業務方法是否需要測試。
如下
package com.xgj.aop.spring.advisor.anno; /** * * * @ClassName: ForumService * * @Description: 演示使用自定義註解@NeedTestAnnotation * * @author: Mr.Yang * * @date: 2017年8月23日 下午5:15:18 */ public class ForumService { @NeedTestAnnotation public void removeTopicId(int topicId) { System.out.println("remove topicId" + topicId); } @NeedTestAnnotation(true) public void removeForumId(int forumId) { System.out.println("remove forumId" + forumId); } }
如果註解類和目標類不在同一個包中,這需要通過improt引用註解類。
在標註註解時,可以通過以下格式對註解成員進行賦值
@<註解名>(<成員名1>=<成員值1>,<成員名2>=<成員值2>,.....)
如果成員是數組類型,這可以通過{}進行賦值,比如 boolean數組的成員可以設置為{true,false,true}
訪問註解
我們說過,註解不會直接影響程序的運行,但是第三方程序或者工具可以利用代碼中的註解完成特殊的任務,間接控制程序的運行。 對於RetentionPolicy.RUNTIME保留期限的註解,可以通過反射機制訪問類中的註解。
在Java5.0中,Package、Class、Constructor、Method以及Field 等反射對象都新增瞭訪問註解信息的方法:<T extends Annotation>T getAnnotation(Class<T> annotationClass)該方法支持通過泛型直接返回註解對象.
下面通過反射來訪問註解,得到ForumService類中通過@NeedTestAnnotation註解所承載的測試需求
package com.xgj.aop.spring.advisor.anno; import java.lang.reflect.Method; import org.junit.Test; /** * * * @ClassName: AnnotaitionSelfDefineTool * * @Description: 訪問註解 * * @author: Mr.Yang * * @date: 2017年8月23日 下午5:18:26 */ public class AnnotaitionSelfDefineTool { @Test public void test() { // 得到對應的Class Class<ForumService> clazz = ForumService.class; // 因為標註在方法上,所以先得到對應的方法 Method[] methods = clazz.getDeclaredMethods(); System.out.println(clazz.getName() + " 有 " + methods.length + " 個方法"); // 遍歷方法 for (Method method : methods) { // 獲取方法上的註解 NeedTestAnnotation needTestAnnotation = method .getAnnotation(NeedTestAnnotation.class); // 判斷是否有標註此註解 if (needTestAnnotation != null) { // 獲取註解中定義的值,做處理 if (needTestAnnotation.value()) { System.out.println(method.getName() + "需要測試"); } else { System.out.println(method.getName() + "不需要測試"); } } } } }
單元測試結果:
com.xgj.aop.spring.advisor.anno.ForumService 有 2 個方法
removeTopicId不需要測試
removeForumId需要測試
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。