APT技术运用之JavaPoet

JavaPoet

JavaPoet 是一个用于生成 .java 源文件的 Java API,是ATP技术的必要组件。我们在工作中如果遇到重复的代码工作反复操作的时候,相信谁都很不耐烦,都希望能够有简单的方法一劳永逸,没错今天他来了。找出重复代码特点,借助JavaPoet语法轻轻松松减轻你的痛苦。

JavaPoet带来的愉快体验:

我们来看名称为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")
     //方法添加描述符,公开且静态的方法
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    //方法添加返回类型,为void空返回
    .returns(void.class)
    //方法添加参数类型是string[],名称是args
    .addParameter(String[].class, "args")
    //方法添加内容,一句打印 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()) 或将其写入文件系统 (JavaFile.writeTo())。

怎么优雅的使用javaPoet写代码

JavaPoet给我们提供了一些很多模型来帮组我们完成代码生成。
类和接口模型(TypeSpec),字段模型 (FieldSpec)、方法和构造函数模型 (MethodSpec)、参数模型 (ParameterSpec) 和注释模型(AnnotationSpec

方法和构造函数的实现JavaPoet没有给我们提供模型,我们可以直接用字符串来实现这一部分的代码。看看下面的代码就明白了。

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 时会经常需要它。它还可以识别任何类,如数组、参数化类型、通配符类型和类型变量。

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会自动给我们导入组件,看这屌样代码就看出来了

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;
  }
}

导入静态属性

把上面例子扩展下

...
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”块添加到配置的文件中,匹配并处理相应地所有调用,并根据需要导入所有其他类型。

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();

相对参数

占位符可以按顺序替换占位符

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

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

位置参数

可以指定参数位填充位置

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

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

命名参数

可以安装名字去匹配参数

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();
}

构造方法

`MethodSpec”用于构造函数:

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();

对应代码如下

public class HelloWorld {
  private final String greeting;

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

参数

Declare parameters on methods and constructors with either ParameterSpec.builder() or
MethodSpec's convenient addParameter() API:
定义方法的参数可以使用ParameterSpec或者使用MethodSpectaddParameterAPI来实现

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两种方式来添加参数,都是添加string的参数,最终生成的代码如下:

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();
}

枚举

创建枚举的方法在TypeSpec中,调用enumBuilder来创建一个枚举,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;
  }
}

匿名内部类

在枚举代码中,我们使用了TypeSpec.anonymousInnerClass()。匿名内部类也可以用于代码块。它们是可以用“$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()设置属性

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实现APT技术的例子,下次见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容