1. 概述
在本教程中,我们将探索JavaPoet库的基本功能。
JavaPoet由Square开发,提供生成Java源代码的API,可以生成原始类型、引用类型及其变体(如类、接口、枚举类型、匿名内部类)、字段、方法、参数、注解以及Javadocs。
JavaPoet自动管理依赖类的导入,它还使用构建器模式来指定生成Java代码的逻辑。
2. Maven依赖
为了使用JavaPoet,我们可以直接下载最新的JAR文件,或者在我们的pom.xml中定义以下依赖:
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.10.0</version>
</dependency>
3. 方法规范
首先,让我们看一下方法规范。要生成方法,我们只需调用MethodSpec类的methodBuilder()方法,我们将生成的方法名称指定为methodBuilder()方法的String参数。
我们可以使用addStatement()方法生成任何以分号结尾的单个逻辑语句。同时,我们可以在控制流中定义一个用花括号括起来的控制流,例如if-else块或for循环。
这是一个简单的例子-生成sumOfTen()方法,计算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;
}
}
4. 代码块
我们还可以将一个或多个控制流和逻辑语句包装到一个代码块中:
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()并提供sumOfTenImpl对象来简化MethodSpec中早期的逻辑:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addCode(sumOfTenImpl)
.build();
代码块也适用于其他规范,例如类型和Javadocs。
5. 字段规范
接下来让我们探索字段规范逻辑。
为了生成一个字段,我们使用FieldSpec类的builder()方法:
FieldSpec name = FieldSpec
.builder(String.class, "name")
.addModifiers(Modifier.PRIVATE)
.build();
这将生成以下字段:
private String name;
我们还可以通过调用initializer()方法来初始化字段的默认值:
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. 参数规范
现在让我们探索一下参数指定逻辑。
如果我们想向方法添加一个参数,我们可以在构建器中的函数调用链中调用addParameter()。
如果参数类型更复杂,我们可以使用ParameterSpec构建器:
ParameterSpec strings = ParameterSpec
.builder(ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)), "strings")
.build();
我们还可以添加方法的修饰符,例如public和/或static:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
生成的Java代码如下所示:
public static void sumOfTen(int number, List<String> strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
7. 类型规范
探索了生成方法、字段和参数的方式之后,现在我们来看看类型规范。
要声明类型,我们可以使用TypeSpec,它可以构建类、接口和枚举类型。
7.1 生成一个类
为了生成一个类,我们可以使用TypeSpec类的classBuilder()方法。
我们还可以指定其修饰符,例如public和final访问修饰符。除了类修饰符之外,我们还可以使用前面提到的FieldSpec和MethodSpec类指定字段和方法。
注意,生成接口或匿名内部类时也可以使用addField()和addMethod()方法。
让我们看一下以下类构建器示例:
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)
.returns(String.class)
.addStatement("this.name = name")
.build())
.addMethod(sumOfTen)
.build();
生成的代码如下:
public class Person {
private String name;
public String getName() {
return this.name;
}
public String 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 生成接口
为了生成Java接口,我们使用TypeSpec的interfaceBuilder()方法。
我们还可以通过在addModifiers()中指定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();
它将生成以下Java代码:
public interface Person {
private static final String DEFAULT_NAME = "Alice";
void getName();
default void getDefaultName() {
return DEFAULT_NAME;
}
}
7.3 生成枚举
要生成枚举类型,我们可以使用TypeSpec的enumBuilder()方法。要指定每个枚举值,我们可以调用addEnumConstant()方法:
TypeSpec gender = TypeSpec
.enumBuilder("Gender")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("MALE")
.addEnumConstant("FEMALE")
.addEnumConstant("UNSPECIFIED")
.build();
上述enumBuilder()逻辑的输出是:
public enum Gender {
MALE,
FEMALE,
UNSPECIFIED
}
7.4 生成匿名内部类
要生成匿名内部类,我们可以使用TypeSpec类的anonymousClassBuilder()方法。请注意,我们必须在addSuperinterface()方法中指定父类。否则,它将使用默认父类,即Object:
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();
这将生成以下 Java 代码:
new Comparator<String>() {
public int compare(String a, String b) {
return a.length() - b.length();
}
});
8. 注解规范
要向生成的代码添加注解,我们可以调用MethodSpec或FieldSpec构建器类中的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类的addMember()方法:
AnnotationSpec toString = AnnotationSpec
.builder(ToString.class)
.addMember("exclude", "\"name\"")
.build();
这将生成以下注释:
@ToString(
exclude = "name"
)
9. 生成Javadocs
可以使用CodeBlock生成Javadoc,或者直接指定值:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addJavadoc(CodeBlock
.builder()
.add("Sum of all integers from 0 to 10")
.build())
.addAnnotation(Override.class)
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
这将生成以下Java代码:
/**
* 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;
}
}
10. 格式化
让我们重新检查第5节中FieldSpec初始化程序的示例,其中包含用于转义“Alice”字符串值的转义字符:
initializer("\"Alice\"")
第8节中还有一个类似的例子,当我们定义注解的排除成员时:
addMember("exclude", "\"name\"")
当我们的JavaPoet代码增长并且具有大量类似的字符串转义或字符串拼接语句时,它就会变得难以处理。
JavaPoet中的字符串格式化功能使beginControlFlow()、addStatement()或initializer()方法中的字符串格式化更加容易。语法类似于Java中的String.format()功能,它可以帮助格式化文字、字符串、类型和名称。
10.1 文字格式化
JavaPoet在输出中用文字值替换$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()并指定以下值:
generateSumMethod("sumOfOneHundred", 0, 100, "+");
JavaPoet将生成以下输出:
int sumOfOneHundred() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
10.2 字符串格式化
字符串格式化会生成一个带引号的值,该值专指Java中的String类型。JavaPoet在输出中将$S替换为String值:
private static MethodSpec generateStringSupplier(String methodName, String fieldName) {
return MethodSpec
.methodBuilder(methodName)
.returns(String.class)
.addStatement("return $S", fieldName)
.build();
}
如果我们调用generateGetter()方法并提供以下值:
generateStringSupplier("getDefaultName", "Bob");
我们将得到以下生成的Java代码:
String getDefaultName() {
return "Bob";
}
10.3 类型格式化
JavaPoet在生成的Java代码中用类型替换$T,JavaPoet会自动处理import语句中的类型。如果我们将类型作为文字提供,JavaPoet将不会处理导入。
MethodSpec getCurrentDateMethod = MethodSpec
.methodBuilder("getCurrentDate")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
JavaPoet将生成以下输出:
Date getCurrentDate() {
return new Date();
}
10.4 名称格式化
如果我们需要引用变量/参数、字段或方法的名称,我们可以在JavaPoet的字符串格式化程序中使用$N。
我们可以将之前的getCurrentDateMethod()添加到新的引用方法中:
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表达式
我们可以利用已经探索过的功能来生成Lambda表达式。例如,多次打印name字段或变量的代码块:
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);
12. 使用JavaFile生成输出
JavaFile类有助于配置和生成生成代码的输出,要生成Java代码,我们只需构建JavaFile、提供包名称和TypeSpec对象的实例。
12.1 代码缩进
默认情况下,JavaPoet使用两个空格进行缩进,为了保持一致性,本教程中的所有示例都使用4个空格缩进,可通过indent()方法配置:
JavaFile javaFile = JavaFile
.builder("cn.tuyucheng.taketoday.javapoet.person", person)
.indent(" ")
.build();
12.2 静态导入
如果我们需要添加静态导入,我们可以通过调用addStaticImport()方法在JavaFile中定义类型和具体方法名称:
JavaFile javaFile = JavaFile
.builder("cn.tuyucheng.taketoday.javapoet.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 输出
writeTo()方法提供将代码写入多个目标的功能,例如标准输出流(System.out)和文件。
要将Java代码写入标准输出流,我们只需调用writeTo()方法,并提供System.out作为参数:
JavaFile javaFile = JavaFile
.builder("cn.tuyucheng.taketoday.javapoet.person", person)
.indent(" ")
.addStaticImport(Date.class, "UTC")
.addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
.build();
javaFile.writeTo(System.out);
writeTo()方法也接受java.nio.file.Path和java.io.File,我们可以提供相应的Path或File对象,以便将Java源代码文件生成到目标文件夹/路径中:
Path path = Paths.get(destinationPath);
javaFile.writeTo(path);
有关JavaFile的更多详细信息,请参阅Javadoc。
13. 总结
本文介绍了JavaPoet的功能,例如生成方法、字段、参数、类型、注解和Javadocs。
JavaPoet仅用于代码生成,如果我们想使用Java进行元编程,JavaPoet自1.10.0版本起不支持代码编译和运行。
Post Directory
