如何让代码使用ASM修改后的OkHttpClient第三方库类?
问题:ASM插桩OkHttpClient构造方法后未生效,如何让代码使用修改后的类?
我用ASM给第三方库的okhttp3.OkHttpClient构造方法插入了指令,反编译生成的OkHttpClientModified.class能看到新增代码,但运行test方法时,没输出预期的"Constructor called"日志。想问下怎么让后续代码用这个被插桩的类,是不是要修改类路径或者操作类加载器?
完整代码
import java.io.FileOutputStream; import java.io.IOException; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class ConstructorInjector { public static void main(String[] args) { String className = "okhttp3.OkHttpClient"; byte[] modifiedBytecode = new byte[0]; try { modifiedBytecode = ConstructorInjector.injectIntoConstructor(className); } catch (Exception e) { throw new IllegalArgumentException(e); } try (FileOutputStream fos = new FileOutputStream("OkHttpClientModified.class")) { fos.write(modifiedBytecode); } catch (Exception e) { throw new RuntimeException(e); } try { test(); } catch (IOException e) { throw new RuntimeException(e); } } // Example method to inject into constructor public static void logConstructorCall() { System.out.println("Constructor called"); } // Example method to modify a class public static byte[] injectIntoConstructor(String className) throws Exception { ClassReader classReader = new ClassReader(className); ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS); ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM7, classWriter) { // Override visitMethod to modify the constructor @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions); if (name.equals("<init>")) { // Override the constructor return new MethodVisitor(Opcodes.ASM7, methodVisitor) { @Override public void visitInsn(int opcode) { // Find the last instruction in the constructor if (opcode == Opcodes.RETURN) { // Insert code before the return instruction visitMethodInsn(Opcodes.INVOKESTATIC, "ConstructorInjector", "logConstructorCall", "()V", false); } super.visitInsn(opcode); } }; } return methodVisitor; } }; // Modify the class using the classVisitor classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); // Return the modified bytecode return classWriter.toByteArray(); } private static void test() throws IOException { OkHttpClient client = new OkHttpClient.Builder() .build(); Request request = new Request.Builder() .url("http://www.publicobject.com/helloworld.txt") .header("User-Agent", "OkHttp Example") .build(); Response response = client.newCall(request).execute(); String body = response.body().string(); System.out.println("++++ BODY: " + body); //response.body().close(); } }
问题分析与解决方法
核心原因
你现在只是把修改后的字节码写到了本地文件OkHttpClientModified.class,但JVM加载的还是原OkHttp jar包里的OkHttpClient类,根本没用到你修改的版本。
解决方法
方法1:替换原jar包中的类(简单直接)
- 解压OkHttp的jar包,找到
okhttp3/OkHttpClient.class文件 - 把你生成的
OkHttpClientModified.class重命名为OkHttpClient.class,替换掉jar包里的对应文件 - 重新打包jar包:执行命令
jar cvf okhttp-modified.jar -C 解压目录/ . - 项目中使用这个修改后的jar包代替原OkHttp jar包
方法2:自定义类加载器(动态加载修改后的类)
通过自定义类加载器,让JVM优先加载你修改后的字节码,而不是原类。修改main方法如下:
public static void main(String[] args) { String className = "okhttp3.OkHttpClient"; byte[] modifiedBytecode = new byte[0]; try { modifiedBytecode = ConstructorInjector.injectIntoConstructor(className); } catch (Exception e) { throw new IllegalArgumentException(e); } try { // 自定义类加载器,优先加载修改后的OkHttpClient ClassLoader customLoader = new ClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (name.equals("okhttp3.OkHttpClient")) { return defineClass(name, modifiedBytecode, 0, modifiedBytecode.length); } return super.findClass(name); } }; // 用反射调用test方法,避免系统类加载器提前加载原OkHttpClient Class<?> injectorClass = customLoader.loadClass("ConstructorInjector"); java.lang.reflect.Method testMethod = injectorClass.getDeclaredMethod("test"); testMethod.setAccessible(true); testMethod.invoke(null); } catch (Exception e) { throw new RuntimeException(e); } }
注意:这种方式下,test方法不能直接在编译期引用OkHttpClient,否则系统类加载器会提前加载原类。如果需要保留原有逻辑,可把test方法的实现改成反射调用。
方法3:使用Java Agent(推荐,无侵入式)
这是ASM字节码插桩的标准做法,在类加载阶段动态修改字节码,无需手动处理类加载逻辑:
- 编写Agent类:
import java.lang.instrument.Instrumentation; import org.objectweb.asm.ClassFileTransformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class OkHttpAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, java.security.ProtectionDomain protectionDomain, byte[] classfileBuffer) { // 只处理OkHttpClient类 if ("okhttp3/OkHttpClient".equals(className)) { ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new ClassVisitor(Opcodes.ASM7, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); // 处理所有构造方法 if ("<init>".equals(name)) { return new MethodVisitor(Opcodes.ASM7, mv) { @Override public void visitInsn(int opcode) { if (opcode == Opcodes.RETURN) { // 插入日志调用 visitMethodInsn(Opcodes.INVOKESTATIC, "ConstructorInjector", "logConstructorCall", "()V", false); } super.visitInsn(opcode); } }; } return mv; } }; cr.accept(cv, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); } return classfileBuffer; } }); } }
- 打包Agent为jar包,在
MANIFEST.MF中添加配置:Premain-Class: OkHttpAgent - 运行程序时添加参数:
-javaagent:你的agent.jar,JVM加载OkHttpClient时会自动应用你的修改
额外注意点
- 你当前的插桩逻辑只处理了构造方法的
RETURN指令,但OkHttpClient.Builder().build()调用的是带参数的构造方法(OkHttpClient(Builder)),需要确保所有<init>方法都被插桩覆盖。 - 确保ASM中引用的
logConstructorCall方法全类名正确,类名要用斜杠分隔(比如类在com.example包下,需写成com/example/ConstructorInjector)。
内容的提问来源于stack exchange,提问作者rysv
相关产品推荐
相关产品推荐

