JDK5.0中注釋(Annotation)的用法
很多
API
都需要相當數量的樣板代碼,比如,為了編寫一個
JAX-RPC
的
WEB
服務,你需要提供一個接口和一個實現類。如果這個程序已經被加了注釋
Annotations
以說明那個方法需要被遠程調用,那么我們可以一個工具去自動生成這些樣板代碼。
還有一些
API
需要在程序代碼另外維護一些文件,比如
JavaBean
需要一個
BeanInfo
類,
EJB
需要一個部署描述文件。如果我們能夠把這些需要另外維護的文件內容以注釋
Annotation
的方式和代碼放在一起維護,一定會更加方便同時也減少出錯的機會。
Java
平臺已經有了一些特別的注釋的機制。比如
transient
修飾符就是一個特別的注釋,表明這個字段應該被序列化子系統忽略;
@deprecated javadoc
標簽是一個特別的標簽來說明某個方法已經不再被使用了。
JDK5.0
提供了一個讓我們自己定義我們自己注釋的功能,這個功能包含了如何定義注釋類型的語法,聲明注釋的語法,讀取注釋的
API
,一個類文件保存注釋(譯者注:注釋可以被看作一個類,我們需要用一個
.java
文件保存我們自己定義的注釋源碼)和一個注釋處理的工具。
注釋并不影響代碼的語義,但卻影響用于處理包含有注釋的程序代碼的工具的處理方式,使他們(工具)能夠影響運行狀態的程序的語義。注釋可以從源代碼中讀取,從編譯后的
.class
文件中讀取,也可以通過反射機制在運行時讀取。
注釋是
JavaDoc
標簽的補充。一般情況下,如果我們的主要目標是影響或者產生文檔,那么我們應該使用
JavaDoc
;否則,我們應該使用注釋
Annotations
。
一般的應用程序開發人員可能從不需要定義一個注釋類型,但定義我們自己的注釋類型并不復雜。注釋類型的定義跟定義一個接口相似,我們需要在 interface 這個關鍵字前面加上一個 @ 符號。注釋中的每一個方法定義了這個注釋類型的一個元素,注釋中方法的聲明中一定不能包含參數,也不能拋出異常;方法的返回值被限制為簡單類型、 String 、 Class 、 emnus 、注釋,和這些類型的數組。方法可以有一個缺省值。這里是一個注釋類型定義的例子:
/**
* Describes the Request-For-Enhancement(RFE) that led
* to the presence of the annotated API element.
*/
public @interface RequestForEnhancement {
??? int??? id();
??? String synopsis();
??? String engineer() default '[unassigned]';
??? String date();??? default '[unimplemented]';
}
一旦定義好了一個注釋類型,你就可以用來作注釋聲明。注釋一中特殊的修飾符,在其他修飾符(比如
public
,
static
,或者
final
等)使用地方都可以使用。按照慣例,注釋應該放在其他修飾符的前面。注釋的聲明用
@
符號后面跟上這個注釋類型的名字,再后面跟上括號,括號中列出這個注釋中元素
/
方法的
key
-
value
對。值必須是常量。這里是一個例子,使用上面定義的注釋類型:
@RequestForEnhancement( ??? id?????? = 2868724, ??? synopsis = 'Enable time-travel', ??? engineer = 'Mr. Peabody', ??? date???? = '4/1/3007'
)
public static void travelThroughTime(Date destination) { ... }
沒有元素 / 方法的注釋被成為標記( marker )注釋類型,例如
/**
* Indicates that the specification of the annotated API element * is preliminary and subject to change. */
public @interface Preliminary { }
?
標記注釋在使用的時候,其后面的括號可以省略,例如:@Preliminary public class TimeTravel { ... }
如果注釋中僅包含一個元素,這個元素的名字應該為 value ,例如: /** * Associates a copyright notice with the annotated API element. */ public @interface Copyright { String value(); }
?
如果元素的名字為 value ,使用這個注釋的時候,元素的名字和等號可以省略,如:
@Copyright('2002 Yoyodyne Propulsion Systems') public class OscillationOverthruster { ... }
為了將上面提到的東西結合在一起,我們創建了一個簡單的基于注釋的測試框架。首先我們需要一個標記注釋類型用以說明一個方法是一個測試方法,并被測試工具執行。 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 { }
?
我們可以注意到這個注釋類型本省也被注釋了,這種注釋叫做元注釋。第一注釋
(@Retention(RetentionPolicy.RUNTIME))
表示這種類型的注釋被
VM
保留從而使其能夠通過反射在運行時讀取;第二個注釋
@Target(ElementType.METHOD)
表示這種注釋只能用來注釋方法。
?
下面是一個簡單的類,其中的幾個方法被加了上面的注釋:
?
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);
?? }
}
?
這個工具用一個類名作為參數,遍歷這個類中的所有方法,并調用其中被加了
@Test
注釋的方法。如果一個方法拋出了一個異常,那么這個測試就失敗了,最終的測試結果被打印了出來。下面是程序運行的結果:
?
$ java RunTests Foo
Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
Passed: 2, Failed 2
?
?
雖然這個測試工具只是一個玩具,但他顯示了注釋的強大的功能。
?