JavaPoet
JavaPoet 是一个用于生成 .java 源文件的 Java API,是ATP技术的必要组件。我们在工作中如果遇到重复的代码工作反复操作的时候,相信谁都很不耐烦,都希望能够有简单的方法一劳永逸,没错今天他来了。找出重复代码特点,借助JavaPoet语法轻轻松松减轻你的痛苦。
JavaPoet带来的愉快体验:
我们来看名称为HelloJavaPoet 的java源文件:
package com.kingtouch.hellojavapoet;
public final class HelloJavaPoet {
public static void main(String[] args) {
System.out.println("HelloJavaPoet!");
}
}
来看看我们使用JavaPoet的api来书写生成这段极其简单的java代码过程,令人大跌眼镜
//申明一个方法对象,方法名是“main”
MethodSpec main = MethodSpec.methodBuilder("main")
//方法添加描述符,公开且静态的方法
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
//方法添加返回类型,为void空返回
.returns(void.class)
//方法添加参数类型是string[],名称是args
.addParameter(String[].class, "args")
//方法添加内容,一句打印 System.out.println("HelloJavaPoet!")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
//申明一个类对象,类名是“HelloJavaPoet”
TypeSpec helloWorld = TypeSpec.classBuilder("HelloJavaPoet")
//类添加描述符,公开且不可继承的类
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
//类添加方法,前面定义的 MethodSpec 变量 main
.addMethod(main)
.build();
//申明一个java文件输出对象
JavaFile javaFile = JavaFile.builder("com.kingtouch.hellojavapoet;", HelloJavaPoet)
.build();
//输出文件
javaFile.writeTo(System.out);
最终我们把文件写入 System.out,我们也可以将其作为字符串获取
(JavaFile.toString()) 或将其写入文件系统 (JavaFile.writeTo())。
怎么优雅的使用javaPoet写代码
JavaPoet给我们提供了一些很多模型来帮组我们完成代码生成。
类和接口模型(TypeSpec),字段模型 (FieldSpec)、方法和构造函数模型 (MethodSpec)、参数模型 (ParameterSpec) 和注释模型(AnnotationSpec)
方法和构造函数的实现JavaPoet没有给我们提供模型,我们可以直接用字符串来实现这一部分的代码。看看下面的代码就明白了。
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ "if(i/2=0){\n"
+ " total += i;\n"
+ " }\n")
+ " return total;\n")
+ "}\n")
.build();
生成的代码就是下面这个屌样
int main() {
int total = 0;
for (int i = 0; i < 10; i++) {
if(i/2=0){
total += i;
}
}
return total;
}
JavaPoet提供了分号和换行的操作,以及大括号使用beginControlFlow() + endControlFlow()配合使用
MethodSpec main = MethodSpec.methodBuilder("main")
.returns(int.class)
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.beginControlFlow("if(i/2=0)")
.addStatement("total += i")
.endControlFlow()
.endControlFlow()
.addStatement("return total")
.build();
如果遇到像if/else try/catch这种控制流的语句,可以使用nextControlFlow()
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("long now = $T.currentTimeMillis()", System.class)
.beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
.nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time stood still!")
.endControlFlow()
.beginControlFlow("try")
.addStatement("throw new Exception($S)", "出错啦")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("throw new $T(e)", RuntimeException.class)
.endControlFlow()
.build();
看看代码是什么屌样
void main() {
long now = System.currentTimeMillis();
if (System.currentTimeMillis() < now) {
System.out.println("HaHaHa");
} else if (System.currentTimeMillis() == now) {
System.out.println("HeiHeiHei");
}
try {
throw new Exception("Failed");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
JavaPoet给我们还提供了几种占位符
$L (通用占位符)
MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", 0, 10)
.addStatement("result = result $L i", "*")
.endControlFlow()
.addStatement("return result")
.build();
看看代码是什么屌样
int main() {
int result = 0;
for (int i = 0; i < 10; i++) {
result = result * i;
}
return result;
}
$S (string占位符)
public static void main(String[] args) throws Exception {
MethodSpec sayHelloMethodSpec = MethodSpec.methodBuilder("sayHello")
.returns(String.class)
.addStatement("return $S", "Hello boy!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("SayHello")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(sayHelloMethodSpec)
.build();
JavaFile javaFile = JavaFile.builder("com.king.sayHello", SayHello)
.build();
javaFile.writeTo(System.out);
}
来看看代码是什么屌样
public final class SayHello {
String sayHello() {
return "Hello boy!";
}
}
$T (类型占位符)
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("Date")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(today)
.build();
JavaFile javaFile = JavaFile.builder("com.king.date", Date)
.build();
javaFile.writeTo(System.out);
来看看代码是什么屌样
package com.king.date;
import java.util.Date;
public final class Date {
Date today() {
return new Date();
}
}
也能够替换ClassName,看看如下示例
ClassName bundle = ClassName.get("android.os", "Bundle");
addStatement("$T bundle = new $T()",bundle)
ClassName 类型非常重要,我们在使用 JavaPoet 时会经常需要它。它还可以识别任何类,如数组、参数化类型、通配符类型和类型变量。
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(listOfHoverboards)
.addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("return result")
.build();
JavaPoet会自动给我们导入组件,看这屌样代码就看出来了
package com.example.helloworld;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
public final class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(new Hoverboard());
result.add(new Hoverboard());
result.add(new Hoverboard());
return result;
}
}
导入静态属性
把上面例子扩展下
...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(listOfHoverboards)
.addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
.addStatement("result.add($T.createNimbus(2000))", hoverboard)
.addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
.addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
.addStatement("$T.sort(result)", Collections.class)
.addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
.build();
TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
.addMethod(beyond)
.build();
JavaFile.builder("com.example.helloworld", hello)
.addStaticImport(hoverboard, "createNimbus")
.addStaticImport(namedBoards, "*")
.addStaticImport(Collections.class, "*")
.build();
JavaPoet 将首先将您的“import static”块添加到配置的文件中,匹配并处理相应地所有调用,并根据需要导入所有其他类型。
package com.example.helloworld;
import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(createNimbus(2000));
result.add(createNimbus("2001"));
result.add(createNimbus(THUNDERBOLT));
sort(result);
return result.isEmpty() ? emptyList() : result;
}
}
$N 占位符代指的是一个名称,方法名称,变量名称等
addStatement("data.$N()",toString)
代码如下
data.toString();
相对参数
占位符可以按顺序替换占位符
CodeBlock.builder().add("I ate $L $L", 3, "tacos")
生成的字符串是, "I ate 3 tacos"
位置参数
可以指定参数位填充位置
CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
生成的字符串是, "I ate 3 tacos"
命名参数
可以安装名字去匹配参数
Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
生成的字符串是, "I ate 3 tacos"
方法
使用Modifiers.ABSTRACT可获得接口和抽象类
MethodSpec flux = MethodSpec.methodBuilder("flux")
.addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addMethod(flux)
.build();
代码如下这个屌样:
public abstract class HelloWorld {
protected abstract void flux();
}
构造方法
`MethodSpec”用于构造函数:
MethodSpec flux = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "greeting")
.addStatement("this.$N = $N", "greeting", "greeting")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(flux)
.build();
对应代码如下
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
参数
Declare parameters on methods and constructors with either ParameterSpec.builder() or
MethodSpec's convenient addParameter() API:
定义方法的参数可以使用ParameterSpec或者使用MethodSpect的addParameterAPI来实现
ParameterSpec android = ParameterSpec.builder(String.class, "android")
.addModifiers(Modifier.FINAL)
.build();
MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(android)
.addParameter(String.class, "robot", Modifier.FINAL)
.build();
如上代码,通过ParamterSpec和addParameter两种方式来添加参数,都是添加string的参数,最终生成的代码如下:
void welcomeOverlords(final String android, final String robot) {
}
接口
JavaPoet的接口方法必须始终为PUBLIC ABSTRACT接口字段必须始终为PUBLIC STATIC FINAL。这些修改器是必需的
定义接口:
TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "change")
.build())
.addMethod(MethodSpec.methodBuilder("beep")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build())
.build();
public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change";
void beep();
}
枚举
创建枚举的方法在TypeSpec中,调用enumBuilder来创建一个枚举,addEnumConstant()添加value
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
生成代码如下:
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
支持花式枚举
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
.addMethod(MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "avalanche!")
.returns(String.class)
.build())
.build())
.addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
.build())
.addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
.build())
.addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(MethodSpec.constructorBuilder()
.addParameter(String.class, "handsign")
.addStatement("this.$N = $N", "handsign", "handsign")
.build())
.build();
生成代码如下:
public enum Roshambo {
ROCK("fist") {
@Override
public String toString() {
return "avalanche!";
}
},
SCISSORS("peace"),
PAPER("flat");
private final String handsign;
Roshambo(String handsign) {
this.handsign = handsign;
}
}
匿名内部类
在枚举代码中,我们使用了TypeSpec.anonymousInnerClass()。匿名内部类也可以用于代码块。它们是可以用“$L”引用的值:
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();
This generates a method that contains a class that contains a method:
void sortByLength(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
}
注释
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hoverboard")
.build();
@Override
public String toString() {
return "Hoverboard";
}
使用AnnotationSpec.builder()设置属性
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(Headers.class)
.addMember("accept", "$S", "application/json; charset=utf-8")
.addMember("userAgent", "$S", "Square Cash")
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
生成代码如下:
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
Gradle 添加依赖:
compile 'com.squareup:javapoet:1.13.0'
好javaPoet的基本用法就介绍到这里了,抽空我再整理下完整的JavaPoet实现APT技术的例子,下次见。