AbstractProcessor: 利用註解動態生成程式碼
按照處理時期,註解分為兩種型別,一種是執行時註解,另一種是編譯時註解。
編譯時註解的核心依賴APT(Annotation Processing Tools)實現,對應的處理流程為:
在某些程式碼元素上(如型別、函式、欄位等)添加註解;
編譯時編譯器會檢查AbstractProcessor的子類,
然後將添加了註解的所有元素都傳遞到該類的process函式中;
使得開發人員可以在編譯器進行相應的處理。
例如,根據註解生成新的Java類,
這也就是EventBus,Retrofit,Dragger等開源庫的基本原理。
本篇部落格就從一個簡單的例子入手,
看看如何利用AbstractProcessor和註解來動態生成程式碼。
一、建立Java Library
Java API已經提供了掃描原始碼並解析註解的框架,
我們只需要繼承AbstractProcessor類來實現解析註解相關的邏輯。
因此,我們一般需要自己建立一個Java Library。
考慮到相容性問題,在Java Library對應的build.gradle中可以新增如下欄位:
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
之後,我們就可以簡單的建立一個註解:
package com.zhangjian;
/**
* @author zhangjian on 18-3-23.
*/
public @interface CustomAnnotation {
}
然後建立對應的註解處理器:
//指定該註解處理器可以解決的型別,需要完整的包名+類命
@SupportedAnnotationTypes("com.zhangjian.CustomAnnotation")
//指定編譯的JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CustomAnnotationProcessor extends AbstractProcessor{
//這裡就是處理註解的process函式
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//建立動態程式碼,實際上就是建立一個String, 寫入到檔案裡
//然後檔案會被解釋為.class檔案
StringBuilder builder = new StringBuilder()
.append("package com.zhangjian.annotationprocessor.generated;\n\n")
.append("public class GeneratedClass {\n\n")
.append("\tpublic String getMessage() {\n")
.append("\t\treturn \"");
//獲取所有被CustomAnnotation修飾的程式碼元素
for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) {
String objectType = element.getSimpleName().toString();
builder.append(objectType).append(" exists!\\n");
}
builder.append("\";\n")
.append("\t}\n")
.append("}\n");
//將String寫入並生成.class檔案
try {
JavaFileObject source = processingEnv.getFiler().createSourceFile(
"com.zhangjian.annotationprocessor.generated.GeneratedClass");
Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
//
}
return true;
}
}
接著,需要在Java Module的main目錄下,創建出resources目錄;
在resources目錄下創建出META-INF目錄;
並在META-INF目錄下創建出services目錄。
最後,在services目錄下創建出名為javax.annotation.processing.Processor的檔案。
在其中申明我們的註解處理器:
com.zhangjian.CustomAnnotationProcessor
二、使用Java Library
建立完Java Library後,我們就可以使用了。
在app module對應的build.gradle中,新增類似如下語句:
task processorTask(type: Exec) {
//將編譯出的java library對應的jar包,複製到app module的libs下
commandLine 'cp', '../processor/build/libs/processor.jar', 'libs/'
}
processorTask.dependsOn(':processor:build')
preBuild.dependsOn(processorTask)
此外,還需要指定依賴檔案並考慮相容性,需要在build.gradle中新增:
android {
............
defaultConfig {
........
//由於我們是自己建立的annotationProcessor, 且在編譯時使用
//因此需要在此申明
//不過這個欄位在高版本的gradle中已經是deprecated了
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
}
}
}
............
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
..........
implementation fileTree(include: ['*.jar'], dir: 'libs')
annotationProcessor "com.neenbedankt.gradle.plugins:android-apt:1.8"
}
如上註釋,在高版本的gradle中建議以如下方式,引入自定義的annotationProcessor:
dependencies {
........
annotationProcessor files("libs/processor.jar")
}
接下來我們就可以在程式碼中使用註解了:
/**
* @author zhangjian
*/
@CustomAnnotation
public class MainActivity extends AppCompatActivity {
@Override
@CustomAnnotation
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//GeneratedClass是動態生成的
Log.v("Test", new GeneratedClass().getMessage());
}
}
我們rebuild一下app module就可以看到對應動態生成的程式碼,
在build/source/apt目錄下:
package com.zhangjian.annotationprocessor.generated;
public class GeneratedClass {
public String getMessage() {
return "MainActivity exists!\nonCreate exists!\n";
}
}
三、總結
至此,我們大概瞭解如何利用註解動態生成程式碼了。
按照套路來,應該還是比較容易理解和使用的。