编译期注解学习六- 生成java文件javapoet

1 生成文件方式

编译期注解通过读取注解,然后处理注解字段Element,最终的目的是在编译代码之前生成所需源码文件作为工具类,减少手动书写代码。
生成文件的方式有多种,例如StringBuilder进行拼接,模板文件进行字段替换,javaPoet 生成。
模板生成代码
StringBuilder进行拼接,模板文件进行字段替换进行简单文件生成还好,如果是复杂文件,拼接起来会相当复杂如下所示:

  private String brewCode(String className, String pkName, ArrayList mElementList) {
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkName + ";\n\n");
        builder.append("public class " + className + "$ViewBinding implements com.ldx.injectlib.InjectIoc { \n\n");
        builder.append("@Override\n\n");
        builder.append("public void inject("+"Object"+" obj1"+"){ \n\n");
        builder.append("        "+pkName+"."+className+" obj" +" = "+"("+pkName+"."+className+")"+"obj1;\n\n");
        for (VariableElement element : mElementList){
            //3.获取注解的成员变量名
            String bindViewFiledName = element.getSimpleName().toString();
            //变量类型
            String bindViewFiledClassType = element.asType().toString();
            BindView bindAnnotation = element.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            String info = String.format("%s %s = findViewById(%d)", bindViewFiledClassType, bindViewFiledName, id);
            builder.append("        System.out.println(\"" + info + "\");\n\n");
            builder.append("        obj."+bindViewFiledName+" = "+"obj.findViewById("+id+");"+"\n\n");
        }

        builder.append("}\n\n");
        builder.append("}");
        return builder.toString();
    }

javaPoet生成代码是Square出品的sdk,学习成本较小,用起来相当顺手。
简单代码示例

private void createClass(){
        //https://github.com/square/javapoet
        try {
            MethodSpec methodSpec = MethodSpec.methodBuilder("main")
                    .addModifiers( javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class,"args")
                    .addStatement("$T.out.println($S)",System.class,"hello world!")
                    .build();
            TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers( javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.FINAL)
                    .addMethod(methodSpec)
                    .build();

            JavaFile javaFile = JavaFile.builder("com.ldx.canvasdrawdemo", typeSpec)
                    .build();

            mMessager.printMessage(Diagnostic.Kind.NOTE, javaFile.toString()+"");
            javaFile.writeTo(mFilerUtils);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

最终生成的文件:

package com.ldx.canvasdrawdemo;
 
  import java.lang.String;
  import java.lang.System;
  
  public final class HelloWorld {
    public static void main(String[] args) {
      System.out.println("hello world!");
    }
  }

2 javapoet 使用讲解

需要引入javapoet的库 implementation 'com.squareup:javapoet:1.11.1';

本篇大致讲解,详细内容参照(https://github.com/square/javapoet),一般生成代码的方式为从Class开始,缺少什么在前面生成对应的代码模块,add到classtype中最终完成class的生成。

常用到的api:

用到的类(多为建造者模式,链式调用):

  • TypeSpec:用于生成类、接口、枚举对象的类(class interface enum)
  • MethodSpec:用于生成方法对象的类(生成method)
  • ParameterSpec:用于生成参数对象的类(方法的参数)
  • AnnotationSpec:用于生成注解对象的类(生成注解)
  • FieldSpec:用于配置生成成员变量的类(成员变量)
  • ClassName:通过包名和类名生成的对象,指明为某个Class,功能类似xxx.class
  • ParameterizedTypeName:通过MainClass和IncludeClass生成包含泛型的Class

用到的方法:

创建类:
TypeSpec.classBuilder("类名“)
TypeSpec.classBuilder(ClassName className)

创建接口:
TypeSpec.interfaceBuilder("接口名称")
TypeSpec.interfaceBuilder(ClassName className)

创建枚举:
TypeSpec.enumBuilder("枚举名称")
TypeSpec.enumBuilder(ClassName className)

添加修饰符,public,static,final,private ,protected
addModifiers(Modifier... modifiers)

举例:

   private void test2(){
        TypeSpec myInterface = TypeSpec.interfaceBuilder("MyInterface")
                .addModifiers(Modifier.PUBLIC)
                .addField(FieldSpec.builder(String.class, "name")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .initializer("$S", "liming")
                        .build())
                .addMethod(MethodSpec.methodBuilder("getName")
                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
                        .build())
                .build();

        JavaFile file1 = JavaFile.builder("com.ldx.canvasdrawdemo", myInterface).build();

        mMessager.printMessage(Diagnostic.Kind.NOTE, file1.toString()+"");

        TypeSpec myEnum = TypeSpec.enumBuilder("Range")
                .addModifiers(Modifier.PUBLIC)
                .addEnumConstant("ONE")
                .addEnumConstant("TWO")
                .addEnumConstant("THREE")
                .build();

        JavaFile file2 = JavaFile.builder("com.ldx.canvasdrawdemo", myEnum).build();

        mMessager.printMessage(Diagnostic.Kind.NOTE, file2.toString()+"");

        MethodSpec methodSpec = MethodSpec.methodBuilder("main")
                .addModifiers( javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC)
                .returns(void.class)
                .addParameter(String[].class,"args")
                .addStatement("$T.out.println($S)",System.class,"hello world!")
                .build();
        TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")
                .addModifiers( javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.FINAL)
                .addMethod(methodSpec)
                .build();

        JavaFile javaFile3 = JavaFile.builder("com.ldx.canvasdrawdemo", typeSpec)
                .build();

        mMessager.printMessage(Diagnostic.Kind.NOTE, javaFile3.toString()+"");

    }

生成的文件:

 package com.ldx.canvasdrawdemo;
  import java.lang.String;
  public interface MyInterface {
    String name = "liming";
    void getName();
  }
  
 package com.ldx.canvasdrawdemo;
  public enum Range {
    ONE,
    TWO,
    THREE
  }
  
 package com.ldx.canvasdrawdemo;
  import java.lang.String;
  import java.lang.System;
  public final class HelloWorld {
    public static void main(String[] args) {
      System.out.println("hello world!");
    }
  }

继承类:
.superclass(ClassName className)
实现接口
.addSuperinterface(ClassName className)
当继承父类存在泛型时,需要使用ParameterizedTypeName
ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments)

 ClassName superClass = ClassName.get("android.support.v7.app","AppCompatActivity");
        ClassName onClickClass = ClassName.get("android.view", "View.OnClickListener");

        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TwoActivity")
                .addModifiers(Modifier.PUBLIC)
                .superclass(superClass)
                .addSuperinterface(onClickClass);

        TypeSpec mainActivity = typeSpecBuilder
                .build();

        JavaFile file = JavaFile.builder("com.ldx.canvasdrawdemo", mainActivity).build();

        mMessager.printMessage(Diagnostic.Kind.NOTE, file.toString()+"");

生成代码

package com.ldx.canvasdrawdemo;
  
  import android.support.v7.app.AppCompatActivity;
  import android.view.View.OnClickListener;
  
  public class TwoActivity extends AppCompatActivity implements View.OnClickListener {
  }

创建函数
MethodSpec.methodBuilder(String name)
创建构造方法
MethodSpec.constructorBuilder()

添加方法
addMethod(MethodSpec methodSpec)

为方法添加参数
addParameter(ParameterSpec parameterSpec)
设置返回值
returns(TypeName returnType)

举例:

   ClassName strclass = ClassName.get("java.lang", "String");

        TypeSpec myclass = TypeSpec.classBuilder("MyClass")
                .addModifiers(Modifier.PUBLIC)
                .addMethod(MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(strclass,"myStr")
                        .build())
                .addMethod(MethodSpec.methodBuilder("myMethod")
                         .addModifiers(Modifier.PRIVATE)
                        .returns(strclass)
                        .addStatement("return $S","name")
                        .build())
                .build();


        JavaFile file = JavaFile.builder("com.ldx.canvasdrawdemo", myclass).build();

        mMessager.printMessage(Diagnostic.Kind.NOTE, file.toString()+"");

生成的代码

package com.ldx.canvasdrawdemo;
  
  import java.lang.String;
  
  public class MyClass {
    public MyClass(String myStr) {
    }
  
    private String myMethod() {
      return "name";
    }
  }

为方法体添加代码
addCode()
addCode(String format, Object... args)
addStatement()
addStatement(String format, Object... args)

addCode 和addStatement的区别是addCode会把添加的String原封不动的作为代码,addState则会帮助添加;等操作。

JavaPoet中,format中存在三种特定的占位符:
$T 在JavaPoet代指的是TypeName,该模板主要将Class抽象出来,用传入的TypeName指向的Class来代替。
$N在JavaPoet中代指的是一个名称,例如调用的方法名称,变量名称,这一类存在意思的名称
$S & $L
$S在JavaPoet中会利用指定字符替换$S的地方,替换后的内容,$S自带了双引号,$L不自带双引号
抛出异常
.addException(TypeName name)

  MethodSpec onClick = MethodSpec.methodBuilder("onClick")
                    .addAnnotation(override)
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(viewParameter)
                    .addJavadoc("自动生成代码,勿删")
                    .addCode("String name = \"xiaoming\";\n\n")
                    .addStatement("$T good = \"\"",String.class)
                    .addStatement("$T twoView = null",viewClass)
                    .addStatement("good = name.$N()","toString")
                    .addStatement("twoView = new $T(this)",viewClass)
                    .addStatement("$T lll = $S",String.class,"lllll")
                    .addStatement("$T tttt = $L",int.class,22)
                    .build();

生成注解
AnnotationSpec.builder(override).build()

  ClassName override = ClassName.get("java.lang", "Override");

            ClassName bundle = ClassName.get("android.os", "Bundle");

            ClassName nullable = ClassName.get("android.support.annotation", "Nullable");

            AnnotationSpec overRideAnno = AnnotationSpec.builder(override).build();
            AnnotationSpec nullableAnno = AnnotationSpec.builder(nullable).build();

标识class

ClassName superClass = ClassName.get("android.support.v7.app","AppCompatActivity");
 ClassName onClickClass = ClassName.get("android.view", "View.OnClickListener");
 ClassName viewClass = ClassName.get("android.view", "View");

生成成员变量

 Class stringClazz = String.class;
 FieldSpec fieldSpec = FieldSpec.builder(stringClazz, "mName", Modifier.PRIVATE).build();
  FieldSpec fieldSpec2 = FieldSpec.builder(stringClazz, "mStr2")
                    .addModifiers(Modifier.PUBLIC)
                    .build();

设置注解
addAnnotation(AnnotationSpec annotationSpec)
addAnnotation(ClassName annotation)
addAnnotation(Class annotation)

            ClassName override = ClassName.get("java.lang", "Override");
            ClassName bundle = ClassName.get("android.os", "Bundle");
            ClassName nullable = ClassName.get("android.support.annotation", "Nullable");
            AnnotationSpec overRideAnno = AnnotationSpec.builder(override).build();
            AnnotationSpec nullableAnno = AnnotationSpec.builder(nullable).build();

            ParameterSpec savedInstanceState = ParameterSpec.builder(bundle, "savedInstanceState")
                    .addAnnotation(nullable)
                   // .addAnnotation(nullableAnno)//这种方式添加也可以
                    .build();

            ParameterSpec viewParameter = ParameterSpec.builder(viewClass, "view")
                    .build();

            MethodSpec onCreate = MethodSpec.methodBuilder("onCreate")
                    .addAnnotation(override)
                   // .addAnnotation(overRideAnno)//这种方式添加也可以
                    .addModifiers(Modifier.PROTECTED)
                    .addParameter(savedInstanceState)
                    .addStatement("super.onCreate(savedInstanceState)")
                    .addStatement("setContentView(R.layout.activity_main)")
                    .build();

            MethodSpec onClick = MethodSpec.methodBuilder("onClick")
                    .addAnnotation(override)//添加注解
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(viewParameter)
                    .addJavadoc("自动生成代码,勿删")
                    .addCode("String name = \"xiaoming\";\n\n")
                    .addStatement("$T good = \"\"",String.class)
                    .addStatement("$T twoView = null",viewClass)
                    .addStatement("good = name.$N()","toString")
                    .addStatement("twoView = new $T(this)",viewClass)
                    .addStatement("$T lll = $S",String.class,"lllll")
                    .addStatement("$T tttt = $L",int.class,22)
                    .build();

            MethodSpec test = MethodSpec.methodBuilder("test")
                    .addModifiers(Modifier.PRIVATE)
                    .addCode("int count = 0;\n")
                    .beginControlFlow("for(int i=0;i<5;i++)")
                    .addStatement("count = count + i")
                    .endControlFlow()
                    .build();

生成的代码:

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  /**
   * 自动生成代码,勿删 */
  @Override
  public void onClick(View view) {
    String name = "xiaoming";

    String good = "";
    View twoView = null;
    good = name.toString();
    twoView = new View(this);
    String lll = "lllll";
    int tttt = 22;
  }

添加注释:
addJavadoc(String format, Object... args)
.addJavadoc("自动生成代码,勿删")

流程控制:
beginControlFlow,endControlFlow会自动添加{}

   MethodSpec test = MethodSpec.methodBuilder("test")
                    .addModifiers(Modifier.PRIVATE)
                    .addCode("int count = 0;\n")
                    .beginControlFlow("for(int i=0;i<5;i++)")
                    .addStatement("count = count + i")
                    .endControlFlow()
                    .build();
 private void test() {
    int count = 0;
    for(int i=0;i<5;i++) {
      count = count + i;
    }
  }

生成文件
JavaFile负责生成最终的java文件内容,可以输出到所需地方,toString会以String输出:

JavaFile.builder(String packageName, TypeSpec typeSpec)
JavaFile通过向build方法传入PackageName(Java文件的包名)、TypeSpec(生成的内容)生成。

javaFile.writeTo(System.out)
生成的内容会输出到控制台中

javaFile.writeTo(File file)
生成的内容会存放到存储的java文件中。

  JavaFile file = JavaFile.builder("com.ldx.canvasdrawdemo", mainActivity).build();
            mMessager.printMessage(Diagnostic.Kind.NOTE, file.toString()+"");
            file.writeTo(mFilerUtils);

3 简单举例

public void createActivity(){

        try {
            ClassName superClass = ClassName.get("android.support.v7.app","AppCompatActivity");
            ClassName onClickClass = ClassName.get("android.view", "View.OnClickListener");
            ClassName viewClass = ClassName.get("android.view", "View");

            TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TwoActivity")
                    .addModifiers(Modifier.PUBLIC)
                    .superclass(superClass)
                    .addSuperinterface(onClickClass);

            ClassName override = ClassName.get("java.lang", "Override");

            ClassName bundle = ClassName.get("android.os", "Bundle");

            ClassName nullable = ClassName.get("android.support.annotation", "Nullable");

            AnnotationSpec overRideAnno = AnnotationSpec.builder(override).build();
            AnnotationSpec nullableAnno = AnnotationSpec.builder(nullable).build();

            ParameterSpec savedInstanceState = ParameterSpec.builder(bundle, "savedInstanceState")
                    .addAnnotation(nullable)
                   // .addAnnotation(nullableAnno)
                    .build();

            ParameterSpec viewParameter = ParameterSpec.builder(viewClass, "view")
                    .build();

            MethodSpec onCreate = MethodSpec.methodBuilder("onCreate")
                    .addAnnotation(override)
                   // .addAnnotation(overRideAnno)
                    .addModifiers(Modifier.PROTECTED)
                    .addParameter(savedInstanceState)
                    .addStatement("super.onCreate(savedInstanceState)")
                    .addStatement("setContentView(R.layout.activity_main)")
                    .build();

            MethodSpec onClick = MethodSpec.methodBuilder("onClick")
                    .addAnnotation(override)
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(viewParameter)
                    .addJavadoc("自动生成代码,勿删")
                    .addCode("String name = \"xiaoming\";\n\n")
                    .addStatement("$T good = \"\"",String.class)
                    .addStatement("$T twoView = null",viewClass)
                    .addStatement("good = name.$N()","toString")
                    .addStatement("twoView = new $T(this)",viewClass)
                    .addStatement("$T lll = $S",String.class,"lllll")
                    .addStatement("$T tttt = $L",int.class,22)
                    .build();

            MethodSpec test = MethodSpec.methodBuilder("test")
                    .addModifiers(Modifier.PRIVATE)
                    .addCode("int count = 0;\n")
                    .beginControlFlow("for(int i=0;i<5;i++)")
                    .addStatement("count = count + i")
                    .endControlFlow()
                    .build();

            Class stringClazz = String.class;
            FieldSpec fieldSpec = FieldSpec.builder(stringClazz, "mName", Modifier.PRIVATE).build();

            FieldSpec fieldSpec2 = FieldSpec.builder(stringClazz, "mStr2")
                    .addModifiers(Modifier.PUBLIC)
                    .build();

            TypeSpec mainActivity = typeSpecBuilder.addMethod(onCreate)
                    .addMethod(onClick)
                    .addMethod(test)
                    .addField(fieldSpec)
                    .addField(fieldSpec2)
                    .build();

            JavaFile file = JavaFile.builder("com.ldx.canvasdrawdemo", mainActivity).build();

            mMessager.printMessage(Diagnostic.Kind.NOTE, file.toString()+"");

            file.writeTo(mFilerUtils);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

生成文件:

image

package com.ldx.canvasdrawdemo;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import java.lang.Override;
import java.lang.String;

public class TwoActivity extends AppCompatActivity implements View.OnClickListener {
  private String mName;

  public String mStr2;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  /**
   * 自动生成代码,勿删 */
  @Override
  public void onClick(View view) {
    String name = "xiaoming";

    String good = "";
    View twoView = null;
    good = name.toString();
    twoView = new View(this);
    String lll = "lllll";
    int tttt = 22;
  }

  private void test() {
    int count = 0;
    for(int i=0;i<5;i++) {
      count = count + i;
    }
  }
}

其他:
匿名内部类:TypeSpec.anonymousInnerClass()

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

生成的文件:

void sortByLength(List strings) {
  Collections.sort(strings, new Comparator() {
    @Override
    public int compare(String a, String b) {
      return a.length() - b.length();
    }
  });
}

更多api参阅:https://github.com/square/javapoet

你可能感兴趣的:(编译期注解学习六- 生成java文件javapoet)