JavaPoet
JavaPoet 是一個用于生成 .java 源文件的 Java API,是ATP技術(shù)的必要組件。我們在工作中如果遇到重復(fù)的代碼工作反復(fù)操作的時(shí)候,相信誰都很不耐煩,都希望能夠有簡單的方法一勞永逸,沒錯今天他來了。找出重復(fù)代碼特點(diǎn),借助JavaPoet語法輕輕松松減輕你的痛苦。
JavaPoet帶來的愉快體驗(yàn):
我們來看名稱為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")
//方法添加描述符,公開且靜態(tài)的方法
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
//方法添加返回類型,為void空返回
.returns(void.class)
//方法添加參數(shù)類型是string[],名稱是args
.addParameter(String[].class, "args")
//方法添加內(nèi)容,一句打印 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()) 或?qū)⑵鋵懭胛募到y(tǒng) (JavaFile.writeTo())。
怎么優(yōu)雅的使用javaPoet寫代碼
JavaPoet給我們提供了一些很多模型來幫組我們完成代碼生成。
類和接口模型(TypeSpec),字段模型 (FieldSpec)、方法和構(gòu)造函數(shù)模型 (MethodSpec)、參數(shù)模型 (ParameterSpec) 和注釋模型(AnnotationSpec)
方法和構(gòu)造函數(shù)的實(shí)現(xiàn)JavaPoet沒有給我們提供模型,我們可以直接用字符串來實(shí)現(xiàn)這一部分的代碼??纯聪旅娴拇a就明白了。
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 時(shí)會經(jīng)常需要它。它還可以識別任何類,如數(shù)組、參數(shù)化類型、通配符類型和類型變量。
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會自動給我們導(dǎo)入組件,看這屌樣代碼就看出來了
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;
}
}
導(dǎo)入靜態(tài)屬性
把上面例子擴(kuò)展下
...
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”塊添加到配置的文件中,匹配并處理相應(yīng)地所有調(diào)用,并根據(jù)需要導(dǎo)入所有其他類型。
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();
相對參數(shù)
占位符可以按順序替換占位符
CodeBlock.builder().add("I ate $L $L", 3, "tacos")
生成的字符串是, "I ate 3 tacos"
位置參數(shù)
可以指定參數(shù)位填充位置
CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
生成的字符串是, "I ate 3 tacos"
命名參數(shù)
可以安裝名字去匹配參數(shù)
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();
}
構(gòu)造方法
`MethodSpec”用于構(gòu)造函數(shù):
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();
對應(yīng)代碼如下
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
參數(shù)
Declare parameters on methods and constructors with either ParameterSpec.builder() or
MethodSpec's convenient addParameter() API:
定義方法的參數(shù)可以使用ParameterSpec或者使用MethodSpect的addParameterAPI來實(shí)現(xiàn)
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兩種方式來添加參數(shù),都是添加string的參數(shù),最終生成的代碼如下:
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();
}
枚舉
創(chuàng)建枚舉的方法在TypeSpec中,調(diào)用enumBuilder來創(chuàng)建一個枚舉,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;
}
}
匿名內(nèi)部類
在枚舉代碼中,我們使用了TypeSpec.anonymousInnerClass()。匿名內(nèi)部類也可以用于代碼塊。它們是可以用“$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()設(shè)置屬性
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實(shí)現(xiàn)APT技術(shù)的例子,下次見。