1. 程式人生 > >AbstractProcessor: 利用註解動態生成程式碼

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 modulelibs
    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";
    }
}

三、總結
至此,我們大概瞭解如何利用註解動態生成程式碼了。
按照套路來,應該還是比較容易理解和使用的。