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
来阅读说明,但是我无法保存课程
答案 0 :(得分:2)
您的代码缺少将类功能从源实际复制到目标的步骤:
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对象,并为“主要添加”字节码转换启用优化。这些优化如下:
- 将原始类中的常量池和引导程序方法照原样复制到新类中,这样可以节省时间。如有必要,将在末尾添加新的常量池条目和新的引导程序方法,但是不会删除未使用的常量池条目或引导程序方法 。
- 未转换的方法直接从原始类的字节码中复制(即在新类中照原样复制(即,不发出所有方法指令的访问事件)),从而节省了 lot 的时间。通过以下事实检测未转换的方法:ClassReader接收来自ClassWriter(而不是来自任何其他ClassVisitor实例)的MethodVisitor对象。
因此,当您在ClassWriter
方法中将ClassReader
直接链接到accept
时,所有方法访问者都将来自编写器,因此,所有访问者都将直接复制。 / p>
当您要显着更改类或构造新类时,应改用构造函数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)
构造函数。