JavaPoet是用于生成
.java
源文件的库 。通过自动生成代码,可以替代许多重复的工作。在安卓项目中添加依赖时,凡是需要添加apt、kpt、annotationProcessor这种前缀标识时,说明其内部使用到了注解处理器,也基本可以确定使用到了JavaPoet
或KotlinPoet
,例如大名鼎鼎的Butterknife
和Dagger
就使用到了JavaPoet
。
我在两三年前也有使用注解处理器和JavaPoet
写过一个Saber的库,在前一阵的维护中,突然对JavaPoet
有些生疏所以想着记录总结一下,以便以后使用时快速上手。
添加依赖:
dependencies {
...
implementation 'com.squareup:javapoet:1.13.0'
}
既然是生成java文件,那么需要的基本元素就包括:
我们就按上面的顺序来一次介绍:
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.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
用来创建类,接口,或者枚举。
// class Test {}
TypeSpec.classBuilder("Test").build();
//interface Test {}
TypeSpec.interfaceBuilder("Test").build();
/*
enum Test {
ONE
}*/
TypeSpec typeSpec = TypeSpec.enumBuilder("Test").addEnumConstant("ONE").build();
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);
}
}
前面的用例过于简单,实际情况生成的代码是大量且复杂的。例如实现一个for循环:
MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
可以看到使用addCode
方法,可以一股脑的添加所有代码。但是我们需要自己换行,输入分号和缩进,这明显是个费力不讨好的方式,所以我个人不推荐使用。
使用addStatement
可以帮我们添加分号和换行,而使用beginControlFlow
和 endControlFlow
组合可以帮我们轻松实现控制流代码。所以上面的代码等价于:
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
表示。
private static MethodSpec whatsMyName(String name) {
return MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return $S", name)
.build();
}
$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
,它的作用是替换类型。
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
$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() {
}
}*/
添加注解的方法可以直接使用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"
)*/
添加注释可以使用addJavadoc
,直接传入注释字符串就行了,具体就不说明了。
大体上就这么多了,文末的参考文章也非常全面,推荐阅读。如果本篇对你有所帮助,希望可以一键三连!