ClassReader classReader = new ClassReader(new FileInputStream(new File("input.class")));
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
Files.write(Paths.get("output.class"), classWriter.toByteArray());


反编译output.class时,我得到

package corrupted_class_files;


input.class很好,我可以使用ClassReader来阅读说明,但是我无法保存该类

最佳答案

您的代码缺少将类功能从源实际复制到目标的步骤:

try(FileInputStream in = new FileInputStream(new File("input.class")) {
    ClassReader classReader = new ClassReader(in);
    ClassWriter classWriter = new ClassWriter(classReader, 0);
    classReader.accept(classWriter, 0);
    Files.write(Paths.get("output.class"), classWriter.toByteArray());
}


如果您的转换保留了大多数原始类文件,则将ClassReader传递给ClassWriter的构造函数不会复制功能,而是可以进行优化。或者,如the documentation of ClassWriter(ClassReader classReader, int flags)所述:


  构造一个新的ClassWriter对象,并为“主要添加”字节码转换启用优化。这些优化如下:
  
  
  将原始类中的常量池和引导程序方法照原样复制到新类中,这样可以节省时间。如有必要,将在末尾添加新的常量池条目和新的引导程序方法,但是不会删除未使用的常量池条目或引导程序方法。
  未转换的方法可以直接从原始类的字节码直接复制到新类中(即,无需为所有方法指令发出访问事件),从而节省了大量时间。 ClassReader接收来自ClassWriter(而不是任何其他ClassVisitor实例)的MethodVisitor对象,可以检测到未转换的方法。
  


因此,当您在ClassWriter方法中将ClassReader直接链接到accept时,所有方法访问者都将来自编写器,因此,所有访问者都将直接复制。

当您要重大更改类或构造新的类时,应使用构造函数ClassWriter(int flags)

请注意,COMPUTE_FRAMES已暗含COMPUTE_MAXS。在上面的示例中,我没有指定任何方法,因为无论如何都复制了方法。当您实际上要更改或添加代码并需要COMPUTE_FRAMES时,应该为阅读器指定SKIP_FRAMES,因为无论如何从头开始重新计算原始帧,都没有必要解码。

因此,典型的转换设置如下所示:

public class MyClassVisitor extends ClassVisitor {

    public MyClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
                                     String signature, String[] exceptions) {
        MethodVisitor visitor = super.visitMethod(
            access, name, desc, signature, exceptions);
        if(method matches criteria) {
            visitor = new MyMethodVisitorAdapter(visitor);
        }
        return visitor;
    }
}




try(FileInputStream in = new FileInputStream(new File("input.class"))) {
    ClassReader classReader = new ClassReader(in);
    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
    classReader.accept(new MyClassVisitor(classWriter), ClassReader.SKIP_FRAMES);
    Files.write(Paths.get("output.class"), classWriter.toByteArray());
}


在通过构造函数链接访问者时,您没有覆盖的每个方法都会委托给链接的访问者,并在最终目标为ClassWriter时复制原始构造。 MethodVisitor提供的ClassWriter。如果该方法不满足您的转换条件,则您返回原始的MethodVisitor,上述优化仍然适用。方法访问者遵循与类访问者相同的模式,覆盖您要拦截的那些方法。

顺便说一句,您应该避免混合使用旧的I / O和NIO。您的代码的简化变体如下所示

ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("input.class")));
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
classReader.accept(new MyClassVisitor(classWriter), ClassReader.SKIP_FRAMES);
Files.write(Paths.get("output.class"), classWriter.toByteArray());


注意阅读和写作之间的对称性

但是,当您使用getResource等时,可能会被迫处理InputStream。但是对于通过系统类加载器可访问的类,您也可以仅将类名称传递给ClassReader(String)构造函数。

09-04 08:01