JavaPoet使用攻略

JavaPoet是用于生成.java源文件的库 。通过自动生成代码,可以替代许多重复的工作。在安卓项目中添加依赖时,凡是需要添加apt、kpt、annotationProcessor这种前缀标识时,说明其内部使用到了注解处理器,也基本可以确定使用到了JavaPoetKotlinPoet,例如大名鼎鼎的ButterknifeDagger就使用到了JavaPoet

我在两三年前也有使用注解处理器和JavaPoet写过一个Saber的库,在前一阵的维护中,突然对JavaPoet有些生疏所以想着记录总结一下,以便以后使用时快速上手。

1.准备

添加依赖:

dependencies {
     
    ...
    implementation 'com.squareup:javapoet:1.13.0'
}

2.基本使用

既然是生成java文件,那么需要的基本元素就包括:

  • 创建字段(属性)
  • 创建方法
  • 创建类、接口或枚举
  • 输出文件

我们就按上面的顺序来一次介绍:

FieldSpec

FieldSpec就是用来创建字段的类,使用起来很简单:

FieldSpec.builder(String.class, "android").build()

以上代码等价于String android;

如果添加修饰符可以使用addModifiers,例如:

FieldSpec.builder(String.class, "android")
	.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
	.build()
// private final String android;

其中Modifier包括我们用到的所有修饰符,下面介绍的类,方法都是使用它:

public enum Modifier {
     
    /** The modifier {@code public} */          PUBLIC,
    /** The modifier {@code protected} */       PROTECTED,
    /** The modifier {@code private} */         PRIVATE,
    /** The modifier {@code abstract} */        ABSTRACT,
    /**
     * The modifier {@code default}
     * @since 1.8
     */
     DEFAULT,
    /** The modifier {@code static} */          STATIC,
    /** The modifier {@code final} */            FINAL,
    /** The modifier {@code transient} */       TRANSIENT,
    /** The modifier {@code volatile} */        VOLATILE,
    /** The modifier {@code synchronized} */    SYNCHRONIZED,
    /** The modifier {@code native} */          NATIVE,
    /** The modifier {@code strictfp} */        STRICTFP;
}

如果需要初始化参数,使用initializer,例如:

FieldSpec.builder(String.class, "android")
	.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
	.initializer("$S", "Android")
	.build()
// private final String android = "Android";

MethodSpec

MethodSpec用来创建方法,一个最简单的方法包含方法名、返回类型。

MethodSpec.methodBuilder("test")
	.returns(void.class)
	.build()
// 	void test() {}

添加修饰符同上,这里不重复说明,如果添加方法参数,需要使用addParameter

MethodSpec.methodBuilder("test")
	.addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "str")
	.returns(void.class)
	.build()
	
/*public void test(String str) {
}*/

最后就是给方法添加语句,需要使用addStatement

MethodSpec.methodBuilder("test")
	.addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "str")
    .addStatement("System.out.println(str)")
	.returns(void.class)
	.build()
	
/*public void test(String str) {
	System.out.println(str);
}*/

TypeSpec

TypeSpec用来创建类,接口,或者枚举。

// class Test {}
TypeSpec.classBuilder("Test").build();

//interface Test {}
TypeSpec.interfaceBuilder("Test").build();

/*
enum Test {
    ONE
}*/
TypeSpec typeSpec = TypeSpec.enumBuilder("Test").addEnumConstant("ONE").build();

JavaFile

JavaFile用于输出包含单个顶级类的Java文件。

JavaFile.builder("com.weilu.test", typeSpec).build();

用法很简单,指定包名和顶级类即可。

到这里,我们就可以将上面的用法串起来了:

class Test {
     
    public static void main(String[] args) {
     
        FieldSpec fieldSpec = FieldSpec.builder(String.class, "android")
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
                .initializer("$S", "Android")
                .build();

        MethodSpec methodSpec = MethodSpec.methodBuilder("test")
                .addModifiers(Modifier.PUBLIC)
                .addParameter(String.class, "str")
                .returns(void.class)
                .addStatement("System.out.println(str)")
                .build();

        TypeSpec typeSpec = TypeSpec.classBuilder("Test")
                .addField(fieldSpec)
                .addMethod(methodSpec)
                .build();

        JavaFile javaFile = JavaFile.builder("com.weilu.test", typeSpec).build();

        System.out.println(javaFile.toString());
    }
}

输出结果如下:

package com.weilu.test;

import java.lang.String;

class Test {
     
  private final String android = "Android";

  public void test(String str) {
     
    System.out.println(str);
  }
}

3.进阶用法

前面的用例过于简单,实际情况生成的代码是大量且复杂的。例如实现一个for循环:

MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "  total += i;\n"
        + "}\n")
    .build();

可以看到使用addCode方法,可以一股脑的添加所有代码。但是我们需要自己换行,输入分号和缩进,这明显是个费力不讨好的方式,所以我个人不推荐使用。

ControlFlow

使用addStatement 可以帮我们添加分号和换行,而使用beginControlFlowendControlFlow组合可以帮我们轻松实现控制流代码。所以上面的代码等价于:

MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();

其他的使用场景,我举一些例子,大家一看便知:

// do... while
.beginControlFlow("do")
.endControlFlow("while (true)")

// if... else if... else...
.beginControlFlow("if (true)")
.nextControlFlow("else if (false)")
.nextControlFlow("else")
.endControlFlow()

// try... catch... finally
.beginControlFlow("try")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("e.printStackTrace()")
.nextControlFlow("finally")
.endControlFlow()

占位符

上面的例子中,我们的代码都是固定的字符串,显得不灵活。因此JavaPoet为我们提供了多种占位符来满足要求。

$S (String)

当代码中包含字符串的时候, 可以使用 $S 表示。

private static MethodSpec whatsMyName(String name) {
     
  return MethodSpec.methodBuilder(name)
      .returns(String.class)
      .addStatement("return $S", name)
      .build();
}

$L (Literal)

$L 是字面量替换,它与$S相似,但是它并不需要转义,也就是不包含字符串的引号。

private MethodSpec computeRange(String name, int from, int to, String op) {
     
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
      .addStatement("result = result $L i", op)
      .endControlFlow()
      .addStatement("return result")
      .build();
}

$T (Type)

上面例子为了简单,都使用的是一些基础类型,为的是不需要导包。实际中我们需要使用大量对象,如果只是在字符串中写死,代码虽没有问题,但是没有导包还是会保错。这是可以考虑使用$T,它的作用是替换类型。

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

$N ( Name)

$N是名称替换。例如我们定义了一个getXXX的方法,我们调用它时可以使用addStatement("get$L()", "XXX")
这种写法实现,但是每次拼接"get"未免太麻烦了,一个不留心说不定还忘记了。那么使用addStatement("$N()", methodSpec)就更加方便了。

MethodSpec methodSpec = MethodSpec.methodBuilder("get" + name)
    .returns(String.class)
    .addStatement("return $S", name)
    .build();

MethodSpec.methodBuilder("getValue")
    .returns(String.class)
    .addStatement("return $N()", methodSpec)
    .build();

继承与接口

TypeSpec.classBuilder("Test")
    .superclass(String.class)
    .addSuperinterface(Serializable.class)
    .build();

//class Test extends String implements Serializable {}

泛型

FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
// private T mT;

TypeVariableName mTypeVariable = TypeVariableName.get("T");
ParameterizedTypeName mListTypeName = ParameterizedTypeName.get(ClassName.get(List.class), mTypeVariable);
FieldSpec fieldSpec = FieldSpec.builder(mListTypeName, "mList", Modifier.PRIVATE).build();

//private List mList;

方法和类中使用addTypeVariable添加泛型。

初始化块

TypeSpec.classBuilder("Test")
    .addStaticBlock(CodeBlock.builder().build())
    .addInitializerBlock(CodeBlock.builder().build())
    .build();
    
/*
class Test {
    static {
    }

    {
    }
}*/

构造方法

MethodSpec methodSpec = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .build();

TypeSpec typeSpec = TypeSpec.classBuilder("Test")
    .addMethod(methodSpec)
    .build();

/*
class Test {
    public Test() {
    }
}*/

Annotations

添加注解的方法可以直接使用addAnnotation

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hoverboard")
    .build();

如果需要给注解设置属性,那么需要使用AnnotationSpec :

AnnotationSpec.builder(Headers.class)
    .addMember("accept", "$S", "application/json; charset=utf-8")
    .addMember("userAgent", "$S", "Square Cash")
    .build()

/*@Headers(
    accept = "application/json; charset=utf-8",
    userAgent = "Square Cash"
)*/

Javadoc

添加注释可以使用addJavadoc,直接传入注释字符串就行了,具体就不说明了。


大体上就这么多了,文末的参考文章也非常全面,推荐阅读。如果本篇对你有所帮助,希望可以一键三连!

参考

  • JavaPoet 看这一篇就够了

你可能感兴趣的:(Android,java,android,javapoet)