1. 概述

本文将带你快速掌握 JavaPoet 的核心用法。

JavaPoet 是由 Square 开发的一款用于生成 Java 源码的库。它能生成基本类型、引用类型及其各种变体(如类、接口、枚举、匿名内部类)、字段、方法、参数、注解和 Javadoc。

✅ 优势亮点:

  • 自动生成所需的 import 语句,无需手动管理
  • 采用 Builder 模式构建代码逻辑,链式调用清晰直观
  • 专为代码生成设计,适合注解处理器、框架开发等场景

⚠️ 注意:JavaPoet 只负责生成 .java 文件,不编译也不运行代码。如果你需要运行时元编程能力,它并不适用。


2. Maven 依赖

使用 JavaPoet 很简单,只需引入以下 Maven 依赖:

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

✅ 提示:建议使用较新稳定版本,避免已知 Bug。当前最新版可在 Maven Central 查看。


3. 方法定义(MethodSpec)

要生成一个方法,使用 MethodSpec.methodBuilder(),传入方法名即可。

常用方法:

  • addStatement(...):添加一条以分号结尾的语句
  • beginControlFlow(...) / endControlFlow():包裹控制流块(如 if、for)

🌰 示例:生成一个计算 0 到 10 累加和的方法

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

生成结果:

void sumOfTen() {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

⚠️ 注意:默认返回类型是 void,如需指定返回值类型,使用 .returns(...)


4. 代码块(CodeBlock)

当你想复用一段逻辑(比如循环体),可以用 CodeBlock 封装。

CodeBlock sumOfTenImpl = CodeBlock
  .builder()
  .addStatement("int sum = 0")
  .beginControlFlow("for (int i = 0; i <= 10; i++)")
  .addStatement("sum += i")
  .endControlFlow()
  .build();

输出:

int sum = 0;
for (int i = 0; i <= 10; i++) {
    sum += i;
}

然后在方法中通过 .addCode() 引入:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addCode(sumOfTenImpl)
  .build();

✅ 优点:提升代码复用性,逻辑更清晰。


5. 字段定义(FieldSpec)

使用 FieldSpec.builder(类型, 字段名) 创建字段。

🌰 示例:定义一个私有字段

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

生成:

private String name;

🌰 带初始化值的静态常量:

FieldSpec defaultName = FieldSpec
  .builder(String.class, "DEFAULT_NAME")
  .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
  .initializer("\"Alice\"")
  .build();

生成:

private static final String DEFAULT_NAME = "Alice";

⚠️ 注意字符串初始化需要手动加引号转义,后面会介绍更优雅的方式。


6. 参数定义(ParameterSpec)

给方法加参数,可以直接用 .addParameter(类型, 名称)

对于复杂类型(如泛型),建议使用 ParameterSpec 显式构建:

ParameterSpec strings = ParameterSpec
  .builder(
    ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)), 
    "strings")
  .build();

然后在方法中使用:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

生成结果:

public static void sumOfTen(int number, List<String> strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

7. 类型定义(TypeSpec)

TypeSpec 是生成类、接口、枚举的核心工具。

7.1 生成类

使用 TypeSpec.classBuilder("类名")

TypeSpec person = TypeSpec
  .classBuilder("Person")
  .addModifiers(Modifier.PUBLIC)
  .addField(name)
  .addMethod(MethodSpec
    .methodBuilder("getName")
    .addModifiers(Modifier.PUBLIC)
    .returns(String.class)
    .addStatement("return this.name")
    .build())
  .addMethod(MethodSpec
    .methodBuilder("setName")
    .addParameter(String.class, "name")
    .addModifiers(Modifier.PUBLIC)
    .addStatement("this.name = name")
    .build())
  .addMethod(sumOfTen)
  .build();

生成:

public class Person {
    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void sumOfTen(int number, List<String> strings) {
        int sum = 0;
        for (int i = 0; i <= 10; i++) {
            sum += i;
        }
    }
}

7.2 生成接口

使用 interfaceBuilder(),支持抽象方法和 default 方法:

TypeSpec person = TypeSpec
  .interfaceBuilder("Person")
  .addModifiers(Modifier.PUBLIC)
  .addField(defaultName)
  .addMethod(MethodSpec
    .methodBuilder("getName")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .build())
  .addMethod(MethodSpec
    .methodBuilder("getDefaultName")
    .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
    .addCode(CodeBlock
      .builder()
      .addStatement("return DEFAULT_NAME")
      .build())
    .build())
  .build();

生成:

public interface Person {
    private static final String DEFAULT_NAME = "Alice";

    void getName();

    default void getDefaultName() {
        return DEFAULT_NAME;
    }
}

7.3 生成枚举

使用 enumBuilder(),通过 addEnumConstant() 添加枚举值:

TypeSpec gender = TypeSpec
  .enumBuilder("Gender")
  .addModifiers(Modifier.PUBLIC)
  .addEnumConstant("MALE")
  .addEnumConstant("FEMALE")
  .addEnumConstant("UNSPECIFIED")
  .build();

生成:

public enum Gender {
    MALE,
    FEMALE,
    UNSPECIFIED
}

7.4 生成匿名内部类

使用 anonymousClassBuilder(),注意必须通过 addSuperinterface() 指定父类或接口:

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

生成:

new Comparator<String>() {
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

8. 注解处理(AnnotationSpec)

给方法或字段加注解,使用 .addAnnotation()

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addAnnotation(Override.class)
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

生成:

@Override
public static void sumOfTen(int number, List<String> strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

如果注解带属性,使用 AnnotationSpec.builder()

AnnotationSpec toString = AnnotationSpec
  .builder(ToString.class)
  .addMember("exclude", "\"name\"")
  .build();

生成:

@ToString(
    exclude = "name"
)

9. 生成 Javadoc

使用 .addJavadoc() 添加文档注释,支持直接字符串或 CodeBlock

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addJavadoc("Sum of all integers from 0 to 10")
  .addAnnotation(Override.class)
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

生成:

/**
 * Sum of all integers from 0 to 10
 */
@Override
public static void sumOfTen(int number, List<String> strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

✅ 建议:为生成的代码加上 Javadoc,提升可读性和维护性。


10. 格式化技巧(避免字符串拼接)

原始方式需要手动处理转义,很麻烦:

initializer("\"Alice\"")        // 要双引号转义
addMember("exclude", "\"name\"") // 同样

JavaPoet 提供了类似 String.format 的格式化语法,大幅提升可读性。

10.1 字面量格式化($L)

$L 代表字面量,自动处理基本类型和字符串:

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

调用:

generateSumMethod("sumOfOneHundred", 0, 100, "+");

生成:

int sumOfOneHundred() {
    int sum = 0;
    for (int i = 0; i <= 100; i++) {
        sum = sum + i;
    }
    return sum;
}

10.2 字符串格式化($S)

$S 会自动加双引号,并处理转义:

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

调用:

generateStringSupplier("getDefaultName", "Bob");

生成:

String getDefaultName() {
    return "Bob";
}

10.3 类型格式化($T)

$T 代表类型,JavaPoet 会自动导入:

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

生成:

Date getCurrentDate() {
    return new Date();
}

✅ 优势:无需手动 import,类型冲突时自动处理全限定名。

10.4 名称引用($N)

$N 用于引用方法、字段、变量名,避免硬编码字符串:

MethodSpec dateToString = MethodSpec
  .methodBuilder("getCurrentDateAsString")
  .returns(String.class)
  .addStatement("$T formatter = new $T($S)", 
                DateFormat.class, 
                SimpleDateFormat.class, 
                "MM/dd/yyyy HH:mm:ss")
  .addStatement("return formatter.format($N())", getCurrentDateMethod)
  .build();

生成:

String getCurrentDateAsString() {
    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
    return formatter.format(getCurrentDate());
}

✅ 这是类型安全的关键技巧,避免手写方法名出错。


11. 生成 Lambda 表达式

JavaPoet 支持生成 Lambda,只需写在 CodeBlock 中即可:

CodeBlock printNameMultipleTimes = CodeBlock
  .builder()
  .addStatement("$T<$T> names = new $T<>()", List.class, String.class, ArrayList.class)
  .addStatement("$T.range($L, $L).forEach(i -> names.add(name))", IntStream.class, 0, 10)
  .addStatement("names.forEach(System.out::println)")
  .build();

生成:

List<String> names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);

✅ 踩坑提醒:确保 $T$L 正确使用,否则编译会失败。


12. 输出文件(JavaFile)

最后一步,使用 JavaFile 将生成的类型写入文件。

12.1 缩进设置

默认是 2 空格,通常我们改为 4 空格:

JavaFile javaFile = JavaFile
  .builder("com.example.person", person)
  .indent("    ")
  .build();

12.2 静态导入

使用 .addStaticImport() 添加静态导入:

JavaFile javaFile = JavaFile
  .builder("com.example.person", person)
  .indent("    ")
  .addStaticImport(Date.class, "UTC")
  .addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
  .build();

生成:

import static java.util.Date.UTC;
import static java.time.ZonedDateTime.*;

12.3 写出到文件或控制台

// 输出到控制台
javaFile.writeTo(System.out);

// 输出到指定路径
Path path = Paths.get("/your/project/src/main/java");
javaFile.writeTo(path);

✅ 建议:在实际项目中,通常集成到注解处理器中,自动生成代码到 generated-sources 目录。


13. 总结

JavaPoet 是一个强大且简洁的 Java 代码生成库,适合用于:

  • ✅ 注解处理器(Annotation Processor)
  • ✅ 框架代码自动生成
  • ✅ 减少样板代码(boilerplate code)

核心优势:

  • ✅ 链式 API 设计,代码清晰
  • ✅ 自动管理 import
  • ✅ 支持格式化占位符($L, $S, $T, $N),避免字符串拼接
  • ✅ 完整覆盖类、接口、枚举、注解等语法结构

⚠️ 注意限制:仅生成源码,不编译不运行。

所有示例代码已托管至 GitHub:https://github.com/baeldung/tutorials
欢迎 star & fork!


原始标题:Introduction to JavaPoet | Baeldung