一、介绍
JavaPoet是一个用于生成. Java源文件的Java API。
gitHub:https://github.com/square/javapoet
文档地址:https://square.github.io/javapoet/1.x/javapoet/
在进行注释处理或与元数据文件(例如,数据库模式、协议格式)交互时,源文件生成非常有用。通过生成代码,您可以消除编写样板文件的需要,同时为元数据保留一个真实的源。
MethodSpec:
生成的构造函数或方法声明。
TypeSpec:
生成的类、接口或枚举声明。
JavaFile:
包含一个顶层类的Java文件。
1、例子:自动生成一个helloWorld 的main 方法
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);
}
二、代码与流程控制
$L发出一个没有转义的文字值。文字的参数可以是字符串、原语、类型声明、注释,甚至是其他代码块。
$N发出一个名称,在必要时使用名称避免冲突。名称的参数可以是字符串(实际上是任何字符序列)、参数、字段、方法和类型。
$S将值转义为字符串,用双引号括起来,并发出该值。例如,6" sandwich发出"6" sandwich"。
$T发出类型引用。如果可能,将导入类型。类型的参数可以是类、*类型镜像和元素。
$$发出美元符号。
$W根据其在行的位置发出空格或换行。这更喜欢将行包装在100列之前。
$Z充当一个零宽度的空间。这更喜欢将行包装在100列之前。
$>增加缩进级别。
$<降低缩进级别。
$[开始一个语句。对于多行语句,第一行之后的每一行都是双缩进的。
$]结束语句
JavaPoet 的大多数API 使用普通的旧的不可变Java对象。还有构建器、方法链接和varargs使API更加友好。JavaPoet 为类和接口(TypeSpec)、字段(FieldSpec)、方法和狗仔器(MethodSpec)、参数(ParameterSpec)和注释(AnnotationSpec)提供模型。
但是方法和构造函数主体没有建模。没有表达式类、语句类的相关树接节点,相反JavaPoet对代码块使用字符串
private static void testFor() throws IOException {
MethodSpec main = MethodSpec.methodBuilder("main").addCode("int total=0;\n for(int i=0;i<10;i++){ \n" +
" total += i;\n}\n").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);
}
手工分号、换行和缩进都很繁琐,因此JavaPoet提供了api来简化这些操作。addStatement()负责分号和换行,beginControlFlow + endControlFlow()用户大括号、换行和缩进
private static void testFor1() throws IOException {
MethodSpec main = MethodSpec.methodBuilder("main").addStatement("int total = 00")
.beginControlFlow("for(int i = 0;i < 10; i++")
.addStatement("total += i")
.endControlFlow().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);
}
一些控制流语句,例如if/else,可以有无限的控制流可能性。您可以使用nextControlFlow()处理这些选项:
private static void testFor1() throws IOException {
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();
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);
}
使用try/catch捕捉异常也是nextControlFlow()的一个用例:
private static void testFor2() throws IOException {
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();
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);
}
$L for Literals
调用beginControlFlow()和addStatement中的字符串连接会分散注意力。太多的运营商。为了解决这个问题,JavaPoet提供了一种语法,这种语法受到String.format()的启发,但又不兼容。它接受$L在输出中发出一个文字值。这就像Formatter的%s:
private static void testFor3() throws IOException {
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(computeRange("main", 0, 10, "*"))
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
}
private static 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 for Strings
当发出包含字符串文本的代码时,我们可以使用$S来发出一个字符串,并使用引号括起来并转义。
private static void testFor4() throws IOException {
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(whatsMyName("hello"))
.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();
}
$T for Types
我们Java程序员喜欢我们的类型:它们使我们的代码更容易理解。JavaPoet也加入了。它对类型有丰富的内置支持,包括自动生成导入语句。只需使用$T引用类型:
private static void testForType() throws IOException {
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);
}
我们传递Date.class来引用一个恰好在生成代码时可用的类。其实并不需要这样。下面是一个类似的例子,但是这个例子引用了一个不存在的类:
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
MethodSpec today = MethodSpec.methodBuilder("tomorrow")
.returns(hoverboard)
.addStatement("return new $T()", hoverboard)
.build();
ClassName类型非常重要,在使用JavaPoet时经常需要它。它可以标识任何已声明的类。声明类型只是Java丰富类型系统的开始:我们还有数组、参数化类型、通配符类型和类型变量。JavaPoet有用于构建每个类的类:
private static void testForType1() throws IOException {
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();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(beyond)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
}
Import static
JavaPoet支持导入静态。它通过显式收集类型成员名来实现这一点。让我们用一些静态的糖来增强前面的例子:
private static void testForType2() throws IOException {
ClassName className = ClassName.get("com.union.xiaobo", "TestForTyype");
ClassName list = ClassName.get("java.util","List");
ClassName arrayList = ClassName.get("java.util","ArrayList");
ClassName namedBoards = ClassName.get("com.union.xiaobo", "TestForTyype", "Boards");
TypeName typeName = ParameterizedTypeName.get(list,className);
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(typeName)
.addStatement("$T result = new $T<>()", typeName, arrayList)
.addStatement("result.add($T.createNimbus(2000))", className)
.addStatement("result.add($T.createNimbus(\"2001\"))", className)
.addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", className, namedBoards)
.addStatement("$T.sort(result)", Collections.class)
.addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(beyond)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.addStaticImport(className,"createNimbus")
.addStaticImport(className,"namedBoards","*")
.addStaticImport(Collections.class," * ")
.build();
javaFile.writeTo(System.out);
}
JavaPoet将首先按照配置将导入静态块添加到文件中,然后匹配和错误处理所有调用,并根据需要导入所有其他类型。
$N for Names
生成的代码通常是自引用的。使用$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()方法:
private static void testNames() throws IOException {
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.returns(char.class)
.addParameter(int.class, "i")
.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();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(byteToHex)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
}
代码块格式字符串
代码块可以通过几种方式指定占位符的值。代码块上的每个操作只能使用一种样式。
相关参数
将格式字符串中每个占位符的参数值传递给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)
三、方法
上面所有的方法都有一个代码体。使用修饰符。得到一个没有任何主体的方法。只有当所包含的类是抽象类或接口时,这才是合法的。
MethodSpec flux = MethodSpec.methodBuilder("flux")
.addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addMethod(flux)
.build();
其他修饰符在允许的地方工作。注意,在指定修饰符时,JavaPoet使用javax.lang.model.element。修饰符,Android上不可用的类。此限制仅适用于代码生成;输出代码可以在任何地方运行:jvm、Android和GWT。
方法还具有参数、异常、varargs、Javadoc、注释、类型变量和返回类型。所有这些都配置了MethodSpec.Builder。
四、构造函数
MethodSpec是一个有点用词不当的词;它也可以用于构造函数:
private static void testConstructors() throws IOException {
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();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
}
五、参数
使用ParameterSpec.builder()或MethodSpec方便的addParameter() API在方法和构造函数上声明参数:
private static void testParameters() throws IOException {
ParameterSpec android = ParameterSpec.builder(String.class,"android",Modifier.PUBLIC).build();
MethodSpec methodSpec = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(android)
.addParameter(String.class,"roboot",Modifier.FINAL)
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("Text").addMethod(methodSpec).build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}
六、字段
与参数一样,字段可以使用构造器创建,也可以使用方便的helper方法创建:
private static void testField() throws IOException {
FieldSpec fieldSpec = FieldSpec.builder(String.class,"android",Modifier.FINAL)
.initializer("$S + $L","Lollipop V", 5.0d)
.build();
MethodSpec methodSpec = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(String.class,"roboot",Modifier.FINAL)
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("Text").addMethod(methodSpec).addField(fieldSpec).build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}
七、接口
JavaPoet在接口方面没有问题。注意,接口方法必须始终是公共抽象,接口字段必须始终是公共静态FINAL。这些修饰符在定义接口时是必要的:
private static void testInterface() throws IOException {
MethodSpec methodSpec = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(String.class,"roboot",Modifier.FINAL)
.build();
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();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",helloWorld).build();
javaFile.writeTo(System.out);
}
八、枚举
使用enumBuilder创建枚举类型,并为每个值添加addEnumConstant():
private static void testEnum() throws IOException {
TypeSpec typeSpec = TypeSpec.enumBuilder("Roshambo").addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}
支持花式枚举,其中枚举值覆盖方法或调用超类构造函数。下面是一个综合的例子:
private static void testEnum1() throws IOException {
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();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",helloWorld).build();
javaFile.writeTo(System.out);
}
九、匿名内部类
在enum代码中,我们使用了TypeSpec.anonymousInnerClass()。匿名内部类也可以在代码块中使用。这些值可以用$L引用:
private static void testAnonymous() throws IOException {
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();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",helloWorld).build();
javaFile.writeTo(System.out);
}
十、注解
简单的注解很容易:
private static void testAnnotations() throws IOException {
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hoverboard")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("TestAnnotations").addMethod(toString)
.build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}
使用AnnotationSpec.builder()在注释上设置属性:
private static void testAnnotations() throws IOException {
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(String.class)
.build();
TypeSpec typeSpec = TypeSpec.annotationBuilder("TestAnnotations").addMethod(logRecord)
.build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}
当您开始使用时,注释值可以是注释本身。使用$L嵌入注释:
private static void testAnnotations1() throws IOException {
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(String.class)
.build();
TypeSpec typeSpec = TypeSpec.annotationBuilder("TestAnnotations").addMethod(logRecord)
.build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}
十一、Javadoc
字段、方法和类型可以用Javadoc记录:
private static void testDoc() throws IOException {
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();
TypeSpec typeSpec = TypeSpec.classBuilder("testDoc").addMethod(dismiss)
.build();
JavaFile javaFile = JavaFile.builder("com.union.xiaobo",typeSpec).build();
javaFile.writeTo(System.out);
}