Skip to content Skip to footer

JEP 457:使用类文件 API 简化 Java 开发

image-20231217173514819

JEP 457,类文件 API(预览版)最近已集成到 JDK 22 中。此 JEP 建议提供用于解析、生成和转换 Java 类文件的 API。这最初将作为 JDK 中 Java 字节码操作和分析框架 ASM 的内部替代品,并计划将其作为公共 API 开放。Oracle 的 Java 语言架构师 Brian Goetz 将 ASM 描述为“一个带有大量遗留包袱的旧代码库”,并提供了有关该草案将如何发展并最终取代 ASM 的背景信息

类文件 API 的核心是几个关键原则。首先,它将类文件实体(如字段、方法、属性和字节码指令)视为不可变对象。这种不可变的表示形式可确保在类文件进行转换时进行可靠的共享。该 API 采用树状结构表示来反映类文件的层次结构性质,从而实现用户驱动的导航以实现高效解析。它还强调解析中的懒惰性,只处理满足用户要求所需的类文件。

Class-File API,位于java.lang.classfilepackage 及其子包包含三个主要抽象:元素、生成器和转换。元素是类文件组件的不可变描述。构建器对应于每种复合元素,便于使用特定的构建方法构建类文件。转换表示在生成过程中修改元素的函数。

该 API 还引入了使用模式解析类文件的新方法,这与 ASM 基于访问者的方法不同。这样可以利用 Java 的模式匹配功能实现更直接、更简洁的表达式。例如,开发人员可以循环访问 CodeModel 中的指令,并匹配依赖关系图构造等任务感兴趣的元素。

请看以下示例:

CodeModel code = ...;
Set<ClassDesc> deps = new HashSet<>();
for (CodeElement e : code) {
    switch (e) {
        case FieldInstruction f  -> deps.add(f.owner());
        case InvokeInstruction i -> deps.add(i.owner());
        // ... and so on for instanceof, cast, etc ...
    }
}

此代码片段演示如何使用模式匹配来分析 Code 属性并收集类依赖关系图的依赖关系,循环访问指令并匹配特定类型。

使用构建器生成类文件是另一个关键功能。API 颠覆了使用构造函数或工厂创建构建器的传统习语;相反,客户端提供接受生成器的 Lambda。这种方法提供了更具体、更透明的代码生成,并有可能重放操作序列。它还为管理块范围、局部变量索引计算和标签管理提供了更高级别的便利。

以下代码演示如何使用生成器生成方法,演示 API 特定且透明的代码生成方法。

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_();
    });

类文件 API 的转换功能值得注意。解析和生成方法保持一致,以便无缝进行转换。例如,开发人员可以处理类文件以删除特定方法或通过应用各种转换来转换方法主体。

以下代码片段演示了 API 转换类文件的功能,展示了如何在转换过程中有选择地修改或替换类元素。

ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(bytes);
byte[] newBytes = cf.transform(classModel, (classBuilder, ce) -> {
    if (ce instanceof MethodModel mm) {
        classBuilder.transformMethod(mm, (methodBuilder, me)-> {
            if (me instanceof CodeModel cm) {
                methodBuilder.transformCode(cm, (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.with(e);
                    }
                });
            }
            else
                methodBuilder.with(me);
        });
    }
    else
        classBuilder.with(ce);
});

JEP 457 的变革性方面之一是它如何应对 Java 生态系统中类文件格式快速发展所带来的挑战。通过提供随 JDK 发展的标准 API,它确保使用此 API 的框架和工具将自动支持最新 JDK 中的类文件。此功能对于采用在类文件中具有表示形式的新语言和 VM 功能至关重要。

总之,JEP 457 的类文件 API 是一个具有前瞻性的解决方案,符合 Java 开发的现代需求。它的设计原则、抽象和转换功能使其成为 Java 开发人员的强大工具,提高了 Java 生态系统中类文件管理的效率和可靠性。