原文請看摸我
?這是有關(guān)注解代碼生成技術(shù)系列博文的第二部分。在第一部分(摸我)中,我們介紹了注解的基本概念與用法。
?在本篇博文中我們將介紹注解處理器的基本概念和使用運(yùn)行方法。
介紹
注解功能強(qiáng)大。你可以使用注解來設(shè)置各類元數(shù)據(jù)或者配置信息,語法格式優(yōu)雅并且功能強(qiáng)大。
?從目前我們了解的知識來看,注解比起Javadoc
來有很多優(yōu)勢,但是這些好像都不足以委員會(huì)將其加入java語言之中。那么,我們可以更好的利用和了解注解嗎?當(dāng)然可以啦:
- 在運(yùn)行時(shí)刻,帶有
runtime retention policy
的注解可以通過反射獲得,Class
類中的getAnnotation()
和getAnnotations()
方法可以做到這些。 - 在編譯時(shí)刻,注解處理器可以處理在編譯時(shí)發(fā)現(xiàn)的各類注解。
注解處理器API
當(dāng)注解在Java 5 首次被引入時(shí),注解處理器API還不是很成熟和規(guī)范化。處理注解需要一個(gè)單獨(dú)的工具,叫做apt
,注解處理工具;還需要Mirror
API(com.sun.mirror),用于編寫自定義處理器。
?從java 6開始,注解處理通過JSR 269(2)
被標(biāo)準(zhǔn)化,被集成進(jìn)標(biāo)準(zhǔn)庫并且apt
無縫的集成到j(luò)ava編譯器javac中。
?因?yàn)槲覀冎辉敿?xì)講述Java 6 中的注解處理器相關(guān)API,你可以在這里和這里找到關(guān)于java 5中注解的更多信息,并在這里找到一些例子。
?自定義注解處理器只是實(shí)現(xiàn)了javax.annotation.processing.Processor
接口并準(zhǔn)從指定的協(xié)議。為了我們的便利,自定義處理器一些常用的功能都由javax.annotation.processing.AbstractProcessor
這個(gè)類給出了抽象實(shí)現(xiàn)。
?自定義注解處理器可以使用到一下三個(gè)注解來配置自己:
-
javax.annotation.processing.SupportedAnnotationTypes:這個(gè)注解用來注冊注解處理器要處理的注解類型。有效值為完全限定名(就是帶所在包名和路徑的類全名)-通配符(此次英語原文為Wildcards,就是?這個(gè)符號代表的類型。比如說
List<? extends String
,想要深入了解,可以看一下這里)也可以。 - javax.annotation.processing.SupportedSourceVersion:這是用來注冊注解處理器要處理的源代碼版本。
-
javax.annotation.processing.SupportedOptions:這個(gè)注解用來注冊可能通過命令行傳遞給處理器的操作選項(xiàng)。
(譯者語:對于android注解處理器,第一個(gè)注解比較有用,另外兩個(gè)了解就可)
?最后,我們提供process()
方法的實(shí)現(xiàn)。
實(shí)現(xiàn)我們第一個(gè)注解處理器
讓我們開始第一個(gè)注解處理器的實(shí)現(xiàn)。按照之前章節(jié)的知識,我們實(shí)現(xiàn)了下面這個(gè)類來處理第一篇博文中的Complexity
注解:
package sdc.assets.annotations.processors;
import …
@SupportedAnnotationTypes("sdc.assets.annotations.Complexity")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ComplexityProcessor extends AbstractProcessor {
public ComplexityProcessor() {
super();
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
return true;
}
}
這個(gè)未完全完成的類,盡管沒有任何操作,但是注冊可以支持處理sdc.assets.annotations.Complexity
注解類型。因此,每次java編譯器遇到一個(gè)被Complexity
標(biāo)記的類都有執(zhí)行這個(gè)處理器,假設(shè)這個(gè)處理器在那個(gè)路徑中可以被獲得(具體原由之后會(huì)看到)。
?process()
方法會(huì)受到兩個(gè)輸入?yún)?shù):
- Set<? extends TypeElement>:注解處理需要執(zhí)行一次或者多次。每次執(zhí)行時(shí),處理器方法被調(diào)用,并且傳入了當(dāng)前要處理的注解類型。
-
RoundEnvironment:這個(gè)對象提供當(dāng)前或者上一次注解處理中被注解標(biāo)注的源文件元素。(簡單點(diǎn)說,就是可以獲得所有被標(biāo)注的元素,無論是類,參數(shù),函數(shù)還是變量)
?除了上述兩個(gè)參數(shù)之外,ProcessingEnvironment
對象也可以通過processingEnv
實(shí)例獲得。這個(gè)對象可以提供一些關(guān)于日志,文件讀寫的通用工具類;它的一些功能之后會(huì)討論到。
?使用RoundEnvironment
對象和Element
接口的反射相關(guān)的函數(shù),我們可以實(shí)現(xiàn)一個(gè)打印Complexity
標(biāo)注元素名的注解處理器。
for (Element elem :roundEnv.getElementsAnnotatedWith(Complexity.class)) {
Complexity complexity = elem.getAnnotation(Complexity.class);
String message = "annotation found in " + elem.getSimpleName() + " with complexity " + complexity.value(); processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
return true; // no further processing of this annotation type
}
打包并且注冊注解處理器
自定義注解處理器的最后一步就是打包并且向java編譯器獲取其他可以識別處理器的工具進(jìn)行注冊。
注冊處理器的最簡單方法就是使用標(biāo)注java服務(wù)機(jī)制:
- 把你的注解處理器打包到j(luò)ar文件中。
- 把jar文件加入到META-INF/services目錄。
- 把javax.annotation.processing.Processor文件加入到目錄。
- 把處理器的全限定名寫入一個(gè)文件中,一個(gè)處理器名一行。
java編譯器或者其他工具會(huì)搜索這個(gè)文件中標(biāo)記的所有處理器,并且在注解處理過程中使用。
?在我們這個(gè)例子中,目錄結(jié)構(gòu)和文件內(nèi)容如下:
?一旦打包完成,我們就準(zhǔn)備使用它。
(譯者語:關(guān)于android平臺(tái)的自動(dòng)打包和使用,可以參考butterknife的方法,就是使用AutoService,這個(gè)詳細(xì)知識之后我會(huì)單獨(dú)博文講解)
使用javac運(yùn)行處理器
想象一下你在一個(gè)項(xiàng)目中使用了一些自定義注解并且可以使用注解處理器。在java 5中,編譯和注解處理是不同的兩步,但是在java 6中,兩個(gè)任務(wù)都集成到j(luò)ava編譯器工具javac中。
?如果你把注解處理器加入到j(luò)avac的路徑中并且他們使用服務(wù)機(jī)制進(jìn)行了注冊,他們就會(huì)被javac執(zhí)行調(diào)用啦。
?在我們這個(gè)例子中,下邊這個(gè)命令會(huì)編譯并且執(zhí)行注解處理:
>javac -cp sdc.assets.annotations-1.0-SNAPSHOT.jar;
sdc.assets.annotations.processors-1.0-SNAPSHOT.jar
SimpleAnnotationsTest.java
用于處理的java類文件內(nèi)容如下:
package sdc.startupassets.annotations.base.client;
import ...
@Complexity(ComplexityLevel.VERY_SIMPLE)
public class SimpleAnnotationsTest {
public SimpleAnnotationsTest()
{
super();
}
@Complexity() // this annotation type applies also to methods // the default value 'ComplexityLevel.MEDIUM' is assumed
public void theMethod()
{
System.out.println("consoleut");
}
}
上述執(zhí)行結(jié)果如下:
有一些javac的選項(xiàng)可以在一些特殊的情況下使用:
- -Akey[=value]:用來傳遞選項(xiàng)值給處理器,處理器只會(huì)接收在通過
SupportedOptions
注解注冊的選項(xiàng)。 - -proc:{none|only}:默認(rèn)情況下,javac會(huì)運(yùn)行注解處理并且編譯源碼。使用
proc:none
選項(xiàng),將不執(zhí)行注解處理;使用proc:only
選項(xiàng)將只執(zhí)行注解處理過程-當(dāng)你在注解處理器中運(yùn)行驗(yàn)證或者質(zhì)量檢查工具或者代碼審查工具時(shí)。 - -processorpath path:用來確定注解處理器和它的依賴的位置
- -s dir:用來確定通過注解處理生成的源代碼放置在哪個(gè)文件夾中。這個(gè)目錄在執(zhí)行命令行之前必須存在。
- -processor class1[,class2,class3…]:用來給出將要執(zhí)行的注解處理器全限定名,當(dāng)使用這個(gè)選項(xiàng)時(shí),默認(rèn)的通過服務(wù)機(jī)制尋找到的注解處理器將被替換,直接使用命令行給出的處理器進(jìn)行處理。
(譯者語:下邊兩個(gè)章節(jié)因?yàn)楹椭鞲蓛?nèi)容沒有太大關(guān)系,所以沒有翻譯,請感興趣的朋友自行查閱)