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
修飾了:
1234567891011121314151617181920212223242526272829303132 | public class TestCase
{ @Testable public void test1()
{ System.out.println( "test1" ); } public void test2() throws IOException
{ System.out.println( "test2" ); throw new IOException( "我test2出錯啦..." ); } @Testable public void test3()
{ System.out.println( "test3" ); throw new RuntimeException( "我test3出錯啦..." ); } public void test4()
{ System.out.println( "test4" ); } @Testable public void test5()
{ System.out.println( "test5" ); } @Testable public void test6()
{ System.out.println( "test6" ); } } |
為了讓程式中的這些註解起作用, 必須為這些註解提供一個註解處理工具.
1234567891011121314151617181920212223242526272829 | public 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例項的方法3 try { 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
1234567891011121314151617181920212223242526272829303132333435 | public 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" ); } @Testable public void test6()
{ System.out.println( "test6" ); } } |
- 註解處理器
123456789101112131415161718192021222324252627282930313233343536373839 | public 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
{ @Override public void actionPerformed(ActionEvent
e) { JOptionPane.showMessageDialog( null , "你點選了確認按鈕!" ); } } class CancelListener implements ActionListener
{ @Override public 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()); } } } } |
- 主程式(注意註釋處)
1234567891011121314151617181920212223242526272829303132333435 | public 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 ; } //下同 } |