天天看點

Java注解知識

歡迎通路本人部落格檢視原文:http://wangnan.tech

有必要對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提供支援所采取的方法。

很多東西的設計都必須遵循最基本的原則,為了防止機器人傷害人類,科幻作家阿西莫夫于1940年提出了“機器人三原則”:第一,機器人不能傷害人類;第二,機器人應遵守人類的指令,與第一條違背的指令除外;第三,機器人應能保護自己,與第一條違背的指令除外。這是給機器人賦予的倫理性綱領,機器人學術界一直将這三條原則作為機器人開發的準則。

一個簡單的注解類

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

代碼清單7-1 NeedTest注解類

package com.baobaotao.aspectj.anno;  
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修飾符定義注解類,如③所示,一個注解可以擁有多個成員,成員聲明和接口方法聲明類似,這裡,我們僅定義了一個成員,如④所示。成員的聲明有以下幾點限制:

成員以無入參無抛出異常的方式聲明,如boolean value(String str)、boolean value() throws Exception等方式是非法的; 

可以通過default為成員指定一個預設值,如String level() default “LOW_LEVEL”、int high() default 2是合法的,當然也可以不指定預設值; 

成員類型是受限的,合法的類型包括原始類型及其封裝類、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 com.baobaotao.aspectj.anno;  
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,多成員的注解:

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

示例2,一個成員的注解,成員名為value。可以省略成員名和指派符号:

示例3,無成員的注解:

示例4,成員為字元串數組的注解:

示例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 getAnnotation(Class annotationClass),該方法支援通過泛型直接傳回注解對象。

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

代碼清單7-3 TestTool:通路代碼中的注解

package com.baobaotao.aspectj.anno;  
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類方法的測試需求。運作以上代碼,輸出以下的資訊:

deleteForum()需要測試 

deleteTopic()不需要測試