0

I am trying to instrument OkHttpClient builder class. However, I am not able to create ClassReader in the first place.

import org.objectweb.asm.ClassReader;
...
// this works meaning dependency from gradle is satisfied
okhttp3.OkHttpClient.Builder test = new okhttp3.OkHttpClient.Builder();
...
// but this fails with java.io.IOException: Class not found
ClassReader cr = new ClassReader("okhttp3.OkHttpClient.Builder");

Assuming this is even possible, is there a way to achieve this? My intention is to insert an interceptor to the build() method of Builer class.

rysv
  • 2,416
  • 7
  • 30
  • 48

1 Answers1

2

The argument to ClassReader is in dollarized syntax.

This is a weirdo, hybrid JVM/java syntax where you use dots as separator between the package, and a dot between package and class, but then dollars for inner class separation. Builder is an inner class of OkHttpClient (the capitalization gives it away: OkHttpClient is the first capitalized thing, so that's a class, and thus Builder is an inner class of that), thus, you end up at okhttp3.OkHttpClient$Builder.

I'd replace that code with:

ClassReader cr = new ClassReader(okhttp3.OkHttpClient.Builder.class.getName());

This gets you the right (with dollars for inners) name and ensures you can't typo that thing (or rather, that you get a compile/write time error if you do, instead of figuring it out later at runtime).

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • 2
    The official term for the “weirdo syntax” is [Binary Name](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ClassLoader.html#binary-name) but due to how the translation works (`ClassReader` does a simple `replace('.', '/')`), it would also work if you pass in the JVM internal name. Calling `getName()` on a `Class` instance does indeed provide you the Binary Name as intended, but, of course, it’s a no-go if you want to perform pre-load transformation, as this expression has the side effect of loading the class. – Holger Mar 13 '23 at 11:05