我的目标是改变这个班级
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();
}
}
如有任何帮助,我们将不胜感激
也许这个问题的答案来得相当晚,但我离题了。
正如评论中提到的,其中一个解决方案是使用检测 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
方法为代价的,这在您编写库时是不可能的。