javapoet

JavaPoet 是用于生成 .java 源文件的 Java API。

在执行诸如注释处理或与元数据文件(例如,数据库模式、协议格式)交互等操作时,源文件生成非常有用。

通过生成代码,您无需编写样板,同时还为元数据保留了单一的真实来源。

Java Poet

JavaPoet is the successor to JavaWriter.

New projects should prefer JavaPoet because it has a stronger code model: it understands types and can manage imports automatically. JavaPoet is also better suited to composition: rather than streaming the contents of a .java file top-to-bottom in a single pass, a file can be assembled as a tree of declarations.

Hello World

  • Import jar
<dependency>
    <groupId>com.squareup</groupId>
    <artifactId>javapoet</artifactId>
    <version>1.9.0</version>
</dependency>
  • HelloWorld.java
package com.ryo.generator.java;

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

import javax.lang.model.element.Modifier;
import java.io.IOException;

/**
 * Java poet demo
 * Created by bbhou on 2017/9/29.
 */
public class HelloWorld {

    public static void main(String[] args) 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!")
                .build();

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

        JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
                .build();

        javaFile.writeTo(System.out);
    }
}

result:

package com.example.helloworld;

import java.lang.String;
import java.lang.System;

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

例子

hello world

package com.example.helloworld;

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

这是使用 JavaPoet 生成它的(令人兴奋的)代码:

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

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

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

为了声明 main 方法,我们创建了一个 MethodSpec “main”,配置了修饰符、返回类型、参数和代码语句。

我们将 main 方法添加到 HelloWorld 类中,然后将其添加到 HelloWorld.java 文件中。

在这种情况下,我们将文件写入 System.out,但我们也可以将其作为字符串获取 (JavaFile.toString()) 或将其写入文件系统 (JavaFile.writeTo())。

Javadoc 对完整的 JavaPoet API 进行了编目,我们将在下面进行探讨。

代码和控制流

JavaPoet 的大部分 API 使用普通的老式不可变 Java 对象。 还有构建器、方法链和可变参数使 API 友好。

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"
        + "  total += i;\n"
        + "}\n")
    .build();

生成如下:

void main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += i;
  }
}

手动分号、换行和缩进很乏味,因此 JavaPoet 提供了 API 以使其更容易。

addStatement() 处理分号和换行符,beginControlFlow() + endControlFlow() 一起用于大括号、换行符和缩进:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();

这个例子很蹩脚,因为生成的代码是常量!

假设我们不只是将 0 加到 10,而是希望操作和范围可配置。

这是一个生成方法的方法:

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 1")
      .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
      .addStatement("result = result " + op + " i")
      .endControlFlow()
      .addStatement("return result")
      .build();
}

这是我们调用 computeRange("multiply10to20", 10, 20, "*") 时得到的结果:

int multiply10to20() {
  int result = 1;
  for (int i = 10; i < 20; i++) {
    result = result * i;
  }
  return result;
}

方法生成方法!

由于 JavaPoet 生成源代码而不是字节码,您可以通读它以确保它是正确的。

某些控制流语句,例如 if/else,可以具有无限的控制流可能性。

您可以使用 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!")
    .nextControlFlow("else")
    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
    .endControlFlow()
    .build();

生成如下:

void main() {
  long now = System.currentTimeMillis();
  if (System.currentTimeMillis() < now)  {
    System.out.println("Time travelling, woo hoo!");
  } else if (System.currentTimeMillis() == now) {
    System.out.println("Time stood still!");
  } else {
    System.out.println("Ok, time still moving forward");
  }
}

使用 try/catch 捕获异常也是 nextControlFlow() 的一个用例:

MethodSpec main = MethodSpec.methodBuilder("main")
    .beginControlFlow("try")
    .addStatement("throw new Exception($S)", "Failed")
    .nextControlFlow("catch ($T e)", Exception.class)
    .addStatement("throw new $T(e)", RuntimeException.class)
    .endControlFlow()
    .build();

生成如下:

void main() {
  try {
    throw new Exception("Failed");
  } catch (Exception e) {
    throw new RuntimeException(e);
  }

$L 用于文字

调用 beginControlFlow() 和 addStatement 中的字符串连接令人分心。 运营商太多。

为了解决这个问题,JavaPoet 提供了一种受 String.format() 启发但不兼容的语法。

它接受 $L 在输出中发出一个文字值。

这就像格式化程序的 %s 一样工作:

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
      .addStatement("result = result $L i", op)
      .endControlFlow()
      .addStatement("return result")
      .build();
}

文字直接发送到输出代码,没有转义。

文字的参数可以是字符串、原语和下面描述的一些 JavaPoet 类型。

$S 表示字符串

当发出包含字符串文字的代码时,我们可以使用 $S 来发出一个字符串,包括引号和转义符。

这是一个发出 3 个方法的程序,每个方法都返回自己的名称:

public static void main(String[] args) throws Exception {
  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
      .addMethod(whatsMyName("slimShady"))
      .addMethod(whatsMyName("eminem"))
      .addMethod(whatsMyName("marshallMathers"))
      .build();

  JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
      .build();

  javaFile.writeTo(System.out);
}

private static MethodSpec whatsMyName(String name) {
  return MethodSpec.methodBuilder(name)
      .returns(String.class)
      .addStatement("return $S", name)
      .build();
}

在这种情况下,使用 $S 给我们引号:

public final class HelloWorld {
  String slimShady() {
    return "slimShady";
  }

  String eminem() {
    return "eminem";
  }

  String marshallMathers() {
    return "marshallMathers";
  }
}

类型的 $T

我们 Java 程序员喜欢我们的类型:它们使我们的代码更容易理解。

并且 JavaPoet 已加入。

它具有丰富的内置类型支持,包括自动生成导入语句。

只需使用 $T 来引用类型:

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

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

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

这会生成以下 .java 文件,并完成必要的导入:

package com.example.helloworld;

import java.util.Date;

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

我们通过 Date.class 来引用一个在我们生成代码时恰好可用的类。

不必如此。

这是一个类似的示例,但此示例引用了一个尚不存在的类:

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

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

并且还导入了那个尚不存在的类:

package com.example.helloworld;

import com.mattel.Hoverboard;

public final class HelloWorld {
  Hoverboard tomorrow() {
    return new Hoverboard();
  }
}

ClassName 类型非常重要,在使用 JavaPoet 时会经常用到它。

它可以识别任何声明的类。

声明类型只是 Java 丰富类型系统的开始:我们还有数组、参数化类型、通配符类型和类型变量。

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

导入静态

JavaPoet 支持静态导入。 它通过显式收集类型成员名称来实现。

让我们用一些静态糖来增强前面的例子:

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

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 用于名称

生成的代码通常是自引用的。

使用 $N 通过其名称引用另一个生成的声明。

这是一个调用另一个方法的方法:

public String byteToHex(int b) {
  char[] result = new char[2];
  result[0] = hexDigit((b >>> 4) & 0xf);
  result[1] = hexDigit(b & 0xf);
  return new String(result);
}

public char hexDigit(int i) {
  return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}

在生成上面的代码时,我们使用 $N 将 hexDigit() 方法作为参数传递给 byteToHex() 方法:

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();

代码块格式字符串

代码块可以通过几种方式为其占位符指定值。

代码块上的每个操作只能使用一种样式。

相对参数

将格式字符串中每个占位符的参数值传递给 CodeBlock.add()。 在每个示例中,我们生成代码来表示“我吃了 3 个炸玉米饼”

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

位置参数

在格式字符串中的占位符之前放置一个整数索引(从 1 开始)以指定要使用的参数。

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

命名参数

使用语法 $argumentName:X,其中 X 是格式字符,并使用包含格式字符串中所有参数键的映射调用 CodeBlock.addNamed()。

参数名称使用 a-z、A-Z、0-9 和 _ 中的字符,并且必须以小写字符开头。

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

方法

以上所有方法都有一个代码体。

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

Which generates this:

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

其他修饰符在允许的情况下起作用。

请注意,在指定修饰符时,JavaPoet 使用 javax.lang.model.element.Modifier,该类在 Android 上不可用。 此限制仅适用于代码生成代码; 输出代码随处运行:JVM、Android 和 GWT。

方法也有参数、异常、可变参数、Javadoc、注释、类型变量和返回类型。

所有这些都使用 MethodSpec.Builder 进行配置。

构造函数

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

在大多数情况下,构造函数就像方法一样工作。

发出代码时,JavaPoet 将在输出文件中的方法之前放置构造函数。

参数

使用 ParameterSpec.builder() 或 MethodSpec 方便的 addParameter() API 在方法和构造函数上声明参数:

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

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

虽然上面生成android和robot参数的代码不同,但输出是一样的:

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

当参数有注解(如@Nullable)时,扩展的Builder形式是必要的。

字段

像参数一样,可以使用构建器或使用方便的辅助方法来创建字段:

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

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

输出:

public class HelloWorld {
  private final String android;

  private final String robot;
}

当字段具有 Javadoc、注释或字段初始值设定项时,扩展 Builder 表单是必需的。

字段初始值设定项使用与上述代码块相同的类似 String.format() 的语法:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .initializer("$S + $L", "Lollipop v.", 5.0d)
    .build();

生成:

private final String android = "Lollipop v." + 5.0;

接口

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

但是这些修饰符在生成代码时被省略了。

这些是默认值,因此我们不需要为了 javac 的好处而包含它们!

public interface HelloWorld {
  String ONLY_THING_THAT_IS_CONSTANT = "change";

  void beep();
}

枚举

使用 enumBuilder 创建枚举类型,并为每个值 addEnumConstant() :

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

这将生成一个方法,该方法包含一个包含方法的类:

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

定义匿名内部类的一个特别棘手的部分是超类构造函数的参数。

在上面的代码中,我们传递了没有参数的空字符串:TypeSpec.anonymousClassBuilder(“”)。

要传递不同的参数,请使用 JavaPoet 的代码块语法,并使用逗号分隔参数。

Annotation

简单的 Annotation 很容易:

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

它使用@Override 注释生成此方法:

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

它使用 accept 和 userAgent 属性生成此注释:

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

当您喜欢时,注释值可以是注释本身。

使用 $L 嵌入注释:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(HeaderList.class)
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "Accept")
            .addMember("value", "$S", "application/json; charset=utf-8")
            .build())
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "User-Agent")
            .addMember("value", "$S", "Square Cash")
            .build())
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();

效果:

@HeaderList({
    @Header(name = "Accept", value = "application/json; charset=utf-8"),
    @Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);

请注意,您可以使用相同的属性名称多次调用 addMember() 以填充该属性的值列表。

Javadoc

字段、方法和类型可以用 Javadoc 记录:

MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
    .addJavadoc("Hides {@code message} from the caller's history. Other\n"
        + "participants in the conversation will continue to see the\n"
        + "message in their own history unless they also delete it.\n")
    .addJavadoc("\n")
    .addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
        + "conversation for all participants.\n", Conversation.class)
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addParameter(Message.class, "message")
    .build();

生成:

/**
 * Hides {@code message} from the caller's history. Other
 * participants in the conversation will continue to see the
 * message in their own history unless they also delete it.
 *
 * <p>Use {@link #delete(Conversation)} to delete the entire
 * conversation for all participants.
 */
void dismiss(Message message);

在 Javadoc 中引用类型时使用 $T 以获取自动导入。

下载

下载最新的 .jar 或通过 Maven 依赖:

<dependency>
  <groupId>com.squareup</groupId>
  <artifactId>javapoet</artifactId>
  <version>1.13.0</version>
</dependency>

Java编写器

JavaPoet 是 JavaWriter 的继承者。 新项目应该更喜欢 JavaPoet,因为它有更强大的代码模型:它理解类型并且可以自动管理导入。 JavaPoet 也更适合组合:不是在一次传递中从上到下流式传输 .java 文件的内容,而是可以将文件组装为声明树。

JavaWriter 继续在 GitHub 和 Maven Central 中可用。

chat

Q1: javapoet 呢

JavaPoet 是一个用于在Java代码中进行代码生成的开源库。

它允许开发人员使用简单的API生成Java源代码,而无需手动拼接字符串。

JavaPoet 提供了一种类型安全、可读性高的方式,用于在编译时生成Java类、方法、字段等代码结构。

使用 JavaPoet,您可以轻松地构建和生成符合Java语法规范的代码。

这在许多代码生成场景中非常有用,例如在编写代码生成器、处理注解处理器(Annotation Processor)时,以及在其他需要生成Java代码的自动化任务中。

以下是 JavaPoet 的官方网站链接:

JavaPoet 提供了一种流畅的API,使得代码生成变得非常直观和易于维护。

它允许您以编程方式构建Java代码结构,然后将其输出为字符串,或者直接生成Java源文件。

这种方式可以避免手动拼接字符串时可能出现的错误,并且使得生成的代码结构更加清晰和可读。

Q2: 类似 javapoet 的工具呢

类似于JavaPoet的工具通常被用于代码生成,允许您以编程方式构建和生成符合语法规范的源代码,而无需手动拼接字符串。

以下是一些类似于JavaPoet的开源工具:

  1. Apache Velocity: Apache Velocity 是一个模板引擎,允许您使用模板语言生成文本文件,包括源代码文件。您可以定义模板并填充数据,Velocity 将会帮助您生成最终的文本文件。

    官方网站:Apache Velocity

  2. StringTemplate: StringTemplate 是一个模板引擎,它允许您定义模板文件并在模板中插入数据,生成文本文件,包括源代码文件。StringTemplate具有强类型的特性,使得生成的代码更加类型安全。

    官方网站:StringTemplate

  3. FreeMarker: FreeMarker 是一个模板引擎,它允许您使用模板文件生成文本输出。您可以在模板中嵌入数据,并使用FreeMarker的语法来控制文本生成的逻辑。

    官方网站:FreeMarker

  4. Thymeleaf: Thymeleaf 是一个现代的服务器端Java模板引擎,可以用于生成HTML、XML、JavaScript、CSS和文本。它提供了强大的模板功能,可以与Spring框架等集成使用。

    官方网站:Thymeleaf

  5. Groovy Templates: Groovy 是一门JVM语言,它具有内建的模板引擎,允许您使用Groovy语法生成文本输出。您可以在Groovy代码中嵌入模板,生成各种文本文件。

    官方网站:Groovy Templates

这些工具提供了不同的模板语法和功能,可以根据您的需求选择最适合的工具来进行代码生成。

参考资料

https://github.com/square/javapoet