JavaPoet简介

时间:2024-05-23 08:55:48

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);
    }
}

这是在运行之前的项目结构。
JavaPoet简介
这是运行后的项目结构,可以看到的是,在项目中添加了一个Hello类。
JavaPoet简介
进去看一下,不仅自动导包了,而且也没有报错,和我们预期的一模一样,很完美。
JavaPoet简介
使用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;
    }

结果如下,报错了。
JavaPoet简介
那正确的应该时怎样的,改成这样就行了。

private static MethodSpec generateMethod(String name) {
        MethodSpec methodSpec = MethodSpec.methodBuilder(name)
                .returns(String.class)
                .addStatement("return \"$L\"",name)
                .build();
        return methodSpec;
    }

怎么样,现在是不是对$S侧重于字符串,而$L侧重于值有了新的理解。