使用 Java ASM 库从 ClassNode 获取更新的字节数组

Getting updated byte array from ClassNode with Java ASM library

使用 Java ASM library you can parse and modify compiled Java classes. I'm trying to make a simple class change and store everything back into the Jar file. Example codes I find don't seem to correctly convert the ClassNode to a byte[]:

private static Map<String, byte[]> getClassBytes(final Map<String, ClassNode> classNodes)
{
    final HashMap<String, byte[]> classesMap = new HashMap<>();

    for (final ClassNode classNode : classNodes.values())
    {
        // TODO Get byte array from ClassNode
        final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        final String key = classNode.name;
        final byte[] byteArray = classWriter.toByteArray();
        classesMap.put(key, byteArray);
    }

    return classesMap;
}

此外,在上面的链接示例中,MappedClass 是什么?我在任何地方都找不到它的定义。

当运行上面的代码时,产生的byte[]只是默认的,而不是class的实际字节码。这是有道理的,不需要 adding/setting 正确的 class 字节。怎么做到的?

您创建了一个新的 ClassWriter 对象,没有传入任何相关内容(设置值,仅此而已)。然后在其上调用 toByteArray。用 5 秒的时间想一想,你应该得出这样的结论:这不可能行得通。您刚刚创建的 classWriter 实例不可能知道您想要编写的实际 class 内容 - 该代码流中没有任何内容提供给此 ClassWriter 实例。

链接代码的质量非常可疑。例如,它将 jar 文件作为资源泄漏(它没有得到适当的资源保护)。它以相当愚蠢的方式使用旧的 API (File) 和新的概念 (.stream())。它执行旧的 'catch an exception, run e.printStackTrace(), keep going' shtick,这是 极其糟糕的代码实践 。它出现在一个简单的例子中是值得怀疑的;它出现在一个明显准备就绪的例子中是不可原谅的)。它涉及实际上不需要的过时的第三方 deps (IOUtils),特别是考虑到此代码不会在低于 java8 的任何情况下编译,因此显然 'at least java 8' 是公平的游戏这个例子。

然后,最后,是的,你放大的东西:那个 ClassWriter 代码。刚刚坏了。

我认为最好忘记这个博客 post。省不了了。

好的,那我该怎么做呢?

好吧,您一开始就把两个完全不相关的东西混为一谈 'write a jar file' 和 'produce the byte content of a class file' 是 不相关的,所以不要混淆它们。您有 2 个独立的作业,因此编写执行作业 1 的代码并对其进行测试。然后编写执行作业 2 的代码,并对其进行测试。然后编写代码,使用您为作业 1 和 2 编写的代码并进行测试,瞧。

如何写入现有的 jar?

你不知道。不是它是如何工作的。你做一个新的。如果你真的需要,一旦你完成了,你会使用 Files.move 到 copy/move 它 'over' 旧的,因此有效地 'replacing it'.

如何制作新罐子?

new JarOutputStream 例如。检查 this SO answer 以了解有关如何操作的一些见解。

如何在给定 ASM 节点的情况下为 JarOutputStream 提供 API?

例如 this tutorial 所示,ASM 与其说是一种 'edit classes in place' 工具,不如说是一种 'edit classes in transit' 工具。你创建了一个 reader 它自己什么都不做,然后你堆放在 reader 各种访问者上,这些访问者在处理节点时会转换节点,然后你将这一切都连接到一个编写器并点击开始按钮。部分原因是 ASM 甚至可能无法将整个 class 文件保存在内存中。

创建 class 编写器后,您应该调用 class 编写器上的各种方法来实际 'write' class 文件。您 'write' 基本统计数据,例如 class 的名称等,然后您 'write' 字段、方法、初始值设定项等。

如果你想基于现有的 class 编写(即转换已经存在的东西而不是从头开始制作 class 文件),你会制作 reader ,并随时进行转换。我链接的那个教程展示了如何做到这一点。