Java中的屠龍之術(shù)(二):如何方便快捷地生成.class文件
點(diǎn)擊藍(lán)色“程序員黃小斜”關(guān)注我喲
加個(gè)“星標(biāo)”,每天和你一起多進(jìn)步一點(diǎn)點(diǎn)!
在之前的“Java中的屠龍之術(shù):如何修改語法樹”中,我們?cè)敿?xì)介紹了如何使用Javac源碼提供的工具類來修改語法樹。
而在此基礎(chǔ)上,有一款開源工具javapoet可以更加快捷地生成字節(jié)碼,實(shí)現(xiàn)原理其實(shí)也就是對(duì)JavaAPT的封裝,然而Javapoet有一個(gè)局限性,就是只能生成新的.class文件,卻無法修改原有的類,這也是它的一大局限性所在。接下來就讓我們看看它的使用方法把。
0x00 概述
注解系列
注解基礎(chǔ)
JavaPoet
編譯期注解處理之APT
上一篇限于篇幅只介紹了APT,這篇來繼續(xù)介紹javapoet,是square公司的開源庫。正如其名,java詩人,通過注解來生成java源文件,通常要使用javapoet這個(gè)庫與Filer配合使用。主要和注解配合用來干掉那些重復(fù)的模板代碼(如butterknife 和databinding所做的事情),當(dāng)然你也可以使用這個(gè)技術(shù)讓你的代碼更加的炫酷。
0x01 簡單使用
使用之前要先引入這個(gè)庫
compile 'com.squareup:javapoet:1.7.0'
復(fù)制代碼
javapoet是用來生成代碼的,需要借助
常用類
使用javapoet前需要了解8個(gè)常用類
| 類名 | 作用 |
|---|---|
| MethodSpec | 代表一個(gè)構(gòu)造函數(shù)或方法聲明 |
| TypeSpec | 代表一個(gè)類,接口,或者枚舉聲明 |
| FieldSpec | 代表一個(gè)成員變量,一個(gè)字段聲明 |
| JavaFile | 包含一個(gè)頂級(jí)類的Java文件 |
| ParameterSpec | 用來創(chuàng)建參數(shù) |
| AnnotationSpec | 用來創(chuàng)建注解 |
| ClassName | 用來包裝一個(gè)類 |
| TypeName | 類型,如在添加返回值類型是使用 TypeName.VOID |
除此之外 JavaPoet提供了一套自定義的字符串格式化規(guī)則,常用的有
| 格式化規(guī)則 | 表示含義 |
|---|---|
| $L | 字面量 |
| $S | 字符串 |
| $T | 類、接口 |
| $N | 變量 |
0x02 使用進(jìn)階
下面由淺入深,循序漸進(jìn)的說明用法
方法&控制流:
添加方法
addcode和addstatement對(duì)與無需類引入的極簡代碼可以直接使用addCode
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
復(fù)制代碼
生成的是
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
復(fù)制代碼
要是需要import的方法,如上面的.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") 就需要使用.addStatement來聲明
更優(yōu)雅的流控制
beginControlFlow 流開啟 addStatement 處理語句 endControlFlow()流結(jié)束
如上面的用流改寫就是
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
復(fù)制代碼
占位符
javapoet里面提供了占位符來幫助我們更好地生成代碼
$L 字面常量(Literals)
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();
}
復(fù)制代碼
這個(gè)就是一個(gè)for循環(huán),op負(fù)責(zé)加減乘除等符號(hào)
$S 字符串常量(String)
$T 類型(Types)
最大的特點(diǎn)是自動(dòng)導(dǎo)入包
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(today)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
復(fù)制代碼
生成的代碼如下,而且會(huì)自動(dòng)導(dǎo)包
package com.example.helloworld;
import java.util.Date;
public final class HelloWorld {
Date today() {
return new Date();
}
}
復(fù)制代碼
$N 命名(Names),通常指我們自己生成的方法名或者變量名等等
比如這樣的代碼塊
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
復(fù)制代碼
我們可以傳遞hexDigit()來代替。
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
復(fù)制代碼
獲取對(duì)應(yīng)類
有兩種方式:
ClassName.bestGuess(“類全名稱”) 返回ClassName對(duì)象,這里的類全名稱表示的類必須要存在,會(huì)自動(dòng)導(dǎo)入相應(yīng)的包
ClassName.get(“包名”,”類名”) 返回ClassName對(duì)象,不檢查該類是否存在
因此如果使用JavaPoet的話后續(xù)代碼重構(gòu)改變類名往往需要格外注意一點(diǎn)
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();
復(fù)制代碼
然后生成
package com.example.helloworld;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
public final class HelloWorld {
List beyond() {
List result = new ArrayList<>();
result.add(new Hoverboard());
result.add(new Hoverboard());
result.add(new Hoverboard());
return result;
}
}
復(fù)制代碼
構(gòu)建類的元素
方法
方法的修飾,如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();
復(fù)制代碼
這將會(huì)生成如下代碼
public abstract class HelloWorld {
protected abstract void flux();
}
復(fù)制代碼
當(dāng)然Methods需要和MethodSpec.Builder配置來增加方法參數(shù)、異常、javadoc、注解等。
構(gòu)造器
這個(gè)其實(shí)也是個(gè)函數(shù)方法而已,因此可以使用MethodSpec來生成構(gòu)造器方法。比如:
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();
復(fù)制代碼
將會(huì)生成
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
復(fù)制代碼
參數(shù)(重要)
之前我們是通過addstatement直接設(shè)置參數(shù),其實(shí)參數(shù)也有自己的一個(gè)專用類ParameterSpec,我們可以使用ParameterSpec.builder()來生成參數(shù),然后MethodSpec的addParameter去使用,這樣更加優(yōu)雅。
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();
復(fù)制代碼
生成的代碼
void welcomeOverlords(final String android, final String robot) {
}
復(fù)制代碼
稍微復(fù)雜點(diǎn)的類型 比如泛型 、Map之類的,需要了解下JavaPoet定義的幾種專門描述類型的類

常見的有
| 分類 | 生成的類型 | JavaPoet 寫法 | 也可以這么寫 (等效的 Java 寫法) |
|---|---|---|---|
| 內(nèi)置類型 | int | TypeName.INT | int.class |
| 數(shù)組類型 | int[] | ArrayTypeName.of(int.class) | int[].class |
| 需要引入包名的類型 | java.io.File | ClassName.get(“java.io”, “File”) | java.io.File.class |
| 參數(shù)化類型 (ParameterizedType | List | ParameterizedTypeName.get(List.class, String.class) | - |
| 類型變量 (WildcardType) 用于聲明泛型 | T | TypeVariableName.get(“T”) | - |
| 通配符類型 | ? extends String | WildcardTypeName.subtypeOf(String.class) | - |
/*
*Build input type, format as :
*Map>
*/
ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
)
);
/*
*Map
*/
ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class)
);
/*
*Build input param name.
*/
ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
復(fù)制代碼
生成參數(shù)類型
public class ARouter$Root$app implements IRouteRoot {
@Override
public void loadInto(Map> routes) {
routes.put("service", ARouter$Group$service.class);
routes.put("test", ARouter$Group$test.class);
}
}
public class ARouter$Group$service implements IRouteGroup {
@Override
public void loadInto(Map atlas) {
atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
atlas.put("/service/json", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
atlas.put("/service/single", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
}
}
復(fù)制代碼
字段
可以使用FieldSpec去聲明字段,然后加到Method中處理
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(android)
.addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
.build();
復(fù)制代碼
然后生成代碼
public class HelloWorld {
private final String android;
private final String robot;
}
復(fù)制代碼
通常Builder可以更加詳細(xì)的創(chuàng)建字段的內(nèi)容,比如javadoc、annotations或者初始化字段參數(shù)等,如:
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S + $L", "Lollipop v.", 5.0d)
.build();
復(fù)制代碼
對(duì)應(yīng)生成的代碼
private final String android = "Lollipop v." + 5.0;
復(fù)制代碼
接口
接口方法必須是PUBLIC ABSTRACT并且接口字段必須是PUBLIC STATIC FINAL ,使用TypeSpec.interfaceBuilder
如下
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();
復(fù)制代碼
生成的代碼如下
public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change";
void beep();
}
復(fù)制代碼
繼承父類 實(shí)現(xiàn)接口
接口代碼
package com.test.javapoet;
public interface TestInterface {
void test(T testPara);
}
復(fù)制代碼
父類代碼
public class TestExtendesClass {
}
復(fù)制代碼
使用javapoet實(shí)現(xiàn)接口并且繼承父類
final ClassName InterfaceName = ClassName.get("com.test.javapoet","TestInterface");
ClassName superinterface = ClassName.bestGuess("com.test.javapoet.TestClass");
//ClassName superinterface = ClassName.get("com.test.javapoet","aa");
TypeSpec.Builder spec = TypeSpec.classBuilder("TestImpl")
.addModifiers(Modifier.PUBLIC)
// 添加接口,ParameterizedTypeName的參數(shù)1是接口,參數(shù)2是接口的泛型
.addSuperinterface(ParameterizedTypeName.get(InterfaceName, superinterface))
//使用ClassName.bestGuess會(huì)自動(dòng)導(dǎo)入包
.superclass(ClassName.bestGuess("com.zs.javapoet.test.TestExtendesClass"));
MethodSpec.Builder methodSpec = MethodSpec.methodBuilder("test")
.addAnnotation(Override.class)
.returns(TypeName.VOID)
.addParameter(superinterface, "testPara")
.addStatement("System.out.println(hello)" );
TypeSpec typeSpec = spec.addMethod(methodSpec.build()).build();
JavaFile file = JavaFile.builder("com.zs.javapoet", typeSpec).build();
file.writeTo(System.out);
復(fù)制代碼
生成代碼
package com.test.javapoet;
import com.zs.javapoet.test.TestExtendesClass;
import java.lang.Override;
public class TestImpl extends TestExtendesClass implements TestInterface {
@Override
void test(TestClass testPara) {
System.out.println(hello);
}
}
復(fù)制代碼
枚舉類型
使用TypeSpec.enumBuilder來創(chuàng)建,使用addEnumConstant來添加
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
復(fù)制代碼
生成的代碼
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
復(fù)制代碼
更復(fù)雜的類型也可以支持,如重寫、注解等
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!")
.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();
復(fù)制代碼
生成代碼
public enum Roshambo {
ROCK("fist") {
@Override
public void toString() {
return "avalanche!";
}
},
SCISSORS("peace"),
PAPER("flat");
private final String handsign;
Roshambo(String handsign) {
this.handsign = handsign;
}
}
復(fù)制代碼
匿名內(nèi)部類
需要使用Type.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();
復(fù)制代碼
生成代碼
void sortByLength(List strings) {
Collections.sort(strings, new Comparator() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
}
復(fù)制代碼
定義匿名內(nèi)部類的一個(gè)特別棘手的問題是參數(shù)的構(gòu)造。在上面的代碼中我們傳遞了不帶參數(shù)的空字符串。TypeSpec.anonymousClassBuilder("")。
注解
注解使用起來比較簡單
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hoverboard")
.build();
復(fù)制代碼
生成代碼
@Override
public String toString() {
return "Hoverboard";
}
復(fù)制代碼
通過AnnotationSpec.builder() 可以對(duì)注解設(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();
復(fù)制代碼
代碼生成如下
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
復(fù)制代碼
注解同樣可以注解其他注解,通過$L引用如
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(HeaderList.class)
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "Accept")
.addMember("value", "$S", "application/json; charset=utf-8")
.build())
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "User-Agent")
.addMember("value", "$S", "Square Cash")
.build())
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
復(fù)制代碼
生成代碼
@HeaderList({
@Header(name = "Accept", value = "application/json; charset=utf-8"),
@Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);
復(fù)制代碼
注釋
javadoc
0x03 后續(xù)
在javapoet之前有javawriter,但javapoet有著更強(qiáng)大的代碼模型,并且對(duì)類的理解更加到位,因此推薦使用javapoet
參考文章
juejin.cn/post/684490…
cloud.tencent.com/developer/a…
blog.csdn.net/qq_26376637…
作者:渡口一艘船 鏈接:https://juejin.cn/post/6844903456629587976 來源:稀土掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
—?【 THE END 】— 公眾號(hào)[程序員黃小斜]全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲取! 最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) PDF?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
