JavaPoet 開源專案使用

黎偉傑發表於2017-02-06

JavaPoet開源專案使用

JavaPoet專案可以為我們動態的生成Java檔案,這是一個很強大和很動態的方法。我們使用註解的時候假如需要生成新的Java檔案就可以通過這個開源專案實現。
專案地址:Javapoet

引入

我們在AndroidStudio中新建一個Java module,宣告如下:

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.squareup:javapoet:1.8.0'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"複製程式碼

這樣子我們就引入了javapoet。

使用

例子

我們新建了一個名字為javapoettest的java module,然後就開始使用它。

package com.example;

import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.File;
import java.io.IOException;

import javax.lang.model.element.Modifier;

public class MyClass {
    public static void main(String args[]) throws IOException {
        MyClass instance = new MyClass();
        instance.generate();
    }
       private void generate() throws IOException {
        // 定義方法
        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!")//使用format
                .build();

        //生成類
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//類名字
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//類修飾符
                .addMethod(main)//新增方法
                .build();
        //生成一個頂級的java檔案描述物件
        JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
                .build();
        File outputFile = new File("javapoettest/src/main/java");
        //輸出檔案
        javaFile.writeTo(outputFile);
    }複製程式碼

執行之後,我們可以看到在同一個包名下面,生成了HelloWorld.java檔案。同時輸出了該Java檔案的原始碼。

詳細

  1. JavaFile,生成Java檔案
  2. MethodSpec 生成方法,構造器
  3. TypeSpec 生成類,介面
  4. FieldSpec 生成屬性
  5. ParameterSpec 生成引數

    MethodSpec

  • 生成方法使用的是MethodSpec類,他的使用如下:
    MethodSpec hi = MethodSpec.methodBuilder("sayHi").addCode("String s;\n" +
                  "s=\"java\";\n" +
                  "System.out.println(s);\n").build();複製程式碼
    這樣子就能夠生成一個名字為sayHi(),引數是空,返回值是void的函式,是:
    void sayHi() {
      String s;
      s="java";
      System.out.println(s);
    }複製程式碼
    methodBuilder(String)方法指定方法名稱,新增程式碼可以使用:
    addCode(String format, Object... args)
    addCode(CodeBlock codeBlock)複製程式碼
    例子是使用了第一種,他沒有使用format對應的佔位符,只是單純的format。
  • addCode方法需要我們自己手動新增分號好換行,我們可以使用另外一個方法實現,就是addStatement,例子:
    MethodSpec sayHi = MethodSpec.methodBuilder("sayHi")
                  .addStatement("int a = 1")
                  .addStatement("int b =2")
                  .addStatement("int sum=a+b")
                  .addCode("System.out.println(sum);\n")
                  .build();複製程式碼
    生成的方法就是:
    void sayHi() {
      int a = 1;
      int b =2;
      int sum=a+b;
      System.out.println(sum);
    }複製程式碼
  • 迴圈
    我們使用beginControlFlow開始迴圈,使用endControlFlow結束迴圈。在這二者之間是迴圈體。
    MethodSpec sayHello = MethodSpec.methodBuilder("sayHello")
                  .addStatement("int sum=0")
                  .beginControlFlow("for(int i=0;i<10;i++)")
                  .addStatement("sum+=i")
                  .endControlFlow()
                  .addStatement("System.out.println(sum)")
                  .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                  .build();複製程式碼
    輸出的方法是:
    public static void sayHello() {
          int sum = 0;
          for (int i = 0; i < 10; i++) {
              sum += i;
          }
          System.out.println(sum);
      }複製程式碼
    我們通過addModifiers()新增方法的修飾符,這裡是public static的,當然你可以選擇其他的。
  • 返回值
    returns(TypeName returnType)
    Builder returns(Type returnType)複製程式碼
    第一種用於指定常用的型別,第二種可以通過反射獲取指定的型別,例如
    MethodSpec say = MethodSpec.methodBuilder("say")
                  .returns(TypeName.BOOLEAN)
                  .addStatement("int a = 0")
                  .addStatement("int b = 1")
                  .addStatement("return a> b")
                  .build();複製程式碼
    或者
    MethodSpec say = null;
          try {
              say = MethodSpec.methodBuilder("say")
                      .returns(Class.forName("java.io.File"))
                      .addStatement("File newFile = new File(\"\")")
                      .addStatement("return newFile").build();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }複製程式碼
  • 佔位符format
    這裡的佔位符跟我們使用java時候的String.format()很類似。他有四種佔位符
  1. $L,就是一個字面量,變數值
  2. $S,代表Strings,可以替換多個String
  3. $T,匯入class,新增型別
  4. $N,代表名字
    例子:

生成方法是:
$L

MethodSpec methodL = MethodSpec.methodBuilder("methodL")
                .addStatement("String s1=\"$L\"", s1)
                .addStatement("int a = $L", a)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return a+s1")
                .returns(String.class)
                .build();
// 生成方法
public String methodL() {
    String s1="hi";
    int a = 10;
    return a+s1;
  }複製程式碼

$S

 String s2 = "hi";
        String s3 = "hello";
        MethodSpec methodS = MethodSpec.methodBuilder("methodS")
                .addStatement("String a=$S,b=$S", s2, s3)
                .addModifiers(Modifier.PUBLIC)
                .build();
// 生成方法是
public void methodS() {
    String a="hi",b="hello";
  }複製程式碼

$T

TypeSpec dog = TypeSpec.classBuilder("Dog")
                .addModifiers(Modifier.PUBLIC).build();
        JavaFile dogJava = JavaFile.builder("com.example.bean", dog).build();
        File dogFile = new File("javapoettest/src/main/java");
        dogJava.writeTo(dogFile);
        Class dogClass = Class.forName("com.example.bean.Dog");
        MethodSpec methodT = MethodSpec.methodBuilder("methodT")
                .addModifiers(Modifier.PUBLIC)
                .addStatement("$T dog= new $T()", dogClass,dogClass)
                .returns(Class.forName("com.example.bean.Dog"))
                .addStatement("return dog")
                .build();複製程式碼

這裡的例子是我們動態生成了一個dog類,在bean包下,我們需要在HelloWord中引入,所以我們就需要進行匯入了。那裡使用就可以匯入,只是需要匯入一次即可。但是第一次的時候執行時不可以的,因為這時候還沒有生成Dog類,需要執行第二次就ok了。如有同學之後如何第一次執行就可以匯入請評論告知,謝謝。當然這裡我們匯入一些java自帶的類的時候就不用反射直接使用XXX.class這樣子就ok了。
靜態匯入,我們在JavaFile構建的時候就可以新增靜態匯入,靜態匯入的好處就是可以直接使用一些被靜態匯入的屬性方法不需要通過class.method()呼叫。例如:

JavaFile javaFile = JavaFile.builder("com.example", typeSpec)
                .addStaticImport(Calendar.class,"*")
                .build();複製程式碼

在HelloWorld中就可以使用Calendar類的所有靜態屬性和靜態方法了。需要注意的是靜態匯入的方式是

import static packageName.className.staticField/staticMethod;
import static packageName.className.*;複製程式碼

$N
引用一個已經存在的變數作為引數替換,比如一個已經宣告存在的方法。例子如下:

     MethodSpec hxDigit = MethodSpec.methodBuilder("hexDigit")
                .addParameter(int.class,"i")
                .returns(char.class)
                .addStatement("return (char)(i<10?i+'0':i-10+'a')")
                .build();
        MethodSpec byteThHex = MethodSpec.methodBuilder("byteToHex")
                .addParameter(int.class, "b")
                .returns(String.class)
                .addStatement("char[] result = new char[2]")
                .addStatement("result[0]=$N((b>>4)%0xf)",hxDigit)
                .addStatement("result[1]=$N(b&0xf)",hxDigit)
                .addStatement("return new String(result)")
                .build();複製程式碼

就會生成方法:

  char hexDigit(int i) {
    return (char)(i<10?i+'0':i-10+'a');
  }
  String byteToHex(int b) {
    char[] result = new char[2];
    result[0]=hexDigit((b>>4)%0xf);
    result[1]=hexDigit(b&0xf);
    return new String(result);
  }複製程式碼
  • 新增引數
    .addParameter(String.class,"str",Modifier.FINAL)
    .addParameter(TypeName.BOOLEAN,"isLogin",Modifier.FINAL)複製程式碼
    第一個是型別,值可以是class或者是TypeName中的某一個,第二個是引數名稱,第三個是引數修飾符,方法引數修飾符一般只有final,或者是沒有。
  • 生成構造方法。
    //生成空的構造方法
    MethodSpec construct = MethodSpec.constructorBuilder()
                  .build();
          MethodSpec construct2 = MethodSpec.constructorBuilder()
                  .addParameter(String.class,"str")
                  .addParameter(TypeName.BOOLEAN,"isShow")
                  .addStatement("init(str,isShow)")
                  .build();
    //帶引數和初始化的構造方法
          MethodSpec init = MethodSpec.methodBuilder("init")
                  .addParameter(String.class,"str")
                  .addParameter(Boolean.class,"isShow")
                  .addStatement("this.str = str")
                  .addStatement("this.isShow = isShow")
                  .build();複製程式碼
    生成構造方法是使用MethodSpec.constructorBuilder()函式,並且不用指定函式名稱的。其他的跟呼叫MethodSpec.methodBuilder(String)差不多。

    TypeSpec

    生成類描述的類。
  • 設定類名:
    Builder classBuilder(String name) 
    Builder classBuilder(ClassName className)複製程式碼
    這兩個方法去設定類名,比如:
    TypeSpec Hi = TypeSpec.classBuilder("Hi").build();
          JavaFile HiJavaFile = JavaFile.builder("com.example", Hi).build();
          File HiFile = new File("javapoettest/src/main/java");
          HiJavaFile.writeTo(HiFile);複製程式碼
    這樣子就可以輸出Hi.java類。或者是
    TypeSpec Hello= TypeSpec.classBuilder(ClassName.get("com.example","Hello")).build();
          JavaFile HelloJavaFile = JavaFile.builder("com.example", Hello).build();
          File HelloFile = new File("javapoettest/src/main/java");
          HelloJavaFile.writeTo(HelloFile);複製程式碼
    這樣子通過自定獲取一個ClassName來生成一個Java類。
  • 新增方法。
    這些其實上面有提到,就死使用addMethod(MethodSpec)就好,無論是普通還是構造方法,還是靜態方法。
  • 新增屬性
    .addField(String.class,"str",Modifier.PRIVATE)
    .addField(TypeName.BOOLEAN,"isShow",Modifier.PRIVATE)複製程式碼
    跟MethodSpec新增一個引數類似,可以是使用Class宣告或者是使用TypeName。
  • 生成介面

    TypeSpec int  MethodSpec interfaceMethod = MethodSpec.methodBuilder("beep")
                  .addModifiers(Modifier.PUBLIC,Modifier.ABSTRACT).build();
          TypeSpec interfaceTest = TypeSpec.interfaceBuilder("InterfaceA")
                  .addModifiers(Modifier.PUBLIC,Modifier.PUBLIC)
                  .addMethod(interfaceMethod)
                  .build();複製程式碼

    需要注意的是生成的抽象方法需要手動加上public和abstract,對於Interface中的常量也是要加上public final的。
    生成抽象方法:

  • 繼承介面複寫方法

          MethodSpec mb = MethodSpec.methodBuilder("beep")
                  .addModifiers(Modifier.PUBLIC).addAnnotation(Override.class).build();
          TypeSpec mbClass = TypeSpec.classBuilder("B")
                  .addModifiers(Modifier.PUBLIC)
                  .addMethod(mb)
                  .addSuperinterface(InterfaceA.class)
                  .build();複製程式碼

    跟一般生類一樣,只不過是需要新增addSuperinterface方法。複寫方法的時候可以新增一個註解Override.class註明。
    還可以通過superclass(Type/TypeName)方法新增父類。複寫父類的方法跟上面的類似。

FieldSpec

生成屬性的描述類
例如:

     FieldSpec var = FieldSpec.builder(int.class,"i").build();
        FieldSpec var2 = FieldSpec.builder(int.class,"j",Modifier.PUBLIC,Modifier.STATIC,Modifier.FINAL)
                .initializer("1").build();複製程式碼

生成的屬性是:

public static final int j = 1;
  int i;複製程式碼

可以使用initializer()方法對變數進行初始化。初始化的時候可以使用佔位符替換實現動態。

ParameterSpec

生成引數描述類

  ParameterSpec spec = ParameterSpec.builder(String.class, "string", Modifier.FINAL).build();
    MethodSpec mb2 = MethodSpec.methodBuilder("beep")
                .addModifiers(Modifier.PUBLIC)
                .addParameter(spec)
                .build();複製程式碼

最後一個引數是修飾符,可以要或者不要,生成的是

  public void beep(final String string) {
  }複製程式碼

其他

square還有另外一個類似的專案 javawriter
文章難免有錯誤,謝謝指出,大家一起共同進步。