java註解

       有必要對JDK 5.0新增的註解(Annotation)技術進行簡單的學習,因爲Spring 支持@AspectJ,而@AspectJ本身就是基於JDK 5.0的註解技術。所以學習JDK 5.0的註解知識有助於我們更好地理解和掌握Spring的AOP技術。 

瞭解註解 
對於Java開發人員來說,在編寫代碼時,除了源程序以外,我們還會使用Javadoc標籤對類、方法或成員變量進行註釋,以便使用Javadoc工具生成和源代碼配套的Javadoc文檔。這些@param、@return等Javadoc標籤就是註解標籤,它們爲第三方工具提供了描述程序代碼的註釋信息。使用過Xdoclet的朋友,對此將更有感觸,像Struts、Hibernate都提供了Xdoclet標籤,使用它們可以快速地生成對應程序代碼的配置文件。 

JDK5.0註解可以看成是Javadoc標籤和Xdoclet標籤的延伸和發展。在JDK5.0中,我們可以自定義這些標籤,並通過Java語言的反射機制中獲取類中標註的註解,完成特定的功能。 

註解是代碼的附屬信息,它遵循一個基本原則:註解不能直接干擾程序代碼的運行,無論增加或刪除註解,代碼都能夠正常運行。Java語言解釋器會忽略這些註解,而由第三方工具負責對註解進行處理。第三方工具可以利用代碼中的註解間接控制程序代碼的運行,它們通過Java反射機制讀取註解的信息,並根據這些信息更改目標程序的邏輯,而這正是Spring AOP對@AspectJ提供支持所採取的方法。


一個簡單的註解類 
通常情況下,第三方工具不但負責處理特定的註解,本身還提供了這些註解的定義,所以我們通常僅需關注如何使用註解就可以了。但定義註解類本身並不困難,Java提供了定義註解的語法。下面,我們馬上着手編寫一個簡單的註解類,如代碼清單7-1所示: 


代碼清單7-1  NeedTest註解類 

package annotation;

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

@Retention(RetentionPolicy.RUNTIME) // ①聲明註解的保留期限
@Target(ElementType.METHOD) // ②聲明可以使用該註解的目標類型
public @interface NeedTest
{// ③定義註解
	boolean value() default true;// ④聲明註解成員
}


Java新語法規定使用@interface修飾符定義註解類,如③所示,一個註解可以擁有多個成員,成員聲明和接口方法聲明類似,這裏,我們僅定義了一個成員,如④所示。成員的聲明有以下幾點限制: 
           A:成員以無入參無拋出異常的方式聲明,如boolean value(String str)、boolean value() throws Exception等方式是非法的;
           B:可以通過default爲成員指定一個默認值,如String level() default "LOW_LEVEL"、int high() default 2是合法的,當然也可以不指定默認值;
           C:成員類型是受限的,合法的類型包括原始類型及其封裝類、String、Class、enums、註解類型,以及上述類型的數組類型。如ForumService value()、List foo()是非法的。

在①和②處,我們所看到的註解是Java預定義的註解,稱爲元註解(Meta-Annotation),它們被Java編譯器使用,會對註解類的行爲產生影響。@Retention(RetentionPolicy. RUNTIME)表示NeedTest這個註解可以在運行期被JVM讀取,註解的保留期限類型在java.lang.annotation.Retention類中定義,介紹如下: 
SOURCE:註解信息僅保留在目標類代碼的源碼文件中,但對應的字節碼文件將不再保留;
CLASS:註解信息將進入目標類代碼的字節碼文件中,但類加載器加載字節碼文件時不會將註解加載到JVM                              中,也即運行期不能獲取註解信息;
RUNTIME:註解信息在目標類加載到JVM後依然保留,在運行期可以通過反射機制讀取類中註解信息。            Target(ElementType.METHOD)表示NeedTest這個註解只能應用到目標類的方法上,註解的應用目標在            java.lang.annotation.ElementType類中定義:
TYPE:類、接口、註解類、Enum聲明處,相應的註解稱爲類型註解;
FIELD:類成員變量或常量聲明處,相應的註解稱爲域值註解;
METHOD:方法聲明處,相應的註解稱爲方法註解;
PARAMETER:參數聲明處,相應的註解稱爲參數註解;
CONSTRUCTOR:構造函數聲明處,相應的註解稱爲構造函數註解;
LOCAL_VARIABLE:局部變量聲明處,相應的註解稱爲局域變量註解;
ANNOTATION_TYPE:註解類聲明處,相應的註解稱爲註解類註解,ElementType. TYPE包括ElementType.ANNOTATION_TYPE;
PACKAGE:包聲明處,相應的註解稱爲包註解。

如果註解只有一個成員,則成員名必須取名爲value(),在使用時可以忽略成員名和賦值號(=),如@NeedTest(true)。註解類擁有多個成員時,如果僅對value成員進行賦值則也可不使用賦值號,如果同時對多個成員進行賦值,則必須使用賦值號,如DeclareParents (value = "NaiveWaiter", defaultImpl = SmartSeller.class)。註解類可以沒有成員,沒有成員的註解稱爲標識註解,解釋程序以標識註解存在與否進行相應的處理;此外,所有的註解類都隱式繼承於java.lang.annotation.Annotation,但註解不允許顯式繼承於其他的接口。 

我們希望使用NeedTest註解對業務類的方法進行標註,以便測試工具可以根據註解情況激活或關閉對業務類的測試。在編寫好NeedTest註解類後,就可以在其他類中使用它了。 

使用註解 
我們在ForumService中使用NeedTest註解,標註業務方法是否需要測試,如代碼清單7-2所示: 
代碼清單7-2  ForumService:使用註解 

package annotation;  

public class ForumService {  
    @NeedTest(value=true) ①  
    public void deleteForum(int forumId){  
        System.out.println("刪除論壇模塊:"+forumId);  
    }  
    @NeedTest(value=false) ②  
    public void deleteTopic(int postId){  
        System.out.println("刪除論壇主題:"+postId);  
    }     
} 


如果註解類和目標類不在同一個包中,需要通過import引用的註解類。在①和②處,我們使用NeedTest分別對deleteForum()和deleteTopic()方法進行標註。在標註註解時,可以通過以下格式對註解成員進行賦值: 

引用
@<註解名>(<成員名1>=<成員值1>,<成員名1>=<成員值1>,...)

如果成員是數組類型,可以通過{}進行賦值,如boolean數組的成員可以設置爲{true,false,true}。下面是幾個註解標註的例子: 

示例1,多成員的註解:

A@AnnoExample(id= 2868724, synopsis = "Enable time-travel",  engineer = "Mr. Peabody",date = "4/1/2007") 

示例2,一個成員的註解,成員名爲value。可以省略成員名和賦值符號: 
@Copyright("2011 bookegou.com All Right Reserved")  

示例3,無成員的註解: 
@Override  

示例4,成員爲字符串數組的註解: 
@SuppressWarnings(value={"unchecked","fallthrough"})  

示例5,成員爲註解數組類型的註解: 
@Reviews({@Review(grade=Review.Grade.EXCELLENT,reviewer="df"),        
           @Review(grade=Review.Grade.UNSATISFACTORY,reviewer="eg",                
                    comment="This method needs an @Override annotation")})  

@Reviews註解擁有一個@Review註解數組類型的成員,@Review註解類型有三個成員,其中reviewer、comment都是String類型,但comment有默認值,grade是枚舉類型的成員。 

        由於NeedTest註解的保留限期是RetentionPolicy.RUNTIME類型,因此當ForumService被加載到JVM時,仍就可通過反射機制訪問到ForumService各方法的註解信息。 


訪問註解 
       前面提到過,註解不會直接影響程序的運行,但是第三方程序或工具可以利用代碼中的註解完成特殊的任務,間接控制程序的運行。對於RetentionPolicy.RUNTIME保留期限的註解,我們可以通過反射機制訪問類中的註解。 

在JDK5.0裏,Package、Class、Constructor、Method以及Field等反射對象都新增了訪問註解信息的方法:<T extends Annotation>T getAnnotation(Class<T> annotationClass),該方法支持通過泛型直接返回註解對象。 

下面,我們就通過反射來訪問註解,得出ForumService 類中通過@NeedTest註解所承載的測試需求,如代碼清單7-3所示:

package annotation;

import java.lang.reflect.Method;

public class TestTool
{
	public static void main(String[] args)
	{

		// ①得到ForumService對應的Class對象
		Class clazz = ForumService.class;

		// ②得到ForumSerivce對應的Method數組
		Method[] methods = clazz.getDeclaredMethods();

		System.out.println(methods.length);
		for (Method method : methods)
		{

			// ③獲取方法上所標註的註解對象
			NeedTest nt = method.getAnnotation(NeedTest.class);
			if (nt != null)
			{
				if (nt.value())
				{
					System.out.println(method.getName() + "()需要測試");
				} else
				{
					System.out.println(method.getName() + "()不需要測試");
				}
			}
		}
	}
}
在③處,通過方法的反射對象,我們獲取了方法上所標註的NeedTest註解對象,接着就可以訪問註解對象的成員,從而得到ForumService類方法的測試需求。


J2SE5.0爲註解單獨提供了4種註解

---------------------------------------------

它們是Target、Retention、Documented和Inherited。

可以在自定義註解時使用這4個註解。

 

@Inherited

---------------------------------------------

繼承是java主要的特性之一。在類中的protected和public成員都將會被子類繼承,但是父類的註解會不會被子類繼承呢?

很遺憾的告訴大家,在默認的情況下,父類的註解並不會被子類繼承。如果要讓這個註解可以被繼承,就必須在定義註解時在源碼上加上@Inherited註解。 

 

Java代碼  收藏代碼
  1. 自大定義一個MyAnnotation註解  
  2. @Inherited  
  3. @interface MyAnnotation { }  
  4.   
  5. 使用MyAnnotation註解  
  6. @MyAnnotation  
  7. public class ParentClass {}  
  8.   
  9. 子類繼承父類  
  10. public class ChildClass extends ParentClass { }  

 

在以上代碼中ChildClass和ParentClass一樣都已被MyAnnotation註解了。 

 

@Retention 

---------------------------------------------

成功通過反射讀取類上或方法上的註解,是有前提的--要使用@Retention,就是設置註解是否保存在class文件中。

下面的代碼是Retention的詳細用法。 

 

Java代碼  收藏代碼
  1. @Retention(RetentionPolicy.SOURCE)  
  2. @interface MyAnnotation1 { }  
  3. //作用是不將註解保存在class文件中,也就是說象“//”一樣在編譯時被過濾掉了。  
  4.   
  5. @Retention(RetentionPolicy.CLASS)  
  6. @interface MyAnnotation2 {}  
  7. //作用是隻將註解保存在class文件中,而使用反射讀取註解時忽略這些註解。  
  8.   
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. @interface MyAnnotation3 {}  
  11. //作用是即將註解保存在class文件中,也可以通過反射讀取註解。這也是最常用的值   
 

 

 

@Target

---------------------------------------------

標識自定義註解 可以作用於 類上、方法上、成員變量上、構造方法、其它...

 

@Documented 

---------------------------------------------

在默認的情況下在使用javadoc自動生成文檔時,註解將被忽略掉。如果想在文檔中也包含註解,必須使用Documented爲文檔註解。 注意是自己定義的註解加@Documented 如:

import java.lang.annotation.Documented;
@Documented
public @interface DocumentedAnnotation
{
	String hello();
}

如何讀取註解--類上的註解

---------------------------------------------

 

Java代碼  收藏代碼
  1. //取得類上的指定的註解  
  2. Annotation annotation = 類.class.getAnnotation(MyAnnotation.class);  
  3.   
  4. //取得類上的所有註解,包括繼承的註解。  
  5. Annotation[] annotations = 類.class.getAnnotations();  
  6.   
  7. //取當前類上的所有的註解,不包括繼承的  
  8. Annotation[] annotations = 類.class.getDeclaredAnnotations();  
 

 

如何讀取註解--方法上的註解

---------------------------------------------

 

Java代碼  收藏代碼
  1. //取得方法上的指定的註解  
  2. Method m=?  
  3. Annotation annotation = m.getAnnotation(MyAnnotation.class);  
  4.   
  5. //取得方法上的所有註解,包括繼承的註解。  
  6. Annotation[] annotations = m.getAnnotations();  
  7.   
  8. //取當前方法上的所有的註解,不包括繼承的  
  9. Annotation[] annotations = m.getDeclaredAnnotations();  
  

注:要想使用反射得到註解信息,這個註解在定義時源碼中必須使用

@Retention(RetentionPolicy.RUNTIME)進行註解。



發佈了33 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章