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!