寫在前面html
該文章是繼Java註解解析-基礎+運行時註解(RUNTIME)以後,使用註解處理器處理CLASS
註解的文章。經過完整的Demo例子介紹整個註解處理器的搭建流程以及注意事項,你將知道如何去搭建本身的註解處理器。前提是你知道如何去寫自定義註解,不清楚的能夠點擊我上面的連接哦~java
介紹android
顧名思義註解處理器就是javac
包中專門用來處理註解的工具。全部的註解處理器都必須繼承抽象類AbstractProcessor
而後重寫它的幾個方法。註解處理器是運行在它本身的JVM中。javac
啓動一個完整Java虛擬機來運行註解處理器,這意味着你可使用任何你在其餘java
應用中使用的的東西。其中抽象方法process
是必需要重寫的,再該方法中註解處理器能夠遍歷全部的源文件,而後經過RoundEnvironment
類獲取咱們須要處理的註解所標註的全部的元素,這裏的元素能夠表明包,類,接口,方法,屬性等,具體的解釋放在下一章節,由於這裏面牽扯到的東西實在是太多了。再處理的過程當中能夠利用特定的工具類自動生成特定的.java文件或者.class文件,來幫助咱們處理自定義註解。git
下面開始搭建github
首先註解處理器須要javax包的支持,咱們的Android環境下是訪問不到javax包的,除非咱們本身配置。json
//app:build.gradle中加入依賴,必定要使用provided files來引入.
provided files('/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/jre/lib/rt.jar') 複製代碼
因此咱們須要建立Java Library包來提供javax環境,另外註解處理器要被打包進jar包裏面才能被系統識別,這就是選用ava Library的緣由,目前註解註解框架均是如此。api
建立好Module以後就能夠寫咱們的自定義的註解處理器了。首先須要繼承抽象類AbstractProcessor
,而後重寫process()
方法。該方法是核心方法,該方法將遍歷源代碼,找出咱們想要註解標註的元素。單單重寫這一個方法是不夠的, 在咱們的開發中每每須要重寫init()
,getSupportedSourceVersion()
,getSupportedAnnotationTypes()
這幾個方法就能夠了。另外再Java7及其之後咱們還可使用註解@SupportedSourceVersion()
和@SupportedAnnotationTypes()
去替代上面的方法,見於該註解有Java版本的限制,因此仍是建議直接重寫方法爲好。app
public class AnnotationTwoProcessor extends AbstractProcessor {
private Messager messager; //用於打印日誌
private Elements elementUtils; //用於處理元素
private Filer filer; //用來建立java文件或者class文件
//該方法再編譯期間會被注入一個ProcessingEnvironment對象,該對象包含了不少有用的工具類。
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
}
/** * 該方法將一輪一輪的遍歷源代碼 * @param set 該方法須要處理的註解類型 * @param roundEnvironment 關於一輪遍歷中提供給咱們調用的信息. * @return 改輪註解是否處理完成 true 下輪或者其餘的註解處理器將不會接收到次類型的註解.用處不大. */
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
/** * 返回咱們Java的版本. * @return */
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
/** * 返回咱們將要處理的註解 * @return */
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<>();
annotataions.add(MyAnnotion.class.getCanonicalName());
return annotataions;
}
}
複製代碼
註冊註解處理器的方法有兩種:框架
第一種: 處理器必須被打包進一個jar包裏面,這也是爲何要創建一個Java Module。該jar包須要有一個特定路徑resources/META-INF/services
的文件javax.annotation.processing.Processor
,該文件的路徑和名稱都是特定的,而後將咱們聲明註解處理器的全路徑寫到該文件中,這樣Java虛擬機會自動找該路徑下中咱們聲明的處理器。ide
咱們再建立文件夾的時候必定要肯定其命名的準確性。建立文件的時候直接右鍵->new file,保證咱們的文件的名字爲javax.annotation.processing.Processor
。
這樣問題來了,如我咱們寫了多個註解處理器該怎麼聲明呢?接着看。若是咱們一個Module裏面聲明瞭多個註解處理器,再該文件中聲明的時候每一個註解處理器要換行聲明,運行的順序就按聲明的順序去執行。這裏須要對註解處理器的運行順序解釋一下,再編譯期間並非一個註解處理器徹底的處理結束再開始下一個的,而是在掃描一輪源代碼 的時候註冊的第一個處理器先執行一輪,而後註冊的第二個處理器開始執行而後。。。三個。。四個。第二輪的時候仍是註冊的第一個處理器先執行,而後第二個。。。三個。。。這裏面的深入解釋會在下一篇講解,這篇只是使用。
第二種: 當以爲第一種方法配置繁瑣的時候就會有新的更簡潔的方式出現。谷歌公司推出的使用註解去註冊註解處理器。
添加依賴,能夠去GitHub上面查找最新版本。
implementation 'com.google.auto.service:auto-service:1.0-rc4'
複製代碼
咱們就可使用了,它的做用和咱們手寫的做用是同樣的。不過註釋的處理器的處理順序跟你類建立的順序是一致的,跟方法一中文件聲明的順序不同。
@AutoService(Processor.class)
public class AnnotationTwoProcessor extends AbstractProcessor {
}
複製代碼
總的來講方式1的註冊方式目前僅在EventBus裏面有用到,方式2仍是推薦使用的,好比:Arouter,BufferKnife等流行框架都是採用的這種方式註冊的,方便快捷。
再引用註解處理器的Module的時候坑其實挺多的。首先咱們得確保處理器所在的jar包會添加到咱們運行的項目中,註解處理器纔會被獲得執行,這裏呢由於源碼不清楚,因此只好本身去試了。Application引入註解處理器包的時候並不像咱們引入其它的Android Module同樣,這裏列舉三種種引入方法。
plugin: 'com.android.application'
咱們可使用implementation,compile,api直接引用註解處理器的module,可是會有一個編譯錯誤:
Error:Execution failed for task ':app:javaPreCompileDebug'.
> Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor. Please add them to the annotationProcessor configuration.
- annotation_processor.jar (project :annotation_processor)
Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior. Note that this option is deprecated and will be removed in the future.
See https://developer.android.com/r/tools/annotation-processor-error-message.html for more details.
複製代碼
咱們須要加一段代碼,代碼位置以下所示,這樣就能夠愉快的引入註解處理器了:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
}
}
}
}
複製代碼
另外咱們能夠用annotationProcessor引入註解處理器,這個引入方式是爲了替換Gradle版本2.2以前的apt引入方法。該引入方式專門用來處理註解處理器。使用該引入方式的時候不會出現錯誤提示,也是Android中推薦使用的引入方法,該方式也是主流方式。
annotationProcessor project(':annotation_processor') 複製代碼
apply plugin: 'com.android.library'
前提module必定要被android application引入。module裏面引入註解處理器的module的話,基本跟android application中一致,這裏說一下兩個不一樣點annotationProcessor和implementation方式的引入都不會執行註解處理器,真實的原理並不清楚,只能猜想是application才能正真的處理註解,因此得把依賴暴露出去。這點再android library的module中必定得注意。不太建議該方式引入。
apply plugin: 'java-library'
前提java library必定要被android application引入。聲明這樣的module 的話咱們就能夠直接引入註解處理器了,也不用擔憂用什麼方式引入。不過這種場景不太多。
如何確保你的註解處理器已經註冊成功了。首先你已經自定義好了一個註解,而且該註解已經用到了你的代碼裏面。以下代碼所示,你已經設置了咱們要處理的是那種類型的註解(代碼1所示),而後再咱們的process方法裏面打上日誌(代碼2所示),而後點擊Android Studio
的Make Project
按鈕,以後打開Gradle Console窗口看build信息,當你發現信息中打印了代碼2所示的信息以後就代表你的註解處理器已經運行起來了。若是沒有打印信息的話嘗試 clean一下項目而後從新Make Project。若是發現沒有打印日誌的話,嘗試查看註解處理器是否已經註冊和註解處理器所在的module是否被android application成功引入。
@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//代碼2
messager.printMessage(Diagnostic.Kind.NOTE,"日誌開始---------------");
return false;
}
//代碼1
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedOptions = new HashSet<>();
supportedOptions.add(MyAnnotion.class.getCanonicalName());
return supportedOptions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
複製代碼
走到該步驟的時候,你要確保你的註解處理器已經正常的運行。咱們使用註解處理器處理源碼註解的目的,就是再編譯期間完成咱們對某個註解的處理工做。好比:BufferKnife再編譯期間會在使用特定註解的文件路徑生成***—ViewBinding的源文件去處理特定註解。這裏用一個Demo去演示如何生成代碼:
先看個人註解:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.LOCAL_VARIABLE})
public @interface MyAnnotion {
String value() default "ssssss";
}
複製代碼
在個人MainActivity上面使用註解:
@MyAnnotion()
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
複製代碼
接着使用註解處理器去處理註解,生成對應的MainActivity_ViewBinding源文件。
public class AnnotationProcessor extends AbstractProcessor {
private Messager messager;
private Elements elementUtils;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE,"日誌開始---------------");
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MyAnnotion.class);
for (Element element:elementsAnnotatedWith) {
if(element.getKind() == ElementKind.CLASS){
TypeElement typeElement = (TypeElement) element;
PackageElement packageElement = elementUtils.getPackageOf(element);
String packagePath = packageElement.getQualifiedName().toString();
String className = typeElement.getSimpleName().toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(packagePath + "." + className + "_ViewBinding", typeElement);
Writer writer = sourceFile.openWriter();
writer.write("package "+packagePath +";\n");
writer.write("import "+packagePath+"."+className+";\n");
writer.write("public class "+className+"_ViewBinding"+" { \n");
writer.write("\n");
writer.append(" public "+className +" targe;\n");
writer.write("\n");
writer.append("}");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
messager.printMessage(Diagnostic.Kind.NOTE,"日誌結束---------------");
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedOptions = new HashSet<>();
supportedOptions.add(MyAnnotion.class.getCanonicalName());
return supportedOptions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
複製代碼
結果展現:
注意一下生成的位置!咱們能夠直接再咱們正常的代碼中應用到該文件,由於該文件是會生成class文件的。
該文章只是介紹瞭如何搭建起一個Java註解處理器,沒有更深刻的去講解AbstractProcessor類以及咱們再處理註解的過程當中用到的各類類的API。固然接下來的文章就會詳細的介紹註解處理器所使用到的類,方法,屬性等的用法和意義,這必定是史上最全的註解處理器API。以後你會更加爲所欲爲的去構建本身的註解框架。
下章節 史上最全的註解處理器API指導!
歡迎你們留言提出文章中出現的錯誤以及不理解的地方,會在第一時間進行更正和解答~