Poet
诗人。
JavaPoet
是一个动态生成代码的开源项目,在某些情况下具有特殊用处。Github
地址:https://github.com/square/javapoet
在Github
上有JavaPoet
的官方教程,权威且全面,因为太好了,整篇博客都参考Github上的教程。
首先,我们想用代码生成这个类,且在当前的项目下。
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet");
}
}
用JavaPoet写的话就是这样,没什么需要理解的,熟练掌握API就行。
public class TestOne {
public static void main(String[] args) throws Exception {
//构造一个方法
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 hello = TypeSpec.classBuilder("Hello") //名称
.addModifiers(Modifier.PUBLIC) //修饰
.addMethod(main) //方法
.build();
//生成一个Java文件
JavaFile javaFile = JavaFile.builder("com.xupt.willscorpio.javatest", hello)
.build();
//将java写到当前项目中
javaFile.writeTo(System.out); //打印到命令行中
File file = new File("."+"\\src\\");
if (file.exists()) {
file.delete();
}
javaFile.writeTo(file);
}
}
这是在运行之前的项目结构。
这是运行后的项目结构,可以看到的是,在项目中添加了一个Hello
类。
进去看一下,不仅自动导包了,而且也没有报错,和我们预期的一模一样,很完美。
使用JavaPoet动态生成Java文件的主体就完成了,剩下的就是填充细节,比如添加注解,JavaDoc或者更加复杂的语句。
1)常用类
类 | 含义 |
---|---|
MethodSpec | 代表一个构造函数或方法声明 |
TypeSpec | 代表一个类,接口,或者枚举声明 |
FieldSpec | 代表一个成员变量,一个字段声明 |
JavaFile | 包含一个顶级类的Java文件 |
ParameterSpec | 用来创建参数 |
AnnotationSpec | 用来创建注解 |
TypeName | 类型,如在添加返回值类型是使用 TypeName.VOID |
ClassName | 用来包装一个类 |
2)占位符
占位符 | 含义 |
---|---|
$L | 参数 |
$S | 字符串的模板,将指定的字符串替换到$S的地方 |
$N | $N在JavaPoet中代指的是一个名称,例如调用的方法名称,变量名称,这一类存在意思的名称 |
$T | $T 在JavaPoet代指的是TypeName,该模板主要将Class抽象出来,用传入的TypeName指向的Class来代替 |
基础知识了解之后,剩下的就在代码中理解吧。
3)大括号
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
如果现在我们想生成这个函数应该怎么做。
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode("int total = 0;\n" +
" for (int i = 0; i < 10; i++) {\n" +
" total += i;\n" +
" }")
.build();
我将函数体全部复制到了addCode()
中,虽然简便而且可以达到目的,但是不够灵活,有没有更好的方法。
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for(int i=0;i<10;i++)")
.addStatement("total +=i;")
.endControlFlow()
.build();
这次我们用语句写,beginControlflow()
代表大括号的开始,endControlFlow()
则代表大括号的结束,而且中间可以添加语句。但还是不够灵活,我们想把其中的数值变一下,可以吗。
int from = 1;
int to = 100;
String op = "*";
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for(int i=" + from + ";i<" + to + ";i++)")
.addStatement("total = total "+op+ " i;")
.endControlFlow()
.build();
现在倒是很灵活,但是可读性也太差了,这时前面的占位符就排上用场了。
int from = 1;
int to = 100;
String op = "*";
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for(int i=$L;i<$L;i++)",from,to)
.addStatement("total = total $L i;",op)
.endControlFlow()
.build();
灵活清晰又美观,使用$L
代替。
4)占位符的使用
$L
在前面说过了,现在看一下$S
,$S
比较简单,字符串占位。
TypeSpec hello = TypeSpec.classBuilder("Hello")
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethod("One"))
.addMethod(generateMethod("Two"))
.addMethod(generateMethod("Three"))
.build();
private static MethodSpec generateMethod(String name) {
MethodSpec methodSpec = MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return $S",name)
.build();
return methodSpec;
}
我们返回的数据使用$S
占位输出,结果如下。
public class Hello {
String One() {
return "One";
}
String Two() {
return "Two";
}
String Three() {
return "Three";
}
}
那·$S
与$L
有什么区别呢,$S
侧重于字符串,而$L
侧重于值,我们做下面这样一个实验,将代码改为这样,用$L
代替$S
。
private static MethodSpec generateMethod(String name) {
MethodSpec methodSpec = MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return $L",name)
.build();
return methodSpec;
}
结果如下,报错了。
那正确的应该是怎样的,改成这样就行了。
private static MethodSpec generateMethod(String name) {
MethodSpec methodSpec = MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return \"$L\"",name)
.build();
return methodSpec;
}
$L
与$S
在前面我们都说过了,接下来就是$T
,代表类,我们在前面也用到了这个占位符,System.out.println()
中的System
我们就用$T
进行了占位。那我现在有一个问题,$T
可以用$T
替换吗?
我们先生成一个Say
类,放在其他目录下。
public static void say() {
System.out.println("Hello,JavaPoet");
}
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class,"args")
.addStatement("$L.say()","Say")
.build();
我们不用$T
而是用$L
,看一下生成后的结果。
报红了,因为没有自动导包,那我们用$T
代替$L
试一下。
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class,"args")
.addStatement("$T.say()", Say.class)
.build();
看来用到类的时候,必须要用$T
,因为这牵扯到导包的问题,当然,如果在同一目录下,不用导包的情况下,也可以用$L
代替$T
。
对于类型,我们还有另外一种写法。
如果我们现在准备下面这段代码应该怎么做。
public class Hello {
public static void main(String[] args) {
List<Say> list = new ArrayList<>();
list.add(new Say());
list.add(new Say());
list.add(new Say());
System.out.println(list.size());
}
}
ClassName say = ClassName.get(Say.class);
ClassName list = ClassName.get(List.class);
ClassName arrayList = ClassName.get(ArrayList.class);
TypeName listOfSay = ParameterizedTypeName.get(list, say);
//构造一个方法
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addStatement("$T list = new $T()", listOfSay, arrayList)
.addStatement("list.add(new $T())", say)
.addStatement("list.add(new $T())", say)
.addStatement("list.add(new $T())", say)
.addStatement("System.out.print(list.size())")
.build();
可以看到没有出问题,泛型的话我们可以用ParameterizedTypeName
这个类来生成,我们也可以用ClassName
为类生成别名,便于使用,因为System
这个类,没有导包的问题,而且是固定的代码,所以可以直接写成字符串。
接下来是$N
,$N
在JavaPoet
中代指的是一个名称,例如调用的方法名称,变量名称,这一类存在意思的名称。使用比较广范,也可以与其他占位符替代使用。
public class Hello {
public static void main(String[] args) {
System.out.println("Start");
say("One");
say("Two");
say("Three");
System.out.println("End");
}
private static void say(String string) {
System.out.println(string);
}
}
在这里我们用$N
占位符。
MethodSpec say = MethodSpec.methodBuilder("say")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.returns(void.class)
.addParameter(String.class, "string")
.addStatement("System.out.println(string)")
.build();
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("System.out.println(\"Start\")")
.addStatement("$N(\"One\")",say)
.addStatement("$N(\"Two\")",say)
.addStatement("$N(\"Three\")",say)
.addStatement("System.out.println(\"End\")")
.build();
TypeSpec hello = TypeSpec.classBuilder("Hello")
.addModifiers(Modifier.PUBLIC)
.addMethod(main)
.addMethod(say)
.build();
因为在main
方法中调用了say
方法,所以要将say
放在前面,这样才可以调用。
$N
可以被$L
等代替使用。
MethodSpec say = MethodSpec.methodBuilder("say")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.returns(void.class)
.addParameter(String.class, "string")
.addStatement("System.out.println(string)")
.build();
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("System.out.println(\"Start\")")
.addStatement("$L(\"One\")","say")
.addStatement("$L(\"Two\")","say")
.addStatement("$L(\"Three\")","say")
.addStatement("System.out.println(\"End\")")
.build();
TypeSpec hello = TypeSpec.classBuilder("Hello")
.addModifiers(Modifier.PUBLIC)
.addMethod(main)
.addMethod(say)
.build();
我们使用$L
代替$N
来使用,同样可以完成功能。
常用的已经差不多了,还是继续推荐官方教程。https://github.com/square/javapoet