Java中Annotation用法


  • Annotation

  Annotation其實是代碼里的特殊標記,這些標記可以在編譯類加載運行時被讀取,並執行相應的處理。通過使用Annotation,程序開發人員可以在不改變原有邏輯的情況下,在源文件嵌入一些補充信息。代碼分析工具、開發工具和部署工具可以通過這些補充信息進行驗證或者進行部署。

  Annotation提供了一條為程序元素設置元數據的方法,從某些方面來看,Annotation就像修飾符一樣被使用,可用於修飾包、類、構造器、方法、成員變量、參數、局部變量的聲明,這些信息被存儲在Annotation的“name=value”對中。

  Annotation能被用來為程序元素(類、方法、成員變量等)設置元數據。值得指出的是:Annotation不能影響程序代碼的執行,無論增加、刪除Annotation,代碼都始終如一地執行。如果希望讓程序中的Annotation能在運行時起一定的作用,只有通過某種配套的工具對Annotation中的信息進行訪問的處理,訪問和處理Annotation的工具統稱APT(Annotation Processing Tool)

  •  基本的Annotation

  Annotation必須使用工具來處理,工具負責提取Annotation里包含的元數據,工具還會根據這些元數據增加額外的功能。在系統學習新的Annotation語法之前,先看一下Java提供的三個基本Annotation的用法:使用Annotation時要在其前面增加@符號,並把該Annotation當成一個修飾符使用,用於修飾它支持的程序元素。

  三個基本的Annotation如下:

    1. @Override         限定重寫父類的方法
    2. @Deprecated     標示已過時
    3. @SuppressWarnings     抑制編譯器警告

 

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

/**
 * 動物類
 */
@SuppressWarnings("unchecked") //壓制警告
public class Animal {
    List<String> list = new ArrayList<String>();
    
    /**
     * 動物吃的方法
     */
    public void eat(){
        System.out.println("animal eat method");
    }
}

/**
 * 狗類
 */
class Dog extends Animal{
    /**
     * 規定狗吃的方法繼承自動物,就加上該@Override注解
     */
    @Override
    public void eat(){
        System.out.println("dog eat method");
    }
    
    /**
     * 定義標識該方法已過期,以后不建議使用該方法
     */
    @Deprecated
    public  void go(){
        
    }
}
  •  自定義Annotation

  定義新的Annotation類型使用@interface關鍵字,它用於定義新的Annotation類型。定義一個新的Annotation類型與定義一個接口非常像,如下代碼可定義一個簡單的Annotation

public @interface Login {
    
}

 

   定義了該Annotation之后,就可以在程序任何地方來使用該Annotation,使用Annotation時的語法非常類似於public、final這樣的修飾符。通常可用於修飾程序中的類、方法、變量、接口等定義,通常我們會把Annotation放在所有修飾符之前,而且由於使用Annotation時可能還需要為其成員變量指定值,因而Annotation長度可能比較長,所以通常把Annotation另放一行,如下程序所示:

/**
 * 定義一個Annotation
 */
public @interface Login {
  
}

class LoginTest{
    /**
     * 使用Annotation
     */
    @Login
   public void login(){ } }

  Annotation不僅可以是這種簡單AnnotationAnnotation還可以帶成員變量Annotation成員變量Annotation定義中以無參數方法的形式聲明。其方法名和返回值定義了該成員的名字和類型。如下代碼可以定義一個有成員變量的Annotation:

/**
 * 定義一個注解
 */
public @interface Login {
    //定義兩個成員變量
    String username();
    String password();
}

  一旦在Annotation里定義了成員變量之后,使用該Annotation時應該為該Annotation的成員變量指定值,如下代碼所示:

/**
 * 定義一個注解
 */
public @interface Login {
    //定義兩個成員變量
    String username();
    String password();
}

class LoginTest{
    /**
     * 使用注解
     */
    @Login(username="lisi", password="111111")
    public void login(){
        
    }
}

 

   我們還可以在定義Annotation的成員變量時為其指定初始值,指定成員變量的初始值可使用default關鍵字,如下代碼:

/**
 * 定義一個注解
 */
public @interface Login {
    //定義兩個成員變量
    //以default為兩個成員變量指定初始值
    String username() default "zhangsan";
    String password() default "123456";
}

 

  如果為Annotation的成員變量指定了默認值,使用該Annotation則可以不為這些成員變量指定值,而是直接使用默認值。如下代碼:

/**
 * 定義一個注解
 */
public @interface Login {
    //定義兩個成員變量
    //以default為兩個成員變量指定初始值
    String username() default "zhangsan";
    String password() default "123456";
}

class LoginTest{
    /**
     * 使用注解
   * 因為它的成員變量有默認值,所以可以無須為成員變量指定值,而直接使用默認值
*/ @Login public void login(){ } }

 

  *根據我們介紹的Annotation是否可以包含成員變量,我們可以把Annotation分為如下兩類:

    • 標記Annotation: 一個沒有成員定義的Annotation類型被稱為標記。這種Annotation僅使用自身的存在與否來為我們提供信息。如前面介紹的@Override。
    • 元數據Annotation:那些包含成員變量的Annotation,因為它們可接受更多元數據,所以也被稱為元數據Annotation。        
  •  提取Annotation的信息

  前面已經提到:Java使用Annotation接口來代表程序元素前面的注釋(反射的時候用它來接收注解對象),該接口是所有Annotation類型的父接口。如下圖所示是Annotation接口:

 

  除此之外,Java在java.lang.reflect包下新增了AnnotateElement接口,該接口代表程序中可以接受注釋的程序元素,該接口主要有如下幾個實現類(注意以下是類)

    1. Class:類定義。
    2. Constructor:構造器定義。
    3. Field:類的成員變量定義。
    4. Method:類的方法定義。
    5. Package:類的包定義。

  如圖所示以Method類為例:

  

 

  

 

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

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

    1. getAnnotation(Class<T> annotationClass);  //返回該程序元素上存在的、指定類型的注釋,如果該類型的注釋不存在,則返回null。
    2. Annotation[] getAnnotations();      //返回該程序元素上存在的所有注釋。
    3. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);      //判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false。    

  下面程序片段用於獲取Test類的info方法里的所有注釋,並將這些注釋打印出來:

//@Retention注解指定Login注解可以保留多久
//(元注釋后面講)
@Retention(RetentionPolicy.RUNTIME) //@Target注解指定注解能修飾的目標(只能是方法) @Target(ElementType.METHOD) @interface Login{ String username() default "zhangsan"; String password() default "123456"; } public class Test { public static void main(String[] args) throws Exception{ //1.1通過反射獲取info方法類 Method method = Test.class.getMethod("info"); //2.1判斷該方法上是否存在@Login注釋 boolean annotationPresent = method.isAnnotationPresent(Login.class); if(annotationPresent){ System.out.println("info方法上存在@Login注釋"); }else{ System.out.println("info方法上不存在@Login注釋"); } //3.1獲取方法上的所有注釋 Annotation[] annotations = method.getAnnotations(); for(Annotation a : annotations){ //如果是@Login注釋,則強制轉化,並調用username方法,和password方法。 if(a !=null && a instanceof Login){ String username = ((Login)a).username(); String password = ((Login)a).password(); System.out.println("username:" + username); System.out.println("password:" + password); } System.out.println(a); } } @Login @Deprecated public void info(){} }
  •  使用Annotation的例子

  下面分別介紹兩個使用Annotation的例子,第一個Annotation @Test沒有任何成員變量,僅是一個標記Annotation,它的作用是標記哪些方法是可測試的。

 

//[rɪˈtenʃn]保留
@Retention(RetentionPolicy.RUNTIME)
// [ˈtɑ:gɪt]目標
@Target(ElementType.METHOD)
@interface Test {
    
}

class Junit{
    @Test
    public static void test1(){
        
    }
    
    public static void test2(){
        
    }
    
    public static void test3(){
        
    }
    
    @Test
    public static void test4(){
        
    }

}

public class TestTarget{
    public static void main(String[] args) throws Exception{
        //1.1通過反射獲取類
        Class<?> forName = Class.forName("com.test.annotation.test1.Junit");
        //1.2獲取該類自身聲明的所有方法
        Method[] methods = forName.getDeclaredMethods();
        int checkCount = 0; //測試的數量
        int uncheckCount = 0;  //未測試的數量
        for (Method method : methods) {
            if(method.isAnnotationPresent(Test.class)){
                checkCount++;
            }else{
                uncheckCount++;
            }
        }
        System.out.println("測試的方法有" + checkCount);
        System.out.println("未測試的方法有" + uncheckCount);
    }
}

 

  運行結果如圖所示:

    

  上面程序定義了一個標記Test Annotation,定義該Annotation時使用了@Retention和@Target兩個系統元注釋,其中@Retention注釋指定Test注釋可以保留多久,@Target注釋指定Test注釋能修飾的目標(只能是方法)。正如前面提到的,僅僅使用注釋來標識程序元素對程序是不會有任何影響的,這也是Java注釋的一條重要原則。

  通過這個運行結果可以看出,程序中的@Test起作用了,Junit類里以@Test注釋修飾的方法被正常測試了。

  前面介紹的只是一個標記Annotation,程序通過判斷該Annotation來決定是否運行指定方法,下面程序通過使用元數據Annotation來簡化事件編程,在傳統的事件編程中總是需要通過addActionListener方法來為事件源綁定事件監聽器,本示例中則通過ActionListenerAnno Annotation來為程序中的按鈕綁定監聽器。

//(元注釋后面講)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)  //定義作用在字段上
@Documented
@interface ActionListenerAnno {
    //該listener成員變量用於保存監聽器實現類
    Class<? extends ActionListener> listener();
}

public class TestListener {
    JFrame jf = new JFrame("測試");
    @ActionListenerAnno(listener=OkListener.class)
    private JButton ok = new JButton("確認");
    @ActionListenerAnno(listener=CancelListener.class)
    private JButton cancel = new JButton("取消");
    public void init() throws IllegalArgumentException, IllegalAccessException, InstantiationException{
        JPanel jp = new JPanel();
        jp.add(ok);
        jp.add(cancel);
        jf.add(jp);
        ButtonActionListener.process(this);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }
    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
        new TestListener().init();
    }
}

class OkListener implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("確認按鈕被點擊");
        JOptionPane.showMessageDialog(null, "確認按鈕被點擊");
    }
}

class CancelListener implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("取消按鈕被點擊");
        JOptionPane.showMessageDialog(null, "取消按鈕被點擊");
    }
    
}

class ButtonActionListener{
    public static void process(Object obj) throws IllegalArgumentException, IllegalAccessException, InstantiationException{
        Class<? extends Object> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for(Field f : fields){
            //將指定Field設置成可自由訪問的,避免private的Field不能訪問
            f.setAccessible(true);
            //獲取指定Field的ActionListenerAnno類型的注解
            ActionListenerAnno a = f.getAnnotation(ActionListenerAnno.class);
            // 獲取成員變量f的值
            Object fObj = f.get(obj);
            if(a != null && fObj instanceof AbstractButton){
                // 獲取a注解里的listner元數據(它是一個監聽器類)
                Class<? extends ActionListener> listenerClazz = a.listener();
                // 使用反射來創建listner類的對象
                ActionListener al = listenerClazz.newInstance();
                AbstractButton ab = (AbstractButton)fObj;
                // 為ab按鈕添加事件監聽器
                ab.addActionListener(al);
            }
        }
    }
}

 運行結果:

  單擊如上圖所示窗口的“確定”按鈕,將會彈出“確認按鈕被點擊”的對話框,這表明使用該注釋成功地為 ok、cancel兩個按鈕綁定了事件監聽器。

  • JDK的元Annotation

  JDK除了在java.lang 下提供了3個基本Annotation之外,還在java.lang.annotation包下提供了四個Meta Annotation(元Annotation),這四個Annotation都是用於修飾其他Annotation定義。

  • 使用@Retention

  @Retention只能用於修飾一個Annotation定義,用於指定該Annotation可以保留多長時間,@Retention包含一個RetentionPolicy類型的value成員變量,所以使用@Retention時必須為該value成員變量指定值。

  value成員變量的值只能是如下三個:

    1. RetentionPolicy.CLASS: 編譯器將把注釋記錄在class文件中。當運行Java程序時,JVM不在保留注釋,這是默認值。
    2. RetentionPolicy.RUNTIME: 編譯器將把注釋記錄在class文件中。當運行Java程序時,JVM也會保留注釋,程序可以通過反射獲取該注釋。
    3. RetentionPolicy.SOURCE注解僅存在於源碼中,在class字節碼文件中不包含。
  • 使用@Target

  @Target也是用於修飾一個Annotation定義,它用於指定被修飾Annotation能用於修飾那些程序元素。@Target Annotation也包含一個名為value的成員變量,該成員變量只能是如下幾個:

    1. ElementType.ANNOTATION_TYPE: 指定該策略的Annotation只能修飾Annotation。
    2. ElementType.CONSTRUCTOR:  指定該策略的Annotation能修飾構造器。
    3. ElementType.FIELD:  指定該策略的Annotation只能修飾成員變量。
    4. ElementType.LOCAL_VARIABLE:  指定該策略的Annotation只能修飾局部變量。
    5. ElementType.METHOD: 指定該策略的Annotation只能修飾方法。
    6. ElementType.PACKAGE:  指定該策略的Annotation只能修飾包定義。
    7. ElementType.PARAMETER:  指定該策略的Annotation可以修飾參數。
    8. ElementType.TYPE:  指定該策略的Annotation可以修飾類、接口(包括注釋類型)或枚舉定義。
  • 使用@Documented

  @Documented用於指定該元Annotation修飾的Annotation類將被javadoc工具提取成文檔,如果定義Annotation類時使用了@Documented修飾,則所有使用該Annotation修飾的程序元素的API文檔中將會包含該Annotation說明。

  • 使用@Inherited

  @InheritedAnnotation指定被它修飾的Annotation將具有繼承性:如果某個類使用了A Annotation(定義該Annotation時使用了@Inherited修飾)修飾,則其子類將自動具有A注釋。

 


免責聲明!

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



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