Java ASM字节码注入失效:自定义类加载器未触发findClass求助
问题原因
Java类加载器遵循双亲委派模型:调用loadClass时,自定义类加载器会先委托父类加载器(此处为系统类加载器)尝试加载类。由于MyClass和MyClassInjector在同一类路径下,运行MyClassInjector时系统类加载器已提前加载MyClass,因此自定义类加载器调用loadClass("MyClass")时,父类加载器直接返回已加载的类,不会触发重写的findClass方法。
同时你的ASM代码存在逻辑错误:在cr.accept后直接调用cv.visitMethod重新定义myMethod,这会完全覆盖原方法代码,而非在原方法中注入逻辑。正确做法是通过ClassVisitor拦截原方法,在原有代码前后插入新逻辑。
解决方案
1. 重写loadClass打破双亲委派(解决类加载器问题)
自定义类加载器时,重写loadClass方法,对目标类MyClass跳过双亲委派,直接用自定义逻辑加载:
ClassLoader cl = new ClassLoader() { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 检查类是否已加载 Class<?> c = findLoadedClass(name); if (c == null) { if (name.equals("MyClass")) { // 加载修改后的MyClass字节码 c = defineClass(name, modifiedClass, 0, modifiedClass.length); } else { // 其他类仍走双亲委派流程 c = super.loadClass(name, resolve); } } if (resolve) { resolveClass(c); } return c; } } };
2. 修正ASM注入逻辑(解决方法覆盖问题)
修改MyClassVisitor,拦截myMethod并在原方法执行前后插入注入代码:
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 mv = super.visitMethod(access, name, desc, signature, exceptions); // 仅处理目标方法myMethod if ("myMethod".equals(name) && "()V".equals(desc)) { return new MyMethodVisitor(mv); } return mv; } } class MyMethodVisitor extends MethodVisitor { public MyMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM5, mv); } @Override public void visitCode() { // 在原方法代码执行前插入调用newMethod的逻辑 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyClass", "newMethod", "()V", false); super.visitCode(); } }
3. 完整修正后的MyClassInjector.java
import org.objectweb.asm.*; public class MyClassInjector { public static void main(String[] args) throws Exception { // 读取原MyClass字节码 ClassReader cr = new ClassReader("MyClass"); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); MyClassVisitor cv = new MyClassVisitor(cw); cr.accept(cv, 0); // 定义注入的newMethod方法 MethodVisitor mv2 = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "newMethod", "()V", null, null); mv2.visitCode(); mv2.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv2.visitLdcInsn("Injected code"); mv2.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv2.visitInsn(Opcodes.RETURN); mv2.visitMaxs(2, 0); mv2.visitEnd(); // 获取修改后的类字节码 byte[] modifiedClass = cw.toByteArray(); // 自定义类加载器 ClassLoader cl = new ClassLoader() { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null) { if (name.equals("MyClass")) { c = defineClass(name, modifiedClass, 0, modifiedClass.length); } else { c = super.loadClass(name, resolve); } } if (resolve) { resolveClass(c); } return c; } } }; // 加载修改后的类并调用方法 Class<?> myClass = cl.loadClass("MyClass"); Object myObject = myClass.newInstance(); myClass.getMethod("myMethod").invoke(myObject); } } 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 mv = super.visitMethod(access, name, desc, signature, exceptions); if ("myMethod".equals(name) && "()V".equals(desc)) { return new MyMethodVisitor(mv); } return mv; } } class MyMethodVisitor extends MethodVisitor { public MyMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM5, mv); } @Override public void visitCode() { mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyClass", "newMethod", "()V", false); super.visitCode(); } }
运行结果
执行后会依次输出:
Injected code Hello, world!
内容的提问来源于stack exchange,提问作者rysv
相关产品推荐
相关产品推荐

