javapoet动态生成java代码

刚接触第一感觉

      动态生成java代码???java代码不就是一个后缀名为.java的txt文档吗?仔细想了下,如果要生成这样的代码的确挺麻烦,你要考虑导包的问题,以及复杂的语法


javapoet代码仓库地址
其实git地址官方介绍的demo写的很清楚,这里我只是坐下笔记

先说几个核心类
  1. MethodSpec定义方法
  2. TypeSpec定义类丶接口丶抽象类丶枚举
  3. JavaFile定义一个输出java文件
MethodSpec介绍
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介绍
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//构造一个类,类名
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//定义类的修饰符
    .addMethod(main)//添加类的方法,也就是上面生成的MethodSpec对象
    .build();


JavaFile介绍

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)//定义生成的包名,和类
    .build();

javaFile.writeTo(System.out);//输出路径,可以收一个file地址


最终生成

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

使用小技巧之占位符的使用
  • $L,我的理解就是值的使用addStatement("int value=$L", 10)
  • $S,字符串,如果被这个用这个占位符占位的会被2个双引号包裹住
  • $T,类型,这个很重要,这个会自动导包addStatement("$T obj=new $T()", Persion.class, Persion.class) 生成的就是new Persion(),接收一个class字节码
  • $N引用声明.比如你有一个MethodSpec对象,如果你想调用这个方法,下面是一个官方例子

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .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();
//生成的代码
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');
}

使用小技巧之善用循环beginControlFlow和endControlFlow
    写循环结构很忙,可以用这个
.beginControlFlow("while(value<$L)", 10)//循环开始
.addStatement("int value2=$L", 10)
.endControlFlow()
while(value<10) {
   int value2=10;
}
使用小技巧之addCode
MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "  total += i;\n"
        + "}\n")
    .build();


使用小技巧之ClassName
   前面说了$T能自动导包,能接受一个Class对象,我们可以用ClassName对象替换class对象
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");//包名和类名

MethodSpec today = MethodSpec.methodBuilder("tomorrow")
    .returns(hoverboard)
    .addStatement("return new $T()", hoverboard)
    .build();

自己写的Demo

public class Demo {
    public static void main(String args[]) {
        try {

            /**
             * $T 传入一个类型,可以使class或者ClassName对象,这个会自动帮你导入包
             * $S 替换一个字符串,会用"引号"
             * $L 就是占位一个值,一个变量
             * $N 就是占位一个方法 可以传入一个MethodSpec对象
             */
            ClassName persionClassName = ClassName.get("com.awen.demo.bean", "Persion");//这种找到的形式都和class形式一样可以用

            MethodSpec main = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                    .addParameter(String[].class, "args") //添加参数
                    .addParameter(persionClassName, "myPersion")//添加参数
                    .returns(persionClassName)
                    .addStatement("int value=$L", 10)
                    .addStatement("$T obj=new $T()", persionClassName, Persion.class)
                    .addStatement("$T.out.println($S)", System.class, "来吧创建一个类来玩玩吧!")
                    .beginControlFlow("while(value<$L)", 10)//循环开始
                    .addStatement("int value2=$L", 10)
                    .endControlFlow()
                    .addStatement("return new $T()", Class.forName("com.awen.demo.bean.Persion"))
                    .build();
            //创建变量
            FieldSpec fieldSpec = FieldSpec.builder(String.class, "VALUE")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                    .initializer("$S","AAA").build();

            TypeSpec classSpec = TypeSpec.classBuilder("Test")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addField(String.class, "name", Modifier.PUBLIC)
                    .addField(fieldSpec)
                    .addMethod(main)
//                    .addType(classSpec2)//追加一个内部类
                    .build();

            JavaFile build = JavaFile.builder("com.awen.demo", classSpec).build();

            build.writeTo(new File("src/main/java"));
        } catch (Exception e) {
            System.out.println("发生错误:" + e.getLocalizedMessage());
        }
    }
}

生成代码

public final class Test {
  public static final String VALUE = "AAA";
  public String name;
  public static Persion main(String[] args, Persion myPersion) {
    int value=10;
    Persion obj=new Persion();
    System.out.println("来吧创建一个类来玩玩吧!");
    while(value<10) {
      int value2=10;
    }
    return new Persion();
  }
}
用处感想

    大家其实大家或多或少都其实用过这玩意,只是大家用了别人的罢了,例如ButterKnife,注入框架,为何不用一个注解就不用在写findViewById了?其实无论本质还是要find的,但是为何不需要了呢?就是在编译前期自动帮你生成了部分代码!

    还有greendao,其实这个你也发现了,在你build的时候,也会生成一部分代码?怎么生成的呢?所以说这个在插件开发中还是用处很大的,还要继续学习,这个面向的java层代码的生成

我想用这个技术想做的事情

    通过定义plugin插件,可以用这个在编译前期生成部分java代码,然后在transform+asm库对class文件进行修改,插入并调用自动生成的java代码,埋点吧!很想学这个玩着个,继续努力

你可能感兴趣的:(技术学习)