You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让代码使用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包中的类(简单直接)

  1. 解压OkHttp的jar包,找到okhttp3/OkHttpClient.class文件
  2. 把你生成的OkHttpClientModified.class重命名为OkHttpClient.class,替换掉jar包里的对应文件
  3. 重新打包jar包:执行命令 jar cvf okhttp-modified.jar -C 解压目录/ .
  4. 项目中使用这个修改后的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字节码插桩的标准做法,在类加载阶段动态修改字节码,无需手动处理类加载逻辑:

  1. 编写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;
            }
        });
    }
}
  1. 打包Agent为jar包,在MANIFEST.MF中添加配置:Premain-Class: OkHttpAgent
  2. 运行程序时添加参数:-javaagent:你的agent.jar,JVM加载OkHttpClient时会自动应用你的修改

额外注意点

  • 你当前的插桩逻辑只处理了构造方法的RETURN指令,但OkHttpClient.Builder().build()调用的是带参数的构造方法(OkHttpClient(Builder)),需要确保所有<init>方法都被插桩覆盖。
  • 确保ASM中引用的logConstructorCall方法全类名正确,类名要用斜杠分隔(比如类在com.example包下,需写成com/example/ConstructorInjector)。

内容的提问来源于stack exchange,提问作者rysv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:47:37