Using the ClassFileTransformer in JDK 1.5

The package java.lang.instrument allows you to modify class classfiles as they are loaded. On the command line, you register your own implementation of the ClassFileTransformer and this will be called by the VM every time a class is loaded.

On this page, you’ll find a working example of a ClassFileTransformer that will add a log statement to every method of every class that is about to be loaded. To perform the bytecode manipulation, I used the well-known Jakarta BCEL library.

Two related Q&A’s to achieve this kind of functionality are
How do I get started with writing a dynamic proxy class?
How do I get started with AspectJ?

MethodInstrument.java:

import java.lang.instrument.Instrumentation;
import java.security.*;
  
import org.apache.bcel.Constants;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;
 
import java.io.*;
import java.lang.reflect.*;
 
public class MethodInstrument 
{
   public static void premain(String options, Instrumentation instrumentation) {
      instrumentation.addTransformer(new EntryExitMethodTransformer());
   }
   
   public static class EntryExitMethodTransformer implements ClassFileTransformer 
   {
      public byte[] transform(ClassLoader loader, String cn, Class classBeingRedefined, 
                              ProtectionDomain protectionDomain, byte[] classfileBuffer) 
                                  throws IllegalClassFormatException
      {
         //System.out.println("Transforming class " + cn);
         if (cn.startsWith("java") || cn.startsWith("javax") || cn.startsWith("sun")) {
            return classfileBuffer;
         }
         try {
            ByteArrayInputStream bais = new ByteArrayInputStream(classfileBuffer);
             
            ClassParser parser = new ClassParser(bais, cn);
            JavaClass clazz = parser.parse();
        
            ClassGen classGen = new ClassGen(clazz);
        
            Method[] methods = clazz.getMethods();
            for (int i=0; i<methods.length; i++) {
               InstructionFactory instructionFactory = new InstructionFactory(classGen);
               InstructionList instructionList = new InstructionList();
               ConstantPoolGen constantPoolGen = classGen.getConstantPool();
               String className = classGen.getClassName();            
  
               Method method = methods[i];
               MethodGen wrapGen = new MethodGen(method, className, constantPoolGen);
               instructionList = wrapGen.getInstructionList();
                
               String text = "Call to method " + cn + "." + method.getName();
               instructionList.insert(instructionFactory.createInvoke(
                        "java.io.PrintStream", "println"
                        , Type.VOID, new Type[] { Type.STRING }
                        , Constants.INVOKEVIRTUAL
                      ));
               instructionList.insert(new PUSH(constantPoolGen, text));
               instructionList.insert(instructionFactory.createFieldAccess(
                        "java.lang.System", "out", new ObjectType("java.io.PrintStream")
                        , Constants.GETSTATIC
                      ));
   
               wrapGen.stripAttributes(true);
               wrapGen.setMaxStack();
               wrapGen.setMaxLocals();
      
               classGen.removeMethod(method);
               classGen.addMethod(wrapGen.getMethod());
            }
    
            return classGen.getJavaClass().getBytes();
         }
         catch(Exception e) {
            throw new IllegalClassFormatException(e.getMessage());
         }
      }
   }
}

Be sure to include the BCEL library in your classpath (eg. bcel-5.1.jar).

A simple program to test our EntryMethodTransformer:

Test.java:

public class Test
{
   public static void main(String []args) {
      a();
   }
   
   public static void a() {
      b();
   }
   
   public static void b() {
      c();
   }
   
   public static void c() {
      System.out.println("C reached!");
   }
}

Now run Test, but provide the MethodInstrument agent on the command line:

java -javaagent:MethodInstrument Test

outputs:

Call to method Test.main
Call to method Test.a
Call to method Test.b
Call to method Test.c
C reached!