深入探讨Java 22:Class-File API | 作者:Ben Weidig - Medium
一个元素描述类文件的某个部分。它是不可变的,可以小到一条指令,也可以大到整个类文件,以及介于两者之间的所有内容,如属性、字段或方法。
元素可以进一步包含它们自己的元素,使它们成为复合元素,如方法或类。
对于每种类型的复合元素,都有一个匹配的生成器,它配备了特定的构建方法,例如 ClassBuilder::withMethod。构建器还充当相关元素类型的消费者。
一个transform 是一个接受元素和构建器的函数,指导如何或是否将该元素转换为其他元素。
使用模式解析类文件
ASM 使用访问者模式来创建类文件视图,而新的 Class-File API 则采用了不同的方式,它依赖于 Java 17 中引入并在 Java 21 中最终确定的特性:模式匹配。
要创建这样的模型,我们可以将字节转换为实例:
ClassModel classModel = ClassFile.of().parse(bytes);
我们可以用循环结构遍历特定元素:
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 管道将不同的步骤重构为方法,从而进一步简化代码。
生成类文件
解析类文件只是操作的一部分,另一部分是生成新的类文件。
假设我们想生成一个包含简单方法的类。传统的 ASM 使用基于访问者的方式来实现,而新的 Class-File API 则采用了 lambda 接受构建器的方法:
构建器没有通过 visitVarInsn 传递指令,而是直接内置了许多方便的方法,如 aload 或 iload。这种方式更加直接和明确。
此外,Class-File API 还提供了更高级别的块范围和局部变量索引计算功能。由于 API 自动处理了块范围,我们不再需要手动创建标签或分支指令。这不仅简化了代码,还提升了代码的可维护性。
转换类文件
类文件库通常用于将读写步骤组合成转换操作:读取一个类并应用本地化更改,但其中大部分不会改变。为此,每个构建器都提供了 with 方法,使元素可以直接通过而无需任何转换。
例如,以下代码展示了如何从名称以 "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);
}
});
对于简单的转换,这种方式已经足够。然而,在类树中导航并检查每个元素可能会导致大量重复的样板代码,尤其是在嵌套代码较深的情况下。
以下代码展示了如何将类 "Foo" 上的方法调用替换为另一个名为 "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))));
通过这种方式,我们可以为常见用例构建一系列简单的转换,并根据需要进行组合。这种方法不仅提升了代码的可读性,还增强了其灵活性和可维护性。
原文链接: https://medium.com/@benweidig/looking-at-java-22-class-file-api-a4cb241ff785
最新文章
- 从2024年三个API趋势中学习,塑造新的一年
- 通过Fetch和Axios在React中使REST API
- 企业如何合法使用三方数据、自有的用户数据?
- 如何在 Python 和 Flask 中使用 IP API 查找地理位置?
- 什么是API方法?
- 玩转色彩世界:The Color API的魔法调色板
- 11 种最佳营养和食品 API 解决方案
- JSON Schema:自定义API响应以提升用户体验
- JavaScript中的Temporal Date API非常优秀,原因如下:- Apidog
- 使用PyCharm调用API指南
- GraphQL vs. REST APIs:为何不应使用GraphQL
- API安全性的最佳实践:全面指南!