JAVA提高五:注解Annotation


今天我們學習JDK5.0中一個非常重要的特性,叫做注解。是現在非常流行的一種方式,可以說因為配置XML 比較麻煩或者比容易查找出錯誤,現在越來越多的框架開始支持注解方式,比如注明的Spring 框架,常用的注解:@Required, @Autowired, @PostConstruct, @PreDestory;可見注解的重要性。

一、什么是注解(Annotation)和 元數據(metadata)?

Annotation(注解)就是Java提供了一種為程序元素關聯任何信息或任何元數據(metadata)的途徑和方法Annotion(注解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然后通過Annotion對象來獲取注解里面的元數據。簡單一點說:就是為程序打上了某種標記,可以加在類,包,字段 ,方法,方法的參數及局部變量上。

Annotation的成員在Annotation類型中以無參數的方法的形式被聲明(比如:String color() default "blue";)。其方法名和返回值定義了該成員的名字和類型。在此有一個特定的默認語法:允許聲明任何Annotation成員的默認值。一個Annotation可以將name=value對作為沒有定義默認值的Annotation成員的值,當然也可以使用name=value對來覆蓋其它成員默認值。這一點有些近似類的繼承特性,父類的構造函數可以作為子類的默認構造函數,但是也可以被子類覆蓋。

上面講了這么多概念,我們來看一個實際的例子:

@Override
public String toString() {
    return "This is String Representation of current object.";
}

上面的代碼中,我重寫了toString()方法並使用了@Override注解。但是,即使我不使用@Override注解標記代碼,程序也能夠正常執行。那么加上與不加上又有什么區別呢?事實上,@Override告訴編譯器這個方法是一個重寫方法(描述方法的元數據),如果父類中不存在該方法,編譯器便會報錯,提示該方法沒有重寫父類中的方法。如果我不小心拼寫錯誤,例如將toString()寫成了toStrring(){double r},而且我也沒有使用@Override注解,那程序依然能編譯運行。但運行結果會和我期望的大不相同。現在我們了解了什么是注解,並且使用注解有助於閱讀程序。也理解了注解實際上就是在源程序上面加上了標簽。

元數據從metadata一詞譯來,就是“關於數據的數據”的意思。在Java中元數據以標簽的形式存在於Java代碼中,元數據標簽的存在並不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或在運行時獲取被運行代碼的描述信息

二、注解的分類(JDK內置系統注解、元注解、自定義注解)

 在學習注解的分類之前,我們先了解下 Annotation和Annotation類型。

Annotation:

  Annotation使用了在java5.0所帶來的新語法,它的行為十分類似public、final這樣的修飾符。每個Annotation具有一個名字和成員個數>=0。每個Annotation的成員具有被稱為name=value對的名字和值(就像javabean一樣),name=value裝載了Annotation的信息。

如:@Override

Annotation類型:

  Annotation類型定義了Annotation的名字、類型、成員默認值。一個Annotation類型可以說是一個特殊的java接口,它的成員變量是受限制的,而聲明Annotation類型時需要使用新語法。當我們通過java反射api訪問Annotation時,返回值將是一個實現了該annotation類型接口的對象,通過訪問這個對象我們能方便的訪問到其Annotation成員。

如:Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解的分類:

根據注解參數的個數,我們可以將注解分為三類:

    1.標記注解:一個沒有成員定義的Annotation類型被稱為標記注解。這種Annotation類型僅使用自身的存在與否來為我們提供信息。比如后面的系統注解@Override;
    2.單值注解
    3.完整注解  

根據注解使用方法和用途,我們可以將Annotation分為三類:

    1.JDK內置系統注解
    2.元注解
    3.自定義注解

1.JDK內置系統注解:

 

注解的語法比較簡單,除了@符號的使用外,他基本與Java固有的語法一致,JavaSE中內置三個標准注解,定義在java.lang中:
    @Override:用於修飾此方法覆蓋了父類的方法;
    @Deprecated:用於修飾已經過時的方法;
    @SuppressWarnnings:用於通知java編譯器禁止特定的編譯警告。

 

下面我們依次看看三個內置標准注解的作用和使用場景。

我們最熟悉的應該是:@Override, 它的定義如下:

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 * The method does override or implement a method declared in a
 * supertype.
 * The method has a signature that is override-equivalent to that of
 * any public method declared in Object.
 *
 * @author  Peter von der Ahé
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

從注釋,我們可以看出,@Override的作用是,提示編譯器,使用了@Override注解的方法必須override父類或者java.lang.Object中的一個同名方法。我們看到@Override的定義中使用到了 @Target, @Retention,它們就是所謂的“元注解”——就是定義注解的注解。我們看下@Retention

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * RetentionPolicy.CLASS.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

@Retention用於提示注解被保留多長時間(簡單點說就是生命周期),有三種取值:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,
    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,
    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
RetentionPolicy.SOURCE 保留在源碼級別,被編譯器拋棄(@Override就是此類); RetentionPolicy.CLASS被編譯器保留在編譯后的類文件級別,但是被虛擬機丟棄;
RetentionPolicy.RUNTIME保留至運行時,可以被反射讀取。
再看 @Target:
package java.lang.annotation;

/**
 * Indicates the contexts in which an annotation type is applicable. The
 * declaration contexts and type contexts in which an annotation type may be
 * applicable are specified in JLS 9.6.4.1, and denoted in source code by enum
 * constants of java.lang.annotation.ElementType
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 9.7.4 Where Annotations May Appear
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}
復制代碼
 @Target用於提示該注解使用的地方,取值有:

復制代碼
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
    /** Field declaration (includes enum constants) */
    FIELD,
    /** Method declaration */
    METHOD,
    /** Formal parameter declaration */
    PARAMETER,
    /** Constructor declaration */
    CONSTRUCTOR,
    /** Local variable declaration */
    LOCAL_VARIABLE,
    /** Annotation type declaration */
    ANNOTATION_TYPE,
    /** Package declaration */
    PACKAGE,
    /**
     * Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     * @since 1.8
     */
    TYPE_USE
}

分別表示該注解可以被使用的地方:1)類,接口,注解,enum; 2)屬性域;3)方法;4)參數;5)構造函數;6)局部變量;7)注解類型;8)包

所以:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

表示 @Override 只能使用在方法上,保留在源碼級別,被編譯器處理,然后拋棄掉。

@Override 是一個標記注解類型,它被用作標注方法。它說明了被標注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種Annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。這個annotaton常常在我們試圖覆蓋父類方法而又寫錯了方法名時發揮威力。使用方法極其簡單:在使用此annotation時只要在被修飾的方法前面加上@Override即可。

@Deprecated,標記已過時:同 樣Deprecated也是一個標記注解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標注的程序元素。而且這種修飾具有一定的 “延續性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋后的類型或者成員並不是被聲明為 @Deprecated,但編譯器仍然要報警。

@SuppressWarnnings,抑制編譯器警告:

@SuppressWarnings 被用於有選擇的關閉編譯器對類、方法、成員變量、變量初始化的警告。在java5.0,sun提供的javac編譯器為我們提供了-Xlint選項來使編譯器對合法的程序代碼提出警告,此種警告從某種程度上代表了程序錯誤。例如當我們使用一個generic collection類而又沒有提供它的類型時,編譯器將提示出"unchecked warning"的警告。通常當這種情況發生時,我們就需要查找引起警告的代碼。如果它真的表示錯誤,我們就需要糾正它。例如如果警告信息表明我們代碼中的switch語句沒有覆蓋所有可能的case,那么我們就應增加一個默認的case來避免這種警告。
有時我們無法避免這種警告,例如,我們使用必須和非generic的舊代碼交互的generic collection類時,我們不能避免這個unchecked warning。此時@SuppressWarning就要派上用場了,在調用的方法前增加@SuppressWarnings修飾,告訴編譯器停止對此方法的警告。

@SuppressWarning不是一個標記注解。它有一個類型為String[]的成員,這個成員的值為被禁止的警告名。對於javac編譯器來講,被-Xlint選項有效的警告名也同樣對@SuppressWarings有效,同時編譯器忽略掉無法識別的警告名。

annotation語法允許在annotation名后跟括號,括號中是使用逗號分割的name=value對用於為annotation的成員賦值。實例如下:

 

package study.javaenhance;

import java.util.ArrayList;
import java.util.List;

public class FruitService {
    @SuppressWarnings(value = { "rawtypes", "unchecked" })
    public static List<String> getFruitList() {
        List<String> fruitList = new ArrayList();
        return fruitList;
    }

    @SuppressWarnings( { "rawtypes", "unchecked" })
    public static List<String> getFruit() {
        List<String> fruitList = new ArrayList();
        return fruitList;
    }

    @SuppressWarnings("unused")
    public static void main(String[] args) {
        List<String> strList = new ArrayList<String>();
    }

}

 

在這個例子中SuppressWarnings annotation類型只定義了一個單一的成員,所以只有一個簡單的value={...}作為name=value對。又由於成員值是一個數組,故使用大括號來聲明數組值。注意:我們可以在下面的情況中縮寫annotation:當annotation只有單一成員,並成員命名為"value="。這時可以省去"value="。比如將上面方法getFruit()的SuppressWarnings annotation就是縮寫的。

SuppressWarnings注解的常見參數值的簡單說明:

    1. deprecation:使用了不贊成使用的類或方法時的警告;
    2. unchecked:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型; 
    3. fallthrough:當switch程序塊直接通往下一種情況而沒有 Break 時的警告;
    4. path:在類路徑、源文件路徑等中有不存在的路徑時的警告; 
    5. serial:當在可序列化的類上缺少serialVersionUID定義時的警告; 
    6. finally:任何finally子句不能正常完成時的警告; 
    7. all:關於以上所有情況的警告。

2.元注解

元注解的作用就是負責注解其他注解(注解的注解)。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)有:

1.CONSTRUCTOR:用於描述構造器
2.FIELD:用於描述域
3.LOCAL_VARIABLE:用於描述局部變量
4.METHOD:用於描述方法
5.PACKAGE:用於描述包
6.PARAMETER:用於描述參數
7.TYPE:用於描述類、接口(包括注解類型) 或enum聲明

@Retention:

@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命周期”限制。

作用:表示需要在什么級別保存該注釋信息,用於描述注解的生命周期(即:被描述的注解在什么范圍內有效)

取值(RetentionPoicy)有:

1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運行時有效(即運行時保留)

@Documented:

@Documented用於描述其它的annotation類型應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記注解,沒有成員。簡單一點說:表示注解是否能被 javadoc 處理並保留在文檔中。

@Inherited:

@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。

注意:@Inherited annotation類型會被標注過的class的子類所繼承。類並不從它所實現的接口繼承annotation,方法並不從它所重載的方法繼承annotation。

4.自定義注解

本小節也是最重要的,我們通過會自己定義注解用於去為源程序打上標簽,比如toString 方法我們想要過濾掉某些敏感信息不打印,那么我們可以加上注解然后識別到這個注解的信息,我們就不去打印這個信息等等用途。

使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義注解時,不能繼承其他的注解或接口(單繼承特性)。@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。

定義注解格式:
  public @interface 注解名 {定義體}

注解參數的可支持數據類型:

    1. 所有基本數據類型(int,float,boolean,byte,double,char,long,short)
    2. String類型
    3. Class類型
    4. enum類型
    5. Annotation類型
    6. 以上所有類型的數組

Annotation類型里面的參數該怎么設定:
  第一, 只能用public或默認(default)這兩個訪問權修飾。例如,String value();這里把方法設為defaul默認類型;   
  第二, 參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和String, Enum, Class, annotations等數據類型,以及這一些類型的數組。例如String value();這里的參數成員就為String;
  第三, 如果只有一個參數成員,最好把參數名稱設為"value",后加小括號。例如下面的例子FruitName注解就只有一個參數成員。

簡單的自定義注解和使用注解實例:

package study.javaenhance;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitName 
{
    String value() default "";
}
package study.javaenhance;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitColor
{
    public enum Color
    {
        BULE,RED,GREEN;
    }
    
    Color fruitColor() default Color.BULE;

}
package study.javaenhance;

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

@Target(ElementType.FIELD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface FruitProvider
{
    /** 
     * 供應商編號 
     * @return 
     */  
    public int id() default -1;  
      
    /** 
     * 供應商名稱 
     * @return 
     */  
    public String name() default "";  
      
    /** 
     * 供應商地址 
     * @return 
     */  
    public String address() default "";  
}

說明:

1.注解元素的默認值:

  注解元素必須有確定的值,要么在定義注解的默認值中指定,要么在使用注解時指定,非基本類型的注解元素的值不可為null。因此,使用空字符串或0作為默認值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個注解的聲明中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數,以表示某個元素不存在,在定義注解時,這已經成為一個習慣用法。

2.注解用處:

定義了注解,並在需要的時候給相關類,類屬性加上注解信息,如果沒有響應的注解信息處理流程,注解可以說是沒有實用價值。如何讓注解真真的發揮作用,主要就在於注解處理方法,下一步我們將學習注解信息的獲取和處理!
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建於使用注解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義注解處理器。

5.注解處理器類庫(java.lang.reflect.AnnotatedElement):

Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:

Class:類定義    AccessibleObject:訪問控制   Constructor:構造器定義  Field:類的成員變量定義  Method:類的方法定義  Package:類的包定義

 

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義為運行時的Annotation后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的如下四個個方法來訪問Annotation信息:

案例如下:

package study.javaenhance;

import java.lang.reflect.Field;

public class FruitInfoUtil 
{
    public static void getFruitInfo(Class<?> clazz) {
        String strFruitName = " 水果名稱:";
        String strFruitColor = " 水果顏色:";
        String strFruitProvicer = "供應商信息:";
        Field[] fileds = clazz.getDeclaredFields();
        for (Field field : fileds) {
            if (field.isAnnotationPresent(FruitName.class)) {
                FruitName fruitName = (FruitName) field
                        .getAnnotation(FruitName.class);
                strFruitName = strFruitName + fruitName.value(); // 得到注解的參數值
                System.out.println(strFruitName);
            } else if (field.isAnnotationPresent(FruitColor.class)) { // 獲取FruitColor注解
                FruitColor fruitColor = (FruitColor) field
                        .getAnnotation(FruitColor.class);
                strFruitColor = strFruitColor
                        + fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
            } else if (field.isAnnotationPresent(FruitProvider.class)) { // 獲取FruitProvider注解
                FruitProvider fruitProvider = (FruitProvider) field
                        .getAnnotation(FruitProvider.class);
                strFruitProvicer = " 供應商編號:" + fruitProvider.id() + " 供應商名稱:"
                        + fruitProvider.name() + " 供應商地址:"
                        + fruitProvider.address();
                System.out.println(strFruitProvicer);
            }
        }
    }

}

三、為什么使用注解?

 在JAVA應用中,我們常遇到一些需要使用模版代碼。例如,為了編寫一個JAX-RPC web service,我們必須提供一對接口和實現作為模版代碼。如果使用annotation對遠程訪問的方法代碼進行修飾的話,這個模版就能夠使用工具自動生成。
另外,一些API需要使用與程序代碼同時維護的附屬文件。例如,JavaBeans需要一個BeanInfo Class與一個Bean同時使用/維護,而EJB則同樣需要一個部署描述符。此時在程序中使用annotation來維護這些附屬文件的信息將十分便利而且減少了錯誤。

四、注解的工作方式和使用方法

在5.0版之前的Java平台已經具有了一些ad hocannotation(即時注解)機制。比如,使用transient修飾符來標識一個成員變量在序列化子系統中應被忽略。而@deprecated這個javadoc tag也是一個ad hocannotation用來說明一個方法已過時。從Java5.0版發布以來,5.0平台提供了一個正式的annotation功能:允許開發者定義、使用自己的annoatation類型。此功能由一個定義annotation類型的語法和一個描述annotation聲明的語法,讀取annotaion的API,一個使用annotation修飾的class文件,一個annotation處理工具(apt)組成。
annotation並不直接影響代碼語義,但是它能夠工作的方式被看作類似程序的工具或者類庫,它會反過來對正在運行的程序語義有所影響。annotation可以從源文件、class文件或者以在運行時反射的多種方式被讀取。
當然annotation在某種程度上使javadoc tag更加完整。一般情況下,如果這個標記對java文檔產生影響或者用於生成java文檔的話,它應該作為一個javadoc tag;否則將作為一個annotation。

Annotation使用方法:

1、類型聲明方式:
  通常,應用程序並不是必須定義annotation類型,但是定義annotation類型並非難事。Annotation類型聲明於一般的接口聲明極為類似,區別只在於它在interface關鍵字前面使用“@”符號。
  annotation類型的每個方法聲明定義了一個annotation類型成員,但方法聲明不必有參數或者異常聲明;方法返回值的類型被限制在以下的范圍:primitives、String、Class、enums、annotation和前面類型的數組;方法可以有默認值。

public @interface RequestForEnhancement {  
    int    id();  
    String synopsis();  
    String engineer() default "[unassigned]";   
    String date();    default "[unimplemented]";   
}  

2、annotation使用時候聲明方式:

annotation是一種修飾符,能夠如其它修飾符(如public、static、final)一般使用。習慣用法是annotaions用在其它的修飾符前面。annotations由“@+annotation類型+帶有括號的成員-值列表”組成。這些成員的值必須是編譯時常量(即在運行時不變)。

@RequestForEnhancement( id= 2868724, synopsis = "Enable time-travel", engineer = "Mr. Peabody",  date     = "4/1/3007" )  
public static void travelThroughTime(Date destination) 
{
    ... 

} 

當聲明一個沒有成員的annotation類型聲明時,可使用以下方式:

/** 
 * Indicates that the specification of the annotated API element 
 * is preliminary and subject to change. 
 */  
public @interface Preliminary { }  

如果在annotations中只有唯一一個成員,則該成員應命名為value:

public @interface Copyright {  
    String value();  
}  

更為方便的是對於具有唯一成員且成員名為value的annotation(如上文),在其使用時可以忽略掉成員名和賦值號(=):

@Copyright("2002 Yoyodyne Propulsion Systems")  
public class OscillationOverthruster { ... }  

下面是一個復雜的Annotataion類型聲明,其成員是Annotation類型的數組:

import java.lang.annotation.*;  
          
/** 
 * Reviews annotation類型只有一個成員, 
 * 由Review annotation組成的數組 
 */  
@Retention(RetentionPolicy.RUNTIME)  
public @interface Reviews {  
    Review[] value();  
}  
  
/** 
 * Review annotation類型有3個成員:  
 * 枚舉類型成員grade 
 * 表示Review名稱的字符串類型成員Reviewer 
 * 具有默認值的字符串類型成員Comment。 
 */  
public @interface Review {  
    // 內嵌的枚舉類型  
    public static enum Grade { EXCELLENT, SATISFACTORY, UNSATISFACTORY };  
  
    // 下面的方法定義了annotation的成員  
    Grade grade();                  
    String reviewer();            
    String comment() default "";    
}  

Reviews annotation類型只有一個成員,但是這個成員的類型是復雜的:由Review annotation組成的數組。Review annotation類型有3個成員:枚舉類型成員grade、表示Review名稱的字符串類型成員Reviewer、具有默認值的字符串類型成員Comment。

Annotation類型的成員不能是generic。只有返回值類型是Class的方法可以在annotation類型中使用generic,因為此方法能夠用類轉換將各種類型轉換為Class。

最后,我們來定義一個annotation方法用於羅列出類運行中所有的unchecked異常(這種情況不一定是錯誤)。這個annotation類型將一個數組作為了唯一的成員。數組中的每個元素都是異常類。為了加強對未檢查的異常(此類異常都是在運行時拋出)進行報告,我們可以在代碼中對異常的類型進行限制:

 

五、注解Annotation實例分析

 結合上面所講的,我們在這里建立一個簡單的基於annotation測試框架。首先我們需要一個annotation類型來表示某個方法是一個應該被測試工具運行的測試方法。

import java.lang.annotation.*;  
  
/** 
 * Indicates that the annotated method is a test method. 
 * This annotation should be used only on parameterless static methods. 
 */  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface Test { }  

值得注意的是annotaion類型聲明是可以標注自己的,這樣的annotation被稱為“meta-annotations”。
在上面的代碼中,@Retention(RetentionPolicy.RUNTIME)這個meta-annotation表示了此類型的annotation將被虛擬機保留使其能夠在運行時通過反射被讀取。而@Target(ElementType.METHOD)表示此類型的annotation只能用於修飾方法聲明。

下面是一個簡單的程序,其中部分方法被上面的annotation所標注:

public class Foo {  
    
@Test 
    public static void m1() { }  
      
    public static void m2() { }  
      
    @Test 
    public static void m3() {  
        throw new RuntimeException("Boom");  
    }  
      
    public static void m4() { }  
      
    @Test
   public static void m5() { }  
      
    public static void m6() { }  
      
    @Test 
    public static void m7() {  
        throw new RuntimeException("Crash");  
    }  
      
    public static void m8() { }  
}

使用測試:

import java.lang.reflect.*;  
  
public class RunTests {  
   public static void main(String[] args) throws Exception {  
      int passed = 0, failed = 0;  
      for (Method m : Class.forName(args[0]).getMethods()) {  
         if (m.isAnnotationPresent(Test.class)) {  
            try {  
               m.invoke(null);  
               passed++;  
            } catch (Throwable ex) {  
               System.out.printf("Test %s failed: %s %n", m, ex.getCause());  
               failed++;  
            }  
         }  
      }  
      System.out.printf("Passed: %d, Failed %d%n", passed, failed);  
   }  
} 

這個程序從命令行參數中取出類名,並且遍歷此類的所有方法,嘗試調用其中被上面的測試annotation類型標注過的方法。在此過程中為了找出哪些方法被annotation類型標注過,需要使用反射的方式執行此查詢。如果在調用方法時拋出異常,此方法被認為已經失敗,並打印一個失敗報告。最后,打印運行通過/失敗的方法數量。

注解在很多框架、類庫中有廣泛的應用。如JUnit測試框架, Spring, Hibernate, EJB等等。這也是開發人員所常常用到的一種方式。

上面的介紹說明了annotation的使用方法、定義方式、分類。初學者可以通過以上的說明制作簡單的annotation程序,但是對於一些高級的annotation應用(例如使用自定義annotation生成javabean映射xml文件)還需要進一步的研究和探討。同時,annotation運行存在兩種方式:運行時、編譯時。上文中討論的都是在運行時的annotation應用,但在編譯時的annotation應用還沒有涉及,因為編譯時的annotation要使用annotation processing tool(APT)
annotation本身使用時十分簡便。例如一個本地變量可以被一個以NonNull命名的annotation類型所標注,來作為對這個本地變量不能被賦予null值的斷言。而我們可以編寫與之配套的一個annotation代碼分析工具,使用它來對具有前面變量的代碼進行解析,並且嘗試驗證這個斷言。當然這些代碼並不必自己編寫。在JDK安裝后,在JDK/bin目錄中可以找到名為“apt”的工具,它提供了處理annotation的框架:它啟動后掃描源代碼中的annotation,並調用我們定義好的annotation處理器完成我們所要完成的工作(比如驗證前面例子中的斷言)。說到這里,annotation的強大功能似乎可以替代XDoclet這類的工具了,隨着我們的深入,大家會更加堅信這一點。
注:詳細描述請參看JSR 250規范 http://www.jcp.org/aboutJava/communityprocess/pfd/jsr250/

七、總結

引用網上的一張圖:

注解總結

 

 

參考資料:

http://blog.csdn.net/zhoudaxia/article/details/33456147

http://blog.csdn.net/zhoudaxia/article/details/33731583


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM