Java ASM ClassReader报找不到类异常,如何插桩OkHttpClient.Builder?
解决OkHttpClient.Builder插桩时ClassReader找不到类的问题及build()方法插桩实现
问题原因
你遇到的Class not found本质是类加载上下文不匹配:
- 能正常实例化
OkHttpClient.Builder是因为应用运行时类路径包含了OkHttp依赖,但插桩代码(比如Gradle构建阶段的Transform、运行时字节码修改工具)使用的类加载器,并没有将OkHttp的类纳入可搜索范围,直接用类名构造ClassReader自然找不到类。
解决方案
1. 正确获取类字节流(Gradle编译期插桩场景)
如果是在Gradle Transform中做字节码插桩,不要直接通过类名加载,而是遍历输入的Jar/目录文件,精准读取OkHttpClient$Builder.class的字节数组来创建ClassReader:
// 遍历Transform输入的Jar文件 for (JarInput jarInput : inputs) { try (JarFile jarFile = new JarFile(jarInput.getFile())) { Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); // 匹配Builder类的字节文件路径 if ("okhttp3/OkHttpClient$Builder.class".equals(entry.getName())) { // 读取类字节流 InputStream is = jarFile.getInputStream(entry); byte[] classBytes = new byte[(int) entry.getSize()]; is.read(classBytes); // 创建ClassReader ClassReader cr = new ClassReader(classBytes); // 后续执行插桩逻辑 processBuilderClass(cr); break; } } } catch (IOException e) { e.printStackTrace(); } }
2. 用目标类的类加载器获取字节流(运行时插桩场景)
如果是在应用运行时做插桩,直接复用OkHttpClient.Builder自身的类加载器来读取字节流,避免当前类加载器的限制:
Class<?> builderClass = okhttp3.OkHttpClient.Builder.class; // 通过类加载器获取类的字节文件输入流 InputStream is = builderClass.getClassLoader().getResourceAsStream("okhttp3/OkHttpClient$Builder.class"); if (is != null) { ClassReader cr = new ClassReader(is); // 执行插桩逻辑 }
3. 实现build()方法的拦截器插入逻辑
拿到ClassReader后,用ASM的ClassVisitor和MethodVisitor修改build()方法,在返回OkHttpClient实例前,插入添加自定义拦截器的逻辑:
private byte[] processBuilderClass(ClassReader cr) { ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); // 匹配build()方法:方法名=build,方法描述符=()Lokhttp3/OkHttpClient; if ("build".equals(name) && "()Lokhttp3/OkHttpClient;".equals(desc)) { return new MethodVisitor(Opcodes.ASM9, mv) { @Override public void visitInsn(int opcode) { // 在方法返回指令(ARETURN)前插入逻辑 if (opcode == Opcodes.ARETURN) { // 加载当前Builder实例(this) mv.visitVarInsn(Opcodes.ALOAD, 0); // 创建自定义拦截器实例(替换为你的拦截器全类名) mv.visitTypeInsn(Opcodes.NEW, "com/yourpackage/CustomOkHttpInterceptor"); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/yourpackage/CustomOkHttpInterceptor", "<init>", "()V", false); // 调用Builder的addInterceptor方法 mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "okhttp3/OkHttpClient$Builder", "addInterceptor", "(Lokhttp3/Interceptor;)Lokhttp3/OkHttpClient$Builder;", false); } super.visitInsn(opcode); } }; } return mv; } }; cr.accept(cv, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); }
注意事项
- 确保ASM版本与目标Java/Android版本兼容(Android项目推荐ASM 9.x)。
- Gradle插桩场景下,需将修改后的字节数组写回对应的输出文件(Jar或目录),避免插桩失效。
- 配置混淆规则,保证
OkHttpClient$Builder和自定义拦截器类不被混淆,否则插桩后的代码会因类名/方法名变更报错。
内容的提问来源于stack exchange,提问作者rysv
相关产品推荐
相关产品推荐

