需求描述
開發一個掃描所有注解信息的腳本程序,希望在編譯期對代碼進行掃描,如果注解書寫不規范則使編譯失敗。
本次的需求剛好用AbstractProcessor可以滿足,在這次需求中我學習到了注解處理器的開發,並且踩了一些坑,在這里記錄下來,希望能夠幫助其他人在開發的時候避免。
AbstractProcessor
簡介
我們在網上搜索到的AbstractProcessor,大多用於Android開發時用到,但實際上注解處理器在我們的工作中仍然也能起到很大的作用。
注解的處理除了可以在運行時通過反射機制處理外,還可以在編譯期進行處理。在編譯期處理注解時,會處理到不再產生新的源文件為止,之后再對所有源文件進行編譯。
Java5中提供了apt工具來進行編譯期的注解處理。apt是命令行工具,與之配套的是一套描述“程序在編譯時刻的靜態結構”的API:Mirror API(com.sun.mirror.*)。通過Mirror API可以獲取到被注解的Java類型元素的信息,從而提供自定義的處理邏輯。具體的處理工具交給apt來處理。編寫注解處理器的核心是兩個類:注解處理器(com.sun.mirror.apt.AnnotationProcessor)、注解處理器工廠(com.sun.mirror.apt.AnnotationProcessorFactory)。apt工具在完成注解處理后,會自動調用javac來編譯處理完成后的源代碼。然而,apt工具是oracle提供的私有實現(在JDK開發包的類庫中是不存在的)。在 Java8中,已經移除了 APT 工具;在JDK6中,將注解處理器這一功能進行了規范化,形成了java.annotation.processing的API包,Mirror API則進行封裝,形成javax.lang.model包。注解處理器的開發進行了簡化,不再單獨使用apt工具,而將此功能集成到了javac命令中。(當前開發使用的JDK版本一般都在6以上,故對apt工具不做研究)。
簡單來講,就是 我們可以通過使用 AbstractProcessor 可以在編譯期處理所有指定的注解。
AbstractProcessor,是一個抽象類,該類實現了接口Processor。處理接口提供了一個核心處理方法process(),用於開發者實現自己的處理邏輯(用於處理先前round中產生的注解)。
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
process()方法有一個boolean類型的返回值,若返回false,表示本輪注解未聲明並且可能要求后續其它的Processor處理它們;若返回true,則代表這些注解已經聲明並且不要求后續Processor來處理它們。
開發
我們暫時不需要考慮其他方法的實現,這些操作我們也不需要,我們只需要指定需要處理的注解,指定編譯的Java版本,並且實現核心邏輯process()方法就可以了。
@SupportedAnnotationTypes({"XXX"})//指定注解的類型
@SupportedSourceVersion(SourceVersion.RELEASE_8)//指定支持的Java版本,這里是Java8
public class VUAnnotationProcessor extends AbstractProcessor {
// annotations為要求處理的注解類型
// roundEnv 中包含了當前和上一輪的環境信息,從中我們可以獲取掃描出來的注解實例
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//在這里書寫處理邏輯
return true;
}
}
聲明
這些開發完了之后,我們需要在resources目錄下創建META-INF/services目錄,在其下創建javax.annotation.processing.Processor
文件,並在其中填寫我們自定義的注解處理器的全類名。
坑:服務配置文件不正確
我們在開發完進行編譯,很快會在控制台發現這行錯誤
錯誤: 服務配置文件不正確, 或構造處理程序對象javax.annotation.processing.Processor: XXX could not be instantiated: java.lang.NoClassDefFoundError: XXX 時拋出異常錯誤
為什么呢?因為注解處理器在啟動的時候,注解處理器本身還沒有編譯,所以找不到。WTF。
那么,同學們,怎么才能解決這個“雞生蛋,蛋生雞”的問題呢?
第一,把這個類拆到單獨的Jar包里面,不要讓它干擾到需要掃描的項目里。
第二,在這個單獨的項目里,引入聲明,表示,這個項目不要用注解處理器掃描。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
這樣才能讓這個類編譯成功,並且讓它在其他項目編譯的時候生效。