1. 程式人生 > >JAVA自定義註解和提取註解資訊

JAVA自定義註解和提取註解資訊

第一節:定義註解

      定義新的Annotation型別使用@interface關鍵字(在原有interface關鍵字前增加@符號)。定義一個新的Annotation型別與定義一個介面很像,例如:

public @interface Test{
}

定義完該Annotation後,就可以在程式中使用該Annotation。使用Annotation,非常類似於public、final這樣的修飾符,通常,會把Annotation另放一行,並且放在所有修飾符之前。例如:

@Test
public class MyClass{
....
}

根據註解是否包含成員變數,可以把註解分為如下兩類:

  • 標記註解:沒有成員變數的Annotation被稱為標記。這種Annotation僅用自身的存在與否來為我們提供資訊,例如@override等。

  • 元資料註解:包含成員變數的Annotation。因為它們可以接受更多的元資料,因此被稱為元資料Annotation。 成員以無引數的方法的形式被宣告,其方法名和返回值定義了該成員變數的名字和型別。

 成員變數

Annotation只有成員變數,沒有方法。Annotation的成員變數在Annotation定義中以“無形參的方法”形式來宣告,其方法名定義了該成員變數的名字,其返回值定義了該成員變數的型別。例如:

public @interface MyTag{
    string name();
    int age();
}

示例中定義了2個成員變數,這2個成員變數以方法的形式來定義。

一旦在Annotation裡定義了成員變數後,使用該Annotation時就應該為該Annotation的成員變數指定值。例如:

public class Test{
    @MyTag(name="紅薯",age=30)
    public void info(){
    ......
    }
}

也可以在定義Annotation的成員變數時,為其指定預設值,指定成員變數預設值使用default關鍵字。示例:

public @interface MyTag{
    string name() default "我蘭";
    int age() default 18;
}

如果Annotation的成員變數已經指定了預設值,使用該Annotation時可以不為這些成員變數指定值,而是直接使用預設值。例如:

public class Test{
    @MyTag
    public void info(){
    ......
    }
}
如果註解只有一個成員變數,則建議取名為value,在使用時可用忽略成員名和賦值符=

註解的語法與定義形式

(1)以@interface關鍵字定義

(2)註解需要標明註解的生命週期,註解的修飾目標等資訊,這些資訊是通過元註解實現。上面的語法不容易理解,下面通過例子來說明一下,這個例子就是Target註解的原始碼,

1 2 3 4 5 6 @Retention(value=RetentionPolicy.RUNTIME) @Target(value={ElementType.ANNOTATION_TYPE}) public@interfaceTarget { ElementType[]value(); }

原始碼分析如下:第一:元註解@Retention,成員value的值為RetentionPolicy.RUNTIME。第二:元註解@Target,成員value是個陣列,用{}形式賦值,值為ElementType.ANNOTATION_TYPE第三:成員名稱為value,型別為ElementType[]另外,需要注意一下,如果成員名稱是value,在賦值過程中可以簡寫。如果成員型別為陣列,但是隻賦值一個元素,則也可以簡寫。如上面的簡寫形式為:@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)

 (3)註解中可以包含列舉

package com.annotation.test;

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 {

    enum Color{RED,YELLOW,WHITE}
    
    Color fruitColor() default Color.RED;
    
}

第二節 提取Annotation資訊

  • 使用註解修飾了類/方法/成員變數等之後,這些註解不會自己生效,必須由這些註解的開發者提供相應的工具來提取並處理註解資訊(當然,只有當定義註解時使用了@Retention(RetentionPolicy.RUNTIME)修飾,JVM才會在裝載class檔案時提取儲存在class檔案中的註解,該註解才會在執行時可見,這樣我們才能夠解析).
  • Java使用Annotation介面來代表程式元素前面的註解,該介面是所有註解的父介面。
  • java5在java.lang.reflect包下新增了 用AnnotatedElement介面代表程式中可以接受註解的程式元素.
  • AnnotatedElement介面的實現類有:Class(類元素)、Field(類的成員變數元素)、Method(類的方法元素)、Package(包元素),每一個實現類代表了一個可以接受註解的程式元素型別。
  • 這樣, 我們只需要獲取到Class、 Method、 Filed等這些實現了AnnotatedElement介面的類的例項,通過該例項物件呼叫該類中的方法(AnnotatedElement介面中抽象方法的重寫) 就可以獲取到我們想要的註解資訊了。

  • 獲得Class類的例項有三種方法

  • (1)利用物件呼叫getClass()方法獲得Class例項

  • (2)利用Class類的靜態的forName()方法,使用類名獲得Class例項

  • (3)運用.class的方式獲得Class例項,如:類名.class

AnnotatedElement介面提供的抽象方法(在該介面的實現類中重寫了這些方法):

1.  <T extends Annotation> T getAnnotation(Class<T> annotationClass)
<T extends Annotation>為泛型引數宣告,表明A的型別只能是Annotation型別或者是Annotation的子類。

功能:返回該程式元素上存在的、指定型別的註解,如果該型別的註解不存在,則返回null

2. Annotation[] getAnnotations()

功能:返回此元素上存在的所有註解,包括沒有顯示定義在該元素上的註解(繼承得到的)。(如果此元素沒有註釋,則返回長度為零的陣列。)

3. <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)

功能:這是Java8新增的方法,該方法返回直接修飾該程式元素、指定型別的註解(忽略繼承的註解)。如果該型別的註解不存在,返回null.

4. Annotation[] getDeclaredAnnotations()

功能:返回直接存在於此元素上的所有註解,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個數組。)

5. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

功能:判斷該程式元素上是否存在指定型別的註解,如果存在則返回true,否則返回false。

6. <T extends Annotation> T[] getAnnotationsByTpye(Class<T> annotationClass)

功能: 因為java8增加了重複註解功能,因此需要使用該方法獲得修飾該程式元素、指定型別的多個註解。

7. <T extends Annotation> T[] getDeclaredAnnotationsByTpye(Class<T> annotationClass)

功能: 因為java8增加了重複註解功能,因此需要使用該方法獲得直接修飾該程式元素、指定型別的多個註解。

Class提供了getMethod()、getField()以及getConstructor()方法(還有其他方法),這些方法分別獲取與方法、域變數以及建構函式相關的資訊,這些方法返回Method、Field 以及Constructor型別的物件。

@Target(ElementType.Method)
@Retention(RetentionPopicy.RUNTIME)
public @interface MyTag
{
     string name() default "yeeku";
     int age() default 32;
}

public class Test
{
    @MyTag
    public void info()
    {

    }
}


獲取Test類的info方法裡的所有註解,並列印這些註解
Annotation [] aArray=Class.forName("Test").getMethod("info").getAnnotations(); //獲取Class例項的方法1
for(Annotation an : aArray)
{
     system.out.println(an);
}

如果需要獲取某個註解裡的元資料則可以將註解強制型別轉換,轉換成所需的註解型別,然後通過註解物件的抽象方法來訪問這些元資料
獲取tt物件的info方法所包含的所有註解
Annotation [] annotation=tt.getClass().getMethod("info").getAnnotations(); //獲取Class例項的方法2
for(Annotation tag : annotation)
{
     //如果tag註解是MyTag型別
     system.out.println("tag.name(): "+((MyTag)tag).name());
     system.out.println("tag.age(): "+((MyTag)tag).age());
}

模擬Junit框架

我們用@Testable標記哪些方法是可測試的, 只有被@Testable修飾的方法才可以被執行.
12345678@Inherited@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Testable { }
如下定義TestCase測試用例定義了6個方法, 其中有4個被@Testable修飾了:
1234567891011121314151617181920212223242526272829303132public class TestCase { @Testablepublic void test1() { System.out.println("test1"); } public void test2() throws IOException { System.out.println("test2"); throw new IOException("我test2出錯啦..."); } @Testablepublic void test3() { System.out.println("test3"); throw new RuntimeException("我test3出錯啦..."); } public void test4() { System.out.println("test4"); } @Testablepublic void test5() { System.out.println("test5"); } @Testablepublic void test6() { System.out.println("test6"); } }
為了讓程式中的這些註解起作用, 必須為這些註解提供一個註解處理工具.
1234567891011121314151617181920212223242526272829public class TestableProcessor { public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException { int passed = 0; int failed = 0; for (Method method : Class.forName(clazz).getMethods()) { if (method.isAnnotationPresent(Testable.class)) {           //獲取Class例項的方法3try { method.invoke(null); ++passed; }catch (IllegalAccessException | InvocationTargetException e) { System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >"); e.printStackTrace(System.out); ++failed; } } } System.out.println("共執行" + (failed + passed) + "個方法, 成功" + passed + "個, 失敗" + failed + "個"); } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { TestableProcessor.process("com.feiqing.annotation.TestCase"); } }

丟擲特定異常

前面介紹的只是一個標記Annotation,程式通過判斷Annotation是否存在來決定是否執行指定方法,現在我們要針對只在丟擲特殊異常時才成功新增支援,這樣就用到了具有成員變數的註解:
123456789@Inherited@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestableException { Class<? extends Throwable>[] value(); }
  • TestCase
1234567891011121314151617181920212223242526272829303132333435public class TestCase { public void test1() { System.out.println("test1"); } @TestableException(ArithmeticException.class) public void test2() throws IOException { int i = 1 / 0; System.out.println(i); } @TestableException(ArithmeticException.class) public void test3() { System.out.println("test3"); throw new RuntimeException("我test3出錯啦..."); } public void test4() { System.out.println("test4"); } @TestableException({ArithmeticException.class, IOException.class}) public void test5() throws FileNotFoundException { FileInputStream stream = new FileInputStream("xxxx"); } @Testablepublic void test6() { System.out.println("test6"); } }
  • 註解處理器
123456789101112131415161718192021222324252627282930313233343536373839public class TestableExceptionProcessor { public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException { int passed = 0; int failed = 0; Object obj = Class.forName(clazz).newInstance(); for (Method method : Class.forName(clazz).getMethods()) { if (method.isAnnotationPresent(TestableException.class)) { try { method.invoke(obj, null); // 沒有丟擲異常(失敗) ++failed; } catch (InvocationTargetException e) { // 獲取異常的引發原因 Throwable cause = e.getCause(); int oldPassed = passed; for (Class excType : method.getAnnotation(TestableException.class).value()) { // 是我們期望的異常型別之一(成功) if (excType.isInstance(cause)) { ++passed; break; } } // 並不是我們期望的異常型別(失敗) if (oldPassed == passed) { ++failed; System.out.printf("Test <%s> failed <%s> %n", method, e); } } } } System.out.println("共執行" + (failed + passed) + "個方法, 成功" + passed + "個, 失敗" + failed + "個"); } public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException { process("com.feiqing.annotation.TestCase"); } }

註解新增監聽器

下面通過使用Annotation簡化事件程式設計, 在傳統的程式碼中總是需要通過addActionListener方法來為事件源繫結事件監聽器:
1234567891011121314151617181920212223242526272829303132333435363738394041424344/** * Created by jifang on 15/12/27. */public class SwingPro { private JFrame mainWin = new JFrame("使用註解繫結事件監聽器"); private JButton ok = new JButton("確定"); private JButton cancel = new JButton("取消"); public void init() { JPanel jp = new JPanel(); // 為兩個按鈕設定監聽事件 ok.addActionListener(new OkListener()); cancel.addActionListener(new CancelListener()); jp.add(ok); jp.add(cancel); mainWin.add(jp); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.pack(); mainWin.setVisible(true); } public static void main(String[] args) { new SwingPro().init(); } } class OkListener implements ActionListener { @Overridepublic void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(null, "你點選了確認按鈕!"); } } class CancelListener implements ActionListener { @Overridepublic void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(null, "你點選了取消按鈕!"); } }
下面我們該用註解繫結監聽器:
  • 首先, 我們需要自定義一個註解
123456789/** * Created by jifang on 15/12/27. */@Inherited@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ActionListenerFor { Class<? extends ActionListener> listener(); }
  • 然後還要一個註解處理器
123456789101112131415161718192021222324/** * Created by jifang on 15/12/27. */public class ActionListenerInstaller { public static void install(Object targetObject) throws IllegalAccessException, InstantiationException { for (Field field : targetObject.getClass().getDeclaredFields()) { // 如果該成員變數被ActionListenerFor標記了 if (field.isAnnotationPresent(ActionListenerFor.class)) { // 設定訪問許可權 field.setAccessible(true); // 獲取到成員變數的值 AbstractButton targetButton = (AbstractButton) field.get(targetObject); // 獲取到註解中的Listener Class<? extends ActionListener> listener = field.getAnnotation(ActionListenerFor.class).listener(); // 新增到成員變數中 targetButton.addActionListener(listener.newInstance()); } } } }
  • 主程式(注意註釋處)
1234567891011121314151617181920212223242526272829303132333435public class SwingPro { private JFrame mainWin = new JFrame("使用註解繫結事件監聽器"); /** * 使用註解設定Listener */@ActionListenerFor(listener = OkListener.class) private JButton ok = new JButton("確定"); @ActionListenerFor(listener = CancelListener.class) private JButton cancel = new JButton("取消"); public SwingPro init() { JPanel jp = new JPanel(); // 使得註解生效 try { ActionListenerInstaller.install(this); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(System.out); } jp.add(ok); jp.add(cancel); mainWin.add(jp); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.pack(); mainWin.setVisible(true); return this; } //下同 }