ASM 使用訪問者模式來創(chuàng)建類文件視圖,而新的 Class-File API 則采用了不同的方式,它依賴于 Java 17 中引入并在 Java 21 中最終確定的特性:模式匹配。
要創(chuàng)建這樣的模型,我們可以將字節(jié)轉(zhuǎn)換為實例:
ClassModel classModel = ClassFile.of().parse(bytes);
我們可以用循環(huán)結(jié)構(gòu)遍歷特定元素:
ClassModel cm = ClassFile.of().parse(bytes);
for (FieldModel fm : cm.fields()) {
System.out.printf("Field %s%n", fm.fieldName().stringValue());
}
模型是一系列 Class 元素,我們也可以利用模式匹配來更有組織地處理:
ClassModel cm = ClassFile.of().parse(bytes);
for (ClassElement classElement : cm) {
switch (cm) {
case MethodModel mm -> System.out.printf("Method %s%n",
mm.methodName().stringValue());
case FieldModel fm -> System.out.printf("Field %s%n",
fm.fieldName().stringValue());
default -> { /* NO-OP */ }
}
}
這種方式雖然功能強大,但代碼層級較深,閱讀起來較為困難。為了解決這一問題,可以通過 Stream 管道將不同的步驟重構(gòu)為方法,從而進(jìn)一步簡化代碼。
解析類文件只是操作的一部分,另一部分是生成新的類文件。
假設(shè)我們想生成一個包含簡單方法的類。傳統(tǒng)的 ASM 使用基于訪問者的方式來實現(xiàn),而新的 Class-File API 則采用了 lambda 接受構(gòu)建器的方法:
構(gòu)建器沒有通過 visitVarInsn 傳遞指令,而是直接內(nèi)置了許多方便的方法,如 aload 或 iload。這種方式更加直接和明確。
此外,Class-File API 還提供了更高級別的塊范圍和局部變量索引計算功能。由于 API 自動處理了塊范圍,我們不再需要手動創(chuàng)建標(biāo)簽或分支指令。這不僅簡化了代碼,還提升了代碼的可維護(hù)性。
類文件庫通常用于將讀寫步驟組合成轉(zhuǎn)換操作:讀取一個類并應(yīng)用本地化更改,但其中大部分不會改變。為此,每個構(gòu)建器都提供了 with 方法,使元素可以直接通過而無需任何轉(zhuǎn)換。
例如,以下代碼展示了如何從名稱以 "debug" 開頭的類中刪除所有方法:
ClassModel classModel = ClassFile.of().parse(bytes);
byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
classBuilder -> {
for (ClassElement ce : classModel) {
if (ce instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug")) {
continue;
}
classBuilder.with(ce);
}
});
對于簡單的轉(zhuǎn)換,這種方式已經(jīng)足夠。然而,在類樹中導(dǎo)航并檢查每個元素可能會導(dǎo)致大量重復(fù)的樣板代碼,尤其是在嵌套代碼較深的情況下。
以下代碼展示了如何將類 "Foo" 上的方法調(diào)用替換為另一個名為 "Bar" 的類:
CodeTransform codeTransform = (codeBuilder, e) -> {
switch (e) {
case InvokeInstruction i when i.owner().asInternalName().equals("Foo") ->
codeBuilder.invokeInstruction(i.opcode(),
ClassDesc.of("Bar"),
i.name().stringValue(),
i.typeSymbol(),
i.isInterface());
default -> codeBuilder.accept(e);
}
};
通過基于 lambda 的 transforms 方法,可以顯著減少嵌套代碼和樣板代碼:
MethodTransform methodTransform = MethodTransform.transformingCode(codeTransform);
ClassTransform classTransform = ClassTransform.transformingMethods(methodTransform);
最終,代碼變得更加簡潔:
ClassFile cc = ClassFile.of();
byte[] newBytes = cc.transform(cc.parse(bytes),
ClassTransform.transformingMethods(
MethodTransform.transformingCode(
firstTransform.andThen(secondTransform))));
通過這種方式,我們可以為常見用例構(gòu)建一系列簡單的轉(zhuǎn)換,并根據(jù)需要進(jìn)行組合。這種方法不僅提升了代碼的可讀性,還增強了其靈活性和可維護(hù)性。
原文鏈接: https://medium.com/@benweidig/looking-at-java-22-class-file-api-a4cb241ff785