APT技術(shù)運(yùn)用之JavaPoet

JavaPoet

JavaPoet 是一個用于生成 .java 源文件的 Java API,是ATP技術(shù)的必要組件。我們在工作中如果遇到重復(fù)的代碼工作反復(fù)操作的時(shí)候,相信誰都很不耐煩,都希望能夠有簡單的方法一勞永逸,沒錯今天他來了。找出重復(fù)代碼特點(diǎn),借助JavaPoet語法輕輕松松減輕你的痛苦。

JavaPoet帶來的愉快體驗(yàn):

我們來看名稱為HelloJavaPoet 的java源文件:

package com.kingtouch.hellojavapoet;

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

來看看我們使用JavaPoet的api來書寫生成這段極其簡單的java代碼過程,令人大跌眼鏡

//申明一個方法對象,方法名是“main”
MethodSpec main = MethodSpec.methodBuilder("main")
     //方法添加描述符,公開且靜態(tài)的方法
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    //方法添加返回類型,為void空返回
    .returns(void.class)
    //方法添加參數(shù)類型是string[],名稱是args
    .addParameter(String[].class, "args")
    //方法添加內(nèi)容,一句打印 System.out.println("HelloJavaPoet!")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

//申明一個類對象,類名是“HelloJavaPoet”
TypeSpec helloWorld = TypeSpec.classBuilder("HelloJavaPoet")
     //類添加描述符,公開且不可繼承的類
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    //類添加方法,前面定義的 MethodSpec 變量  main
    .addMethod(main)
    .build();
//申明一個java文件輸出對象
JavaFile javaFile = JavaFile.builder("com.kingtouch.hellojavapoet;", HelloJavaPoet)
    .build();
//輸出文件
javaFile.writeTo(System.out);

最終我們把文件寫入 System.out,我們也可以將其作為字符串獲取
(JavaFile.toString()) 或?qū)⑵鋵懭胛募到y(tǒng) (JavaFile.writeTo())。

怎么優(yōu)雅的使用javaPoet寫代碼

JavaPoet給我們提供了一些很多模型來幫組我們完成代碼生成。
類和接口模型(TypeSpec),字段模型 (FieldSpec)、方法和構(gòu)造函數(shù)模型 (MethodSpec)、參數(shù)模型 (ParameterSpec) 和注釋模型(AnnotationSpec

方法和構(gòu)造函數(shù)的實(shí)現(xiàn)JavaPoet沒有給我們提供模型,我們可以直接用字符串來實(shí)現(xiàn)這一部分的代碼??纯聪旅娴拇a就明白了。

MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "if(i/2=0){\n"
        + "  total += i;\n"
        + "  }\n")
        + " return total;\n")
        + "}\n")
    .build();

生成的代碼就是下面這個屌樣

int main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
      if(i/2=0){
          total += i;
      }    
  }
  return total;
}

JavaPoet提供了分號和換行的操作,以及大括號使用beginControlFlow() + endControlFlow()配合使用

MethodSpec main = MethodSpec.methodBuilder("main")
    .returns(int.class)
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .beginControlFlow("if(i/2=0)")
    .addStatement("total += i")
    .endControlFlow()
    .endControlFlow()
    .addStatement("return total")
    .build();

如果遇到像if/else try/catch這種控制流的語句,可以使用nextControlFlow()

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("long now = $T.currentTimeMillis()", System.class)
    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
    .endControlFlow()
    .beginControlFlow("try")
    .addStatement("throw new Exception($S)", "出錯啦")
    .nextControlFlow("catch ($T e)", Exception.class)
    .addStatement("throw new $T(e)", RuntimeException.class)
    .endControlFlow()
    .build();

看看代碼是什么屌樣

void main() {
  long now = System.currentTimeMillis();
  if (System.currentTimeMillis() < now)  {
    System.out.println("HaHaHa");
  } else if (System.currentTimeMillis() == now) {
    System.out.println("HeiHeiHei");
  }
  
   try {
    throw new Exception("Failed");
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
  
}

JavaPoet給我們還提供了幾種占位符

$L (通用占位符)


  MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", 0, 10)
      .addStatement("result = result $L i", "*")
      .endControlFlow()
      .addStatement("return result")
      .build();

看看代碼是什么屌樣

int main() {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = result * i;
  }
  return result;
}

$S (string占位符)

public static void main(String[] args) throws Exception {
MethodSpec sayHelloMethodSpec = MethodSpec.methodBuilder("sayHello")
      .returns(String.class)
      .addStatement("return $S", "Hello boy!")
      .build();


  TypeSpec helloWorld = TypeSpec.classBuilder("SayHello")
      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
      .addMethod(sayHelloMethodSpec)
      .build();

  JavaFile javaFile = JavaFile.builder("com.king.sayHello", SayHello)
      .build();

  javaFile.writeTo(System.out);
}

來看看代碼是什么屌樣

public final class SayHello {
  String sayHello() {
    return "Hello boy!";
  }

}

$T (類型占位符)

MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("Date")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build();

JavaFile javaFile = JavaFile.builder("com.king.date", Date)
    .build();

javaFile.writeTo(System.out);

來看看代碼是什么屌樣

package com.king.date;

import java.util.Date;

public final class Date {
  Date today() {
    return new Date();
  }
}

也能夠替換ClassName,看看如下示例

ClassName bundle = ClassName.get("android.os", "Bundle");
addStatement("$T bundle = new $T()",bundle)

ClassName 類型非常重要,我們在使用 JavaPoet 時(shí)會經(jīng)常需要它。它還可以識別任何類,如數(shù)組、參數(shù)化類型、通配符類型和類型變量。

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("return result")
    .build();

JavaPoet會自動給我們導(dǎo)入組件,看這屌樣代碼就看出來了

package com.example.helloworld;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    return result;
  }
}

導(dǎo)入靜態(tài)屬性

把上面例子擴(kuò)展下

...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add($T.createNimbus(2000))", hoverboard)
    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
    .addStatement("$T.sort(result)", Collections.class)
    .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
    .build();

TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
    .addMethod(beyond)
    .build();

JavaFile.builder("com.example.helloworld", hello)
    .addStaticImport(hoverboard, "createNimbus")
    .addStaticImport(namedBoards, "*")
    .addStaticImport(Collections.class, "*")
    .build();

JavaPoet 將首先將您的“import static”塊添加到配置的文件中,匹配并處理相應(yīng)地所有調(diào)用,并根據(jù)需要導(dǎo)入所有其他類型。

package com.example.helloworld;

import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(createNimbus(2000));
    result.add(createNimbus("2001"));
    result.add(createNimbus(THUNDERBOLT));
    sort(result);
    return result.isEmpty() ? emptyList() : result;
  }
}

$N 占位符代指的是一個名稱,方法名稱,變量名稱等

addStatement("data.$N()",toString)

代碼如下

data.toString();

相對參數(shù)

占位符可以按順序替換占位符

CodeBlock.builder().add("I ate $L $L", 3, "tacos")

生成的字符串是, "I ate 3 tacos"

位置參數(shù)

可以指定參數(shù)位填充位置

CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)

生成的字符串是, "I ate 3 tacos"

命名參數(shù)

可以安裝名字去匹配參數(shù)

Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)

生成的字符串是, "I ate 3 tacos"

方法

使用Modifiers.ABSTRACT可獲得接口和抽象類

MethodSpec flux = MethodSpec.methodBuilder("flux")
    .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addMethod(flux)
    .build();

代碼如下這個屌樣:

public abstract class HelloWorld {
  protected abstract void flux();
}

構(gòu)造方法

`MethodSpec”用于構(gòu)造函數(shù):

MethodSpec flux = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "greeting")
    .addStatement("this.$N = $N", "greeting", "greeting")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(flux)
    .build();

對應(yīng)代碼如下

public class HelloWorld {
  private final String greeting;

  public HelloWorld(String greeting) {
    this.greeting = greeting;
  }
}

參數(shù)

Declare parameters on methods and constructors with either ParameterSpec.builder() or
MethodSpec's convenient addParameter() API:
定義方法的參數(shù)可以使用ParameterSpec或者使用MethodSpectaddParameterAPI來實(shí)現(xiàn)

ParameterSpec android = ParameterSpec.builder(String.class, "android")
    .addModifiers(Modifier.FINAL)
    .build();

MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
    .addParameter(android)
    .addParameter(String.class, "robot", Modifier.FINAL)
    .build();

如上代碼,通過ParamterSpecaddParameter兩種方式來添加參數(shù),都是添加string的參數(shù),最終生成的代碼如下:

void welcomeOverlords(final String android, final String robot) {
}

接口

JavaPoet的接口方法必須始終為PUBLIC ABSTRACT接口字段必須始終為PUBLIC STATIC FINAL。這些修改器是必需的

定義接口:

TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
        .initializer("$S", "change")
        .build())
    .addMethod(MethodSpec.methodBuilder("beep")
        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
        .build())
    .build();
public interface HelloWorld {
  String ONLY_THING_THAT_IS_CONSTANT = "change";

  void beep();
}

枚舉

創(chuàng)建枚舉的方法在TypeSpec中,調(diào)用enumBuilder來創(chuàng)建一個枚舉,addEnumConstant()添加value

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK")
    .addEnumConstant("SCISSORS")
    .addEnumConstant("PAPER")
    .build();

生成代碼如下:

public enum Roshambo {
  ROCK,

  SCISSORS,

  PAPER
}

支持花式枚舉

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
        .addMethod(MethodSpec.methodBuilder("toString")
            .addAnnotation(Override.class)
            .addModifiers(Modifier.PUBLIC)
            .addStatement("return $S", "avalanche!")
            .returns(String.class)
            .build())
        .build())
    .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
        .build())
    .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
        .build())
    .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(MethodSpec.constructorBuilder()
        .addParameter(String.class, "handsign")
        .addStatement("this.$N = $N", "handsign", "handsign")
        .build())
    .build();

生成代碼如下:

public enum Roshambo {
  ROCK("fist") {
    @Override
    public String toString() {
      return "avalanche!";
    }
  },

  SCISSORS("peace"),

  PAPER("flat");

  private final String handsign;

  Roshambo(String handsign) {
    this.handsign = handsign;
  }
}

匿名內(nèi)部類

在枚舉代碼中,我們使用了TypeSpec.anonymousInnerClass()。匿名內(nèi)部類也可以用于代碼塊。它們是可以用“$L”引用的值:

TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
    .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
    .addMethod(MethodSpec.methodBuilder("compare")
        .addAnnotation(Override.class)
        .addModifiers(Modifier.PUBLIC)
        .addParameter(String.class, "a")
        .addParameter(String.class, "b")
        .returns(int.class)
        .addStatement("return $N.length() - $N.length()", "a", "b")
        .build())
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addMethod(MethodSpec.methodBuilder("sortByLength")
        .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
        .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
        .build())
    .build();

This generates a method that contains a class that contains a method:

void sortByLength(List<String> strings) {
  Collections.sort(strings, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
      return a.length() - b.length();
    }
  });
}

注釋

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hoverboard")
    .build();
  @Override
  public String toString() {
    return "Hoverboard";
  }

使用AnnotationSpec.builder()設(shè)置屬性

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(Headers.class)
        .addMember("accept", "$S", "application/json; charset=utf-8")
        .addMember("userAgent", "$S", "Square Cash")
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();

生成代碼如下:

@Headers(
    accept = "application/json; charset=utf-8",
    userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);

Gradle 添加依賴:

compile 'com.squareup:javapoet:1.13.0'

好javaPoet的基本用法就介紹到這里了,抽空我再整理下完整的JavaPoet實(shí)現(xiàn)APT技術(shù)的例子,下次見。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容