annotationProcessor 自動生成代碼(上)

概要

有時候,我們需要開發大量重複的代碼。每段代碼,只有少數成員變量命名不同。這樣的場景在開發接口層時,感覺尤爲明顯。 接口類可能只是實現類的抽象形式。但每個實現方法,我們都要寫一遍接口。且每個接口方法的命名,可能和實現方法完全一致。 那麼,能否有一種方案,讓我們用代碼自行生成接口呢?這個方案之前是apt,如今是 annotationProcessor

快速開始

annotationProcessor的使用大概分爲兩部分:annotationannotation-compiler。總體原理是,我們定義annotation,然後在合適的地方使用annotation。當編譯器編譯到我們使用annotation的地方時,變會執行annotation-compiler生成相應的代碼。通過annotation的定義位置和相關參數,我們可以生成不同的代碼。

annotation

首先我們新建Java-Library,並定義註解類:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface DoctorInterface {
}

此時我們只是定義了一個註解DoctorInterface,它暫時還不具有任何實際的意義。

annotation-compiler

我們再新建一個Java-Library。

首先,值得注意的是它的build.gradle,我們需要一些依賴:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.auto.service:auto-service:1.0-rc4' //自動註冊註解處理器
    implementation 'com.squareup:javapoet:1.8.0' //javapoet代碼生成框架
    implementation project(':router-annotation') //依賴註解模塊
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

接着,我們就來嘗試實現前面定義的註解DoctorInterface的意義。

@AutoService(Processor.class)  //自動註冊
@SupportedSourceVersion(SourceVersion.RELEASE_7) //指定java版本
public class InterfaceProcessor extends AbstractProcessor {


    private Filer filer;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnv.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(DoctorInterface.class.getCanonicalName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        MethodSpec main = MethodSpec.methodBuilder("main")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(String[].class, "args")
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                .build();
        // HelloWorld class
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)
                .build();
        try {
            // build com.example.HelloWorld.java
            JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
                    .addFileComment(" This codes are generated automatically. Do not modify!")
                    .build();
            // write to file
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


}

對於它的具體實現,我們先不避深究。我們首先應該注意到:

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(DoctorInterface.class.getCanonicalName());
    }

它指定了,這個實現對應的註解類。

然後,我們可以注意到自動生成的類,其實現在process方法中:

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        MethodSpec main = MethodSpec.methodBuilder("main")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(String[].class, "args")
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                .build();
        // HelloWorld class
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)
                .build();
        try {
            // build com.example.HelloWorld.java
            JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
                    .addFileComment(" This codes are generated automatically. Do not modify!")
                    .build();
            // write to file
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

實際上,它生成的代碼是:

//  This codes are generated automatically. Do not modify!
package com.example;

import java.lang.String;
import java.lang.System;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

使用

值得注意的是,並不是我們代碼寫完,開始編譯,HelloWorld類就能自行生成。如前面所說,我們對這個Processor指定了註解,只有編譯時發現註解,纔會生成這個類。

我們在自己的module的build.gradle中加入:

dependencies {
    ...
    implementation project(':router-annotation')
    annotationProcessor project(':router-compiler')
}

然後某個Java類上,加入註解:

import com.ocean.doctor.router_annotation.DoctorInterface;

@DoctorInterface
public class InterfaceBuilder {
}

這樣在編譯過程中,我們就可以在對應module的build目錄中,找到我們生成的HelloWorld類。

總結

以上就是通過Javapoet和annotation自動生成Java代碼的一個基本模式。生成代碼的具體細節,本文沒有深究。關於生成代碼的過程中,我們如何加入自己的想法,增加代碼的可擴展性,將在下篇講解。

如有問題,歡迎指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章