Search code examples
springspring-aopjava-bytecode-asmcglib

How to view Bytecode spring framework generated proxy classes?


Is there API in Spring AOP/ASM libraries that lets us read the bytecode representation of Spring Generated Proxy class. In my test code, I am having access to the class file.

private static void printClassBytes(String classFilePath) throws Exception{
    TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
    ClassReader reader = new ClassReader(new FileInputStream(new File("/xxx/asm_source/asmtest-0.0.1-SNAPSHOT/com/test/asm/Application.class")));
    
    reader.accept(visitor, 0);
}

But in my application, Proxy class is generated using Spring Integration Gateway in runtime, I only have object reference of Proxy Object. Is there some API in Spring or ASM that lets me find the bytecode of the corresponding Proxy class, using object reference Something like

private static void printClassBytes(Object obj) throws Exception{

Solution

  • I do not know if Spring has on-board means to do that, but AFAIK the ASM classes embedded in Spring do not include the TraceClassVisitor. So if you like its output, you have to use ASM (artifact asm-util) directly, which I assume you did for your sample code. If you want to stick with on-board means, you can just write a transformer which dumps the full byte code as a class file into a file and then look at the file with the JDK command line tool javap, e.g. via javap -c -p -v MyDumpedBytes.class.

    Anyway, an easy thing to do is to implement a Java agent and attach it to the Java command line starting the Spring project via -javaagent:/path/to/my-agent.jar. I found this article for you which explains how to implement a simple Java agent with manifest file etc. and also how to attach it to a running process via Java attach API. The article uses Javassist as an example for writing a transformer, but you can just use ASM instead.

    Your Java agent + transformer would look something like this:

    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.util.TraceClassVisitor;
    
    import java.io.PrintWriter;
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.lang.instrument.Instrumentation;
    import java.security.ProtectionDomain;
    
    class TraceClassTransformer implements ClassFileTransformer {
    
      /**
       * Attach agent dynamically after JVM start-up
       */
      public static void agentmain(String commandLineOptions, Instrumentation instr) {
        premain(commandLineOptions, instr);
      }
    
      /**
       * Start agent via <code>-javaagent:/path/to/my-agent.jar=<i>options</i></code> JVM parameter
       */
      public static void premain(String commandLineOptions, Instrumentation instrumentation) {
        TraceClassTransformer transformer = new TraceClassTransformer();
        instrumentation.addTransformer(transformer, true);
      }
    
      @Override
      public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        dumpClass(classfileBuffer);
        // Do not apply any transformation
        return null;
      }
    
      private void dumpClass(byte[] classfileBuffer) {
        TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
        ClassReader reader = new ClassReader(classfileBuffer);
        reader.accept(visitor, 0);
      }
    
    }
    

    Just make sure that the agent's manifest enables retransformation via Can-Retransform-Classes: true.

    After the agent has started, you can just call instrumentation.retransformClasses(proxyInstance.getClass()); and then enjoy the log output.

    In order to make it a bit simpler for this example, let us use byte-buddy-agent which contains a neat little tool set to attach transformers during runtime without the need to wrap them into Java agents. The artifact is small and does not contain the rest of ByteBuddy, just the agent tool.

    That would simplify your class to (you can keep or drop the premain and agentmain methods, depending on whether you are planning to use the class as a Java agent or not):

    import net.bytebuddy.agent.ByteBuddyAgent;
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.util.TraceClassVisitor;
    
    import java.io.Closeable;
    import java.io.PrintWriter;
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.lang.instrument.Instrumentation;
    import java.lang.instrument.UnmodifiableClassException;
    import java.lang.reflect.Proxy;
    import java.security.ProtectionDomain;
    
    class TraceClassTransformer implements ClassFileTransformer {
      public static void main(String[] args) throws UnmodifiableClassException {
        // Easy way to get an Instrumentation instance, so we can directly register our transformer on it
        Instrumentation instrumentation = ByteBuddyAgent.install();
        // I am just creating a Java dynamic proxy for a JRE interface. In your own application,
        // you would just get a reference to a dynamic proxy created by Spring.
        Object proxyInstance = Proxy.newProxyInstance(
          Closeable.class.getClassLoader(),
          new Class<?>[] { Closeable.class },
          (proxy, method, args1) -> null
        );
    
        // Register + use dummy ClassFileTransformer, then unregister again (optional)
        TraceClassTransformer transformer = new TraceClassTransformer();
        try {
          instrumentation.addTransformer(transformer, true);
          instrumentation.retransformClasses(proxyInstance.getClass());
        }
        finally {
          instrumentation.removeTransformer(transformer);
        }
      }
    
      @Override
      public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        dumpClass(classfileBuffer);
        // Do not apply any transformation
        return null;
      }
    
      private void dumpClass(byte[] classfileBuffer) {
        TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
        ClassReader reader = new ClassReader(classfileBuffer);
        reader.accept(visitor, 0);
      }
    }
    

    When running the sample main class, you get a console log like:

    // class version 58.0 (58)
    // access flags 0x11
    public final class com/sun/proxy/$Proxy0 extends java/lang/reflect/Proxy implements java/io/Closeable {
    
    
      // access flags 0xA
      private static Ljava/lang/reflect/Method; m0
    
      // access flags 0xA
      private static Ljava/lang/reflect/Method; m1
    
      // access flags 0xA
      private static Ljava/lang/reflect/Method; m2
    
      // access flags 0xA
      private static Ljava/lang/reflect/Method; m3
    
      // access flags 0x1
      public <init>(Ljava/lang/reflect/InvocationHandler;)V
        ALOAD 0
        ALOAD 1
        INVOKESPECIAL java/lang/reflect/Proxy.<init> (Ljava/lang/reflect/InvocationHandler;)V
        RETURN
        MAXSTACK = 2
        MAXLOCALS = 2
    
    (...)