1. 引言

Java Class-File API 是 Java 24 中通过 JEP-484 引入的新特性。它提供了一套标准接口,用于处理 class 文件,无需依赖 JDK 内部集成的 ASM 库 实现。

本文将带你了解如何使用 Class-File API 从零构建 class 文件,以及如何将一个 class 文件转换为另一个。

2. Class-File API 核心组件

Class-File API 包含三个核心元素,用于实现后续的生成和转换功能:

  • 元素(Element):表示代码的组成部分,如变量、指令、方法或类。一个元素可能包含其他元素(例如类元素包含方法元素,方法元素又包含变量或指令元素)
  • 构建器(Builder):用于创建各类元素,包括方法构建器(MethodBuilder)和代码构建器(CodeBuilder)
  • 转换器(Transform):通过构建器将元素转换为其他元素的函数

接下来我们通过实际案例,看看这三个组件如何协同工作。

3. 生成 Class 文件

本节演示如何使用 MethodBuilderCodeBuilder 生成 class 文件。

3.1. 示例方法

以计算员工年终奖的简单方法为例:

public double calculateAnnualBonus(double baseSalary, String role) {
    if (role.equals("sales")) {
        return baseSalary * 0.35;
    }

    if (role.equals("engineer")) {
        return baseSalary * 0.25;
    }

    return baseSalary * 0.15;
}

3.2. 使用 MethodBuilder 和 CodeBuilder

要生成与 calculateAnnualBonus() 功能相同的方法,需要使用 MethodBuilderCodeBuilder。首先定义 generate() 方法,其中包含一个 Consumer<MethodBuilder> 用于构建方法:

public static void generate() throws IOException {
    Consumer<MethodBuilder> calculateAnnualBonusBuilder = methodBuilder -> methodBuilder.withCode(codeBuilder -> {
        Label notSales = codeBuilder.newLabel();
        Label notEngineer = codeBuilder.newLabel();
        ClassDesc stringClass = ClassDesc.of("java.lang.String");

        // ...
    });
}

关键点说明:

  1. 定义两个 Label 对象用于条件跳转
  2. 创建 String 类的 ClassDesc 常量备用

接着在 lambda 表达式中添加第一段逻辑:

codeBuilder.aload(3)
  .ldc("sales")
  .invokevirtual(stringClass, "equals", MethodTypeDesc.of(ClassDesc.of("Z"), stringClass))
  .ifeq(notSales)
  .dload(1)
  .ldc(0.35)
  .dmul()
  .dreturn()

逐行解析:

  • aload(3):将 role 参数加载到操作数栈(注意:参数槽位从 1 开始,double 占两个槽位,故 baseSalary 在槽位 1,role 在槽位 3)
  • ldc("sales"):将字符串常量压入栈
  • invokevirtual():调用 String.equals() 方法比较参数(ClassDesc.of("Z") 表示返回布尔值)
  • ifeq(notSales):如果比较结果为 false 则跳转到 notSales 标签
  • 后续操作:加载 baseSalary → 压入 0.35 → 执行乘法 → 返回结果

添加第二个条件分支:

  .labelBinding(notSales)
  .aload(3)
  .ldc("engineer")
  .invokevirtual(stringClass, "equals", MethodTypeDesc.of(ClassDesc.of("Z"), stringClass))
  .ifeq(notEngineer)
  .dload(1)
  .ldc(0.25)
  .dmul()
  .dreturn()

labelBinding(notSales) 标识第一个条件为 false 时的跳转位置,后续逻辑与第一个条件类似。

最后添加默认返回逻辑:

  .labelBinding(notEngineer)
  .dload(1)
  .ldc(0.15)
  .dmul()
  .dreturn();

完成方法构建后,生成 class 文件:

var classBuilder = ClassFile.of()
  .build(ClassDesc.of("EmployeeSalaryCalculator"),
    cb -> cb.withMethod("calculateAnnualBonus", MethodTypeDesc.of(CD_double, CD_double, CD_String), 
      AccessFlag.PUBLIC.mask(), 
      calculateAnnualBonusBuilder));

Files.write(Path.of("EmployeeSalaryCalculator.class"), classBuilder);

关键点:

  • ClassFile.of().build() 创建类构建器
  • ClassDesc.of() 指定类名
  • withMethod() 定义方法(方法名、签名、访问修饰符、方法构建器)
  • 方法签名 MethodTypeDesc.of(CD_double, CD_double, CD_String) 表示返回 double,接收 doubleString 参数
  • 使用 Files.write() 写入文件

4. 转换 Class 文件

现在演示如何复制 class 文件内容到另一个文件:

public static void transform() throws IOException {
    var basePath = Files.readAllBytes(Path.of("EmployeeSalaryCalculator.class"));

    CodeTransform codeTransform = ClassFileBuilder::accept;

    MethodTransform methodTransform = MethodTransform.transformingCode(codeTransform);
    ClassTransform classTransform = ClassTransform.transformingMethods(methodTransform);

    ClassFile classFile = ClassFile.of();
    byte[] transformedClass = classFile.transformClass(classFile.parse(basePath), classTransform);
    Files.write(Path.of("TransformedEmployeeSalaryCalculator.class"), transformedClass);
}

流程解析:

  1. 读取原始 class 文件
  2. 定义转换器链:
    • CodeTransform:接受所有原始代码元素
    • MethodTransform:基于代码转换器构建
    • ClassTransform:基于方法转换器构建
  3. 使用 transformClass() 执行转换
  4. 写入新文件

⚠️ 这种组合模式(组合模式)便于复用转换器。

自定义转换示例(仅处理特定方法):

MethodTransform methodTransform = (methodBuilder, methodElement) -> {
    if (methodElement.header().name().stringValue().equals("calculateAnnualBonus")) {
        methodBuilder.withCode(codeBuilder -> {
            for (var codeElement: methodElement.code()) {
                codeBuilder.accept(codeElement);
            }
        });
    }
};

逻辑说明:

  • 检查方法名是否为 calculateAnnualBonus
  • 若匹配,则使用原始方法的指令构建新方法

5. 总结

本文介绍了 Class-File API 的核心功能: ✅ 从零生成 class 文件 ✅ 转换现有 class 文件 ✅ 使用构建器(Builder)和转换器(Transform)操作类元素

通过实际案例展示了如何利用这些组件在运行时动态创建和修改类。完整代码示例可在 GitHub 获取。