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 | 字符串 |
$N | 我们自己生成的方法名或者变量名等等 |
$T | 类型 |
基础知识了解之后,剩下的就在代码中理解吧。
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;
}
怎么样,现在是不是对$S
侧重于字符串,而$L
侧重于值有了新的理解。