annotationProcessor 自動生成程式碼(上)
概要
有時候,我們需要開發大量重複的程式碼。每段程式碼,只有少數成員變數命名不同。這樣的場景在開發介面層時,感覺尤為明顯。
介面類可能只是實現類的抽象形式。但每個實現方法,我們都要寫一遍介面。且每個介面方法的命名,可能和實現方法完全一致。
那麼,能否有一種方案,讓我們用程式碼自行生成介面呢?這個方案之前是apt,如今是 annotationProcessor
快速開始
annotationProcessor的使用大概分為兩部分: annotation 和 annotation-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程式碼的一個基本模式。生成程式碼的具體細節,本文沒有深究。關於生成程式碼的過程中,我們如何加入自己的想法,增加程式碼的可擴充套件性,將在下篇講解。
如有問題,歡迎指正。