歡迎轉載,但請保留作者鏈接:http://www.lxweimin.com/p/82093e5160ae
閱讀前提:了解反射及類型信息
相關文章 「深入Java」類型信息:RTTI和反射
提供額外的信息與操作手段——這就是“注解”全部的意義。
內置注解
以下三個是java.lang
包下的內置注解:
// 方法注解,表示此注解修飾的方法覆蓋了父類或是接口的方法
// 如果不是這樣,則輸出警告
@Override
// 對于此注解所修飾的對象(類、域、方法等)
// 當你使用了它們時編譯器將輸出“已廢棄”警告
@Deprecated
// 關閉警告,通過給此注解的元素賦值
// 可以關閉特定警告
@SuppressWarnings
元注解
元注解,用來注解注解的注解——有點繞-_-||。
// 定義注解所能作用的目標,說明該注解能作用于何種對象(類、方法、域……之類)。
@Target
// 定義注解保存級別
// 1.源代碼注解,被編譯器丟棄
// 2.類注解,class文件中可用,被VM丟棄
// 3.運行時可用,搭配反射
@Retention
// 標志將此注解包含至javadoc中
@Documented
// 說明假如此注解是類注解而且你在父類中使用此注解,那么子類將會繼承此注解
@Inherited
定義注解
我們來看一下官方例子@Override
的源碼:
@Target(ElementType.METHOD) // 此注解作用于方法
@Retention(RetentionPolicy.SOURCE) // 源代碼級別
public @interface Override {
}
其中,ElementType,RetentionPolicy是enum類型。
定義上看起來有點像interface
,這實在是很奇怪的一點。不過事實上,注解也被會編譯成.class
文件。
兩個元注解@Target
,@Retention
作用于注解@Override
。
對于@Override
來說,從它所作用的目標上考慮,因為是用于檢查方法是否正確覆蓋自父親(或接口),所以作用于方法就好;從它的保存級別上考慮,因為這個注解僅用于提供編譯期檢查,之后就沒有用了,所以應該被編譯器丟棄。
注解持有的元素
至于像@Target(ElementType.METHOD)
這樣的描述是什么意思,我們先看下@Target
的源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType[] value();
看上去有點像interface中的方法定義,但兩者其實沒有任何關聯。在官方叫法上,"value 是屬于 @Target 的元素"。
注意元素支持的類型是有限定的,這一點可以查詢相關資料。而十分有用的一個特性在于,可以將一個注解作為另一個注解的元素。
@Target(ElementType.METHOD)
其實就是在給注解@Target
的元素value
賦值,標準寫法應該是@Target(value = ElementType.METHOD)
,然而這里存在一個隱晦規則:
- 如果注解中定義了
value
元素 - 如果在使用注解時
value
是惟一需要賦值的元素 - 那么只需在括號內給出
value
的值即可
這就是最終簡寫的原因。
可以給元素添加一個默認值像是這樣:
ElementType[] value() default {ElementType.ANNOTATION_TYPE};
這樣假如在使用@Target
時沒有給出value
的值,那么處理@Target
的注解處理器就會使用默認值。
注意,元素不能有不確定的值,即要么存在默認值,要么就在使用注解時給元素賦值。
對于元注解@Target
,如果你希望一個注解可以作用于ElementType中的所有類型,那么就可以不使用@Target
——@Deprecated
就是這樣做的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}
注解不支持繼承
注解不支持繼承,不能用extends
關鍵字來完成“父注解抽象”這樣便利的事。不過好在可以將一個注解作為另一個注解的元素,這可以看作是對"組合"進行了支持。
使用注解提供的信息
我們有兩種方式來使用注解所提供的信息。
第一種是基于反射的注解處理器。
用《Java編程思想》中的例子來說明問題:
- 考慮設計一個注解用來追蹤項目中的用例,如果一個方法實現了某個用例的需求,則給它加上該注解。
- 通過計算完成的用例,可以掌握項目進度
- 如果要更改業務邏輯,開發人員也很容易在代碼中找到與相應用例對應的方法
用例@UseCase
是這樣的:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}
在下面這個類中,有三個方法被注解為用例:
public class PasswordUtils {
@UseCase(id = 47, description =
"Passwords must contain at least one numeric")
public boolean validatePassword(String password) {
return (password.matches("\\\\w*\\\\d\\\\w*"));
}
@UseCase(id = 48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
@UseCase(id = 49, description =
"New passwords can’t equal previously used ones")
public boolean checkForNewPassword(
List<String> prevPasswords, String password) {
return !prevPasswords.contains(password);
}
}
再是一個處理此注解的類:
public class UseCaseTracker {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for(Method m : cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);
if(uc != null) {
System.out.println("Found Use Case:" + uc.id() +
" " + uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for(int i : useCases) {
System.out.println("Warning: Missing use case-" + i);
}
}
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
/* Output:
Found Use Case:47 Passwords must contain at least one numeric
Found Use Case:48 no description
Found Use Case:49 New passwords can’t equal previously used ones
Warning: Missing use case-50
*///:~
第二種是基于apt的注解處理工具。
apt:annotation processing tool
總得來說,實現一個apt工具分兩步,一是實現處理器(實現接口AnnotationProcessor),二是實現返回此處理器的工廠類(實現接口AnnotationProcessorFactory)。
最后運行命令使用它,如:
apt -nocompile -factory com.bjinfotech.practice.annotation.apt.ReviewProcessorFactory ./*.java
這一條命令及以下內容摘自:Java Annotation 高級應用,更多內容敬請查看原文:
- 何謂APT?
根據sun官方的解釋,APT(annotation processing tool)是一個命令行工具,它對源代碼文件進行檢測找出其中的annotation后,使用annotation processors來處理annotation。而annotation processors使用了一套反射API并具備對JSR175規范的支持。
annotation processors處理annotation的基本過程如下:首先,APT運行annotation processors根據提供的源文件中的annotation生成源代碼文件和其它的文件(文件具體內容由annotation processors的編寫者決定),接著APT將生成的源代碼文件和提供的源文件進行編譯生成類文件。
簡單的和前面所講的annotation實例BRFW相比,APT就像一個在編譯時處理annotation的javac。而且從sun開發者的blog中看到,java1.6 beta版中已將APT的功能寫入到了javac中,這樣只要執行帶有特定參數的javac就能達到APT的功能。
為何使用APT?
使用APT主要目的是簡化開發者的工作量,因為APT可以在編譯程序源代碼的同時,生成一些附屬文件(比如源文件、類文件、程序發布描述文字等),這些附屬文件的內容也都是與源代碼相關的。換句話說,使用APT就是代替了傳統的對代碼信息和附屬文件的維護工作。使用過hibernate或者beehive等軟件的朋友可能深有體會。APT可以在編譯生成代碼類的同時將相關的文件寫好,比如在使用beehive時,在代碼中使用annotation聲明了許多struct要用到的配置信息,而在編譯后,這些信息會被APT以struct配置文件的方式存放。如何定義processor?
APT工作過程:
從整個過程來講,首先APT檢測在源代碼文件中哪些annotation存在。然后APT將查找我們編寫的annotation processor factories類,并且要求factories類提供處理源文件中所涉及的annotation的annotation processor。接下來,一個合適的annotation processors將被執行,如果在processors生成源代碼文件時,該文件中含有annotation,則APT將重復上面的過程直到沒有新文件生成。編寫annotation processors:
編寫一個annotation processors需要使用java1.5 lib目錄中的tools.jar提供的以下4個包:
com.sun.mirror.apt: 和APT交互的接口;
com.sun.mirror.declaration: 用于模式化類成員、類方法、類聲明的接口;
com.sun.mirror.type: 用于模式化源代碼中類型的接口;
com.sun.mirror.util: 提供了用于處理類型和聲明的一些工具。
每個processor實現了在com.sun.mirror.apt包中的AnnotationProcessor接口,這個接口有一個名為“process”的方法,該方法是在APT調用processor時將被用到的。一個processor可以處理一種或者多種annotation類型。
一個processor實例被其相應的工廠返回,此工廠為AnnotationProcessorFactory接口的實現。APT將調用工廠類的getProcessorFor方法來獲得processor。在調用過程中,APT將提供給工廠類一個AnnotationProcessorEnvironment 類型的processor環境類對象,在這個環境對象中,processor將找到其執行所需要的每件東西,包括對所操作的程序結構的參考,與APT通訊并合作一同完成新文件的建立和警告/錯誤信息的傳輸。
提供工廠類有兩個方式:通過APT的“-factory”命令行參數提供,或者讓工廠類在APT的發現過程中被自動定位(關于發現過程詳細介紹[請看這里](http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html))。前者對于一個已知的factory來講是一種主動而又簡單的方式;而后者則是需要在jar文件的META-INF/services目錄中提供一個特定的發現路徑:
在包含factory類的jar文件中作以下的操作:在META-INF/services目錄中建立一個名為com.sun.mirror.apt.AnnotationProcessorFactory 的UTF-8編碼文件,在文件中寫入所有要使用到的factory類全名,每個類為一個單獨行。
本篇至此結束。
參考資料
- 《Java編程思想》