JDK6.0的新特性之六:插入式註解處理API(Pluggable Annotation Processing API)

okone96發表於2007-01-10
作者: 飛翼 發表日期: 2007-01-03 20:10

插入式註解處理API()提供一套標準API來處理Annotations(),實際上JSR 269不僅僅用來處理Annotation,我覺得更強大的功能是它建立了Java 語言本身的一個模型,它把method, package, constructor, type, variable, enum, annotation等Java語言元素對映為Types和Elements(兩者有什麼區別?), 從而將Java語言的語義對映成為物件, 我們可以在javax.lang.model包下面可以看到這些類. 所以我們可以利用JSR 269提供的API來構建一個功能豐富的超程式設計(metaprogramming)環境. JSR 269用Annotation Processor在編譯期間而不是執行期間處理Annotation, Annotation Processor相當於編譯器的一個外掛,所以稱為插入式註解處理.如果Annotation Processor處理Annotation時(執行process方法)產生了新的Java程式碼,編譯器會再呼叫一次Annotation Processor,如果第二次處理還有新程式碼產生,就會接著呼叫Annotation Processor,直到沒有新程式碼產生為止.每執行一次process()方法被稱為一個"round",這樣整個Annotation processing過程可以看作是一個round的序列. JSR 269主要被設計成為針對Tools或者容器的API. 舉個例子,我們想建立一套基於Annotation的單元測試框架(如TestNG),在測試類裡面用Annotation來標識測試期間需要執行的測試方法,如下所示:

@TestMethod
public void testCheckName(){
//do something here
}

這時我們就可以用JSR 269提供的API來處理測試類,根據Annotation提取出需要執行的測試方法.

另一個例子是如果我們出於某種原因需要自行開發一個符合Java EE 5.0的Application Server(當然不建議這樣做),我們就必須處理Common Annotations(),Web Services Metadata()等規範的Annotations,這時可以用JSR 269提供的API來處理這些Annotations. 在現在的開發工具裡面,Eclipse 3.3承諾將支援JSR 269

下面我用程式碼演示如何來用JSR 269提供的API來處理Annotations和讀取Java原始檔的後設資料(metadata)

/**
* Created by IntelliJ IDEA.
* User: Chinajash
* Date: Dec 31, 2006
*/

@SupportedAnnotationTypes("PluggableAPT.ToBeTested")//可以用"*"表示支援所有Annotations@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MyAnnotationProcessor extends AbstractProcessor {
private void note(String msg) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
}
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
//annotations的值是透過@SupportedAnnotationTypes宣告的且目標原始碼擁有的所有Annotations
for(TypeElement te:annotations){
note("annotation:"+te.toString());
}
Set extends Element> elements = roundEnv.getRootElements();//獲取原始碼的對映物件 for(Element e:elements){
//獲取原始碼物件的成員 List extends Element> enclosedElems = e.getEnclosedElements();
//留下方法成員,過濾掉其他成員
List extends ExecutableElement> ees = ElementFilter.methodsIn(enclosedElems);
for(ExecutableElement ee:ees){
note("--ExecutableElement name is "+ee.getSimpleName());
List extends AnnotationMirror> as = ee.getAnnotationMirrors();//獲取方法的Annotations
note("--as="+as);
for(AnnotationMirror am:as){
//獲取Annotation的值
Map extends ExecutableElement, ? extends AnnotationValue> map= am.getElementValues();
Set extends ExecutableElement> ks = map.keySet();
for(ExecutableElement k:ks){//列印Annotation的每個值 AnnotationValue av = map.get(k);
note("----"+ee.getSimpleName()+"."+k.getSimpleName()+"="+av.getValue());
}
}
}
}
return false;
}
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ToBeTested{
String owner() default "Chinajash";
String group();
}

編譯以上程式碼,然後再建立下面的Testing物件,不要編譯Testing物件,我在後面會編譯它

public class Testing{
@ToBeTested(group="A")
public void m1(){
}
@ToBeTested(group="B",owner="QQ")
public void m2(){
}
@PostConstruct//Common Annotation裡面的一個Annotation
public void m3(){
}
}

下面我用以下命令編譯Testing物件

javac -XprintRounds -processor PluggableAPT.MyAnnotationProcessor Testing.java

-XprintRounds表示列印round的次數,執行上面命令後在控制檯會看到如下輸出:

Round 1:
input files: {PluggableAPT.Testing}
annotations: [PluggableAPT.ToBeTested, javax.annotation.PostConstruct]
last round: false
Note: annotation:PluggableAPT.ToBeTested
Note: --ExecutableElement name is m1
Note:
")
Note: ----m1.group=A
Note: --ExecutableElement name is m2
Note:
", owner="QQ")
Note: ----m2.group=B
Note: ----m2.owner=QQ
Note: --ExecutableElement name is m3
Note:

Round 2:
input files: {}
annotations: []
last round: true

本來想用JDK6.0的Compiler API來執行上面編譯命令,可是好像現在Compiler API還不支援-processor引數,執行時總報以下錯誤

Exception in thread "main" java.lang.IllegalArgumentException: invalid flag: -processor PluggableAPT.MyAnnotationProcessor

呼叫Compiler API的程式碼是這樣的

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable extends JavaFileObject> sourcefiles = fileManager.getJavaFileObjects("Testing.java");
Set options = new HashSet();
options.add("-processor PluggableAPT.MyAnnotationProcessor");
compiler.getTask(null, fileManager, null, options, null, sourcefiles).call();

不知道這是不是Compiler API的一個bug.

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/750220/viewspace-889290/,如需轉載,請註明出處,否則將追究法律責任。

相關文章