深入理解 JDK23 的类文件API

2024-09-26 22:42:44 浏览数 (1)

类文件API(第二次预览),JEP 466 提供解析、生成和转换 Java 类文件的标准 API。

Java生态系统很大程度依赖于解析、生成和转换类文件的能力。框架使用即时字节码转换来透明地添加功能,例如这些框架通常捆绑了类文件库,如ASM或Javassist来处理类文件处理。然而,它们受到JDK六个月发布周期的影响,类文件格式比以前发展得更快,这意味着它们可能遇到比它们捆绑的类文件库更新的类文件。

为解决这个问题,JEP 466提出一个标准的类文件API,它可以生成始终与运行中的JDK保持最新的类文件。这个API将随着类文件格式的发展而发展,使框架完全依赖于这个API,而不是第三方开发人员更新和测试他们的类文件库的意愿。

元素、构建器和转换

位于java.lang.classfile包中的类文件API由三个主要组件组成:

  • 元素 : 描述类文件部分的不可变描述,如指令、属性、字段、方法或整个文件
  • 构建器 : 对应的构建器用于复合元素,提供特定的构建方法(例如,ClassBuilder::withMethod)并作为元素类型的消费者
  • 转换 : 接受一个元素和一个构建器的函数,确定元素是否以及如何转换为其他元素。这允许灵活地修改类文件元素
示例和与ASM的比较

假设我们希望在类文件中生成以下方法:

代码语言:java复制
void fooBar(boolean z, int x) {
    if (z)
        foo(x);
    else
        bar(x);
}

使用ASM,可像这样生成方法:

代码语言:java复制
ClassWriter classWriter = ...;
MethodVisitor mv = classWriter.visitMethod(0, "fooBar", "(ZI)V", null, null);
mv.visitCode();
mv.visitVarInsn(ILOAD, 1);
Label label1 = new Label();
mv.visitJumpInsn(IFEQ, label1);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "Foo", "foo", "(I)V", false);
Label label2 = new Label();
mv.visitJumpInsn(GOTO,label2);
mv.visitLabel(label1);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "Foo", "bar", "(I)V", false);
mv.visitLabel(label2);
mv.visitInsn(RETURN);
mv.visitEnd();

与ASM不同,在这里,客户直接创建一个ClassWriter,然后请求一个MethodVisitor,类文件API采用了不同的方法。在这里,客户不是通过构造函数或工厂启动构建器,而是提供一个lambda函数,该函数接受构建器作为其参数:

代码语言:java复制
ClassBuilder classBuilder = ...;
classBuilder.withMethod("fooBar", MethodTypeDesc.of(CD_void, CD_boolean, CD_int), flags,
                        methodBuilder -> methodBuilder.withCode(codeBuilder -> {
    Label label1 = codeBuilder.newLabel();
    Label label2 = codeBuilder.newLabel();
    codeBuilder.iload(1)
        .ifeq(label1)
        .aload(0)
        .iload(2)
        .invokevirtual(ClassDesc.of("Foo"), "foo", MethodTypeDesc.of(CD_void, CD_int))
        .goto_(label2)
        .labelBinding(label1)
        .aload(0)
        .iload(2)
        .invokevirtual(ClassDesc.of("Foo"), "bar", MethodTypeDesc.of(CD_void, CD_int))
        .labelBinding(label2);
    .return_();
}));
V.S Java 22

根据第一次预览阶段的经验和反馈,进行了一些改进;这些是最重要的:

  • CodeBuilder类已被简化。这个类有三种类型的字节码指令工厂方法:低级工厂、中级工厂和基本块的高级构建器。根据反馈,删除了重复低级方法或不经常使用的中级方法,并将剩余的中级方法重命名以提高可用性。
  • Attributes中的AttributeMapper实例已经通过静态方法而不是静态字段变得可访问,以允许延迟初始化并减少启动成本。
  • Signature.TypeArg被重新设计为代数数据类型,以便于在TypeArg的种类为有界时访问绑定的类型。
  • 添加了类型感知的ClassReader::readEntryOrNullConstantPool::entryByIndex方法,如果索引处的条目不是期望的类型,则抛出ConstantPoolException而不是ClassCastException。这允许类文件处理器指示常量池条目类型不匹配是类文件格式问题而不是处理器的问题。

0 人点赞