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