Java ASM 在运行时覆盖现有的类/方法

问题描述 投票:0回答:1

我的目标是改变这个班级

public Class C {
    public static void print(int i) {
        System.out.println(i);
    }
}

进入这个类,然后用这个序列调用

print()
方法

C.print(1);  // call original method

// Code that overrides existing class  

C.print(2);  // call overridden method

作为,

    public Class C {
        public static void print(int i) {
            System.out.println(i);
            System.out.println(i);
        }
    }

我一直在阅读http://download.forge.objectweb.org/asm/asm4-guide.pdf的文档,但我似乎不知道如何覆盖现有的类/方法。 (我想在内存中完成所有操作,我不想将类写入磁盘。)我已经能够使用类加载器动态创建类,并通过反射调用更改后的方法,但我不明白如何覆盖现有的。

到目前为止,这是我的尝试。

import org.objectweb.asm.*;

public class MainClass implements Opcodes {

    public static void main(String[] args){
        try {
            // print out original method
            C.print(1);
            // new line to separate calls
            System.out.println();

            // get the bytes i want to inject
            byte[] b1 = CDump.dump();

            // dynamically load the class
            MyClassLoader loader = new MyClassLoader();
            Class<?> myClass = loader.defineClass("C", b1);
            // invoke the method
            myClass.getMethods()[0].invoke(null, new Object[] { 2 });
            // new line to separate calls
            System.out.println();

            // ??? (code that overrides existing class)

            // i want to call the overridden method
            C.print(2);

        } catch (Exception e) {
        e.printStackTrace();
        }
}

public class MyClassLoader extends ClassLoader {

    public Class defineClass(String name, byte[] b) {
        return defineClass(name, b, 0, b.length);
    }

}

来自 ASMifier 的代码。这包含我想要执行的编辑

public class CDump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        MethodVisitor mv;

        cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "C", null, "java/lang/Object", null);

        cw.visitSource("C.java", null);

        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(2, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "LC;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "print", "(I)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(19, l0);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitVarInsn(ILOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V");
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(20, l1);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitVarInsn(ILOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V");
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLineNumber(21, l2);
            mv.visitInsn(RETURN);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLocalVariable("i", "I", null, l0, l3, 0);
            mv.visitMaxs(2, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

如有任何帮助,我们将不胜感激

java java-bytecode-asm
1个回答
0
投票

也许这个问题的答案来得相当晚,但我离题了。

正如评论中提到的,其中一个解决方案是使用检测 API,正如所指出的,某些 JVM 不支持该 API,恕我直言,这是一个快速但肮脏的解决方案。

另一种方法是在 main 方法中创建类加载器(例如,

Entry.main
)。然后,加载该类,该类实际上将从您刚刚创建的类加载器引导应用程序(例如,
Bootstrap
,及其各自的
main
方法)。现在,在
Bootstrap
类中运行的所有代码都应该使用您创建的类加载器。这是一个粗略的例子:

// Entry.java

public static void main(String[] args) {
    ClassLoader parent = getClass().getClassLoader();
    MyClassLoader cl = new MyClassLoader(parent);
    Class<?> bootstrap = cl.loadClass("me.myapp.Bootstrap");
    try {
        Method method = bootstrap.getMethod("main", String[].class);
        method.invoke(null);
    } catch (NoSuchMethodException e) {
        // ...
    } catch (InvocationTargetException e) {
        // ...
    }
}

// Bootstrap.java

public static void main(String[] args) {
    System.out.println("Hello from MyClassLoader!");
}

这种方法的主要优点是它几乎可以在所有 JVM 上运行。然而,这是以要求您实际控制

main
方法为代价的,这在您编写库时是不可能的。

© www.soinside.com 2019 - 2024. All rights reserved.