Friday, 8 February 2013

Dynamically defining a new java class at runtime using ASM



Recently , I came across a requirement where I had to dynamically define a totally new class at runtime , load it , get an instance of that class and then use that class in my business logic. Please note that, by dynamic construction I don't mean the Class.forName("") mechanism , as this assumes that the requested class is available in the runtime , which is not the case here.

The only way this can be achieved is, by writing the byte code to define the new class and then passing the byte array to your class-loader's defineClass() method. Byte code libraries like ASM can be used to write the byte code of the new class. However , this requires a deep understanding of the byte code structure and the ASM APIs and hence is not trivial enough.

There is a cool solution to this issue as outlined in the sequence of steps below. This solution uses the org.objectweb.asm.util.ASMifier class which is part of the ASM Byte Code library. No further knowledge of ASM APIs is needed however.

  • Using your favorite IDE/Editor write the java code implementation of the dynamic class you want to use in runtime. For example , lets take as example the following HelloWorld class which we want to dynamically construct at runtime.

public class HelloWorld {

            private static final String HELLO = "Hello World";

            public void hello() {
              System.out.println(HELLO);
            }
         }
  • Compile the java file to a class file
  • Excecute the following command on your terminal/console window.Ensure that you have the 2 jars in your classpath.
  • java -classpath asm-4.0.jar:asm-util-4.0.jar org.objectweb.asm.util.ASMifier HelloWorld.class
    
  • This will dump on the terminal window the the ASM code to generate the class's byte code. The generated code for , HelloWorld.class looks like this,
import java.util.*;
import org.objectweb.asm.*;
import org.objectweb.asm.attrs.*;
public class HelloWorldDump implements Opcodes {

public static byte[] dump () throws Exception {

ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "HelloWorld", null, "java/lang/Object", null);

{
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "HELLO", "Ljava/lang/String;", null, "Hello World");
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "hello", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello World");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}
  • Copy the dump() method body to your custom classloader's define class method as follows,
static class HelloWorldClassLoader extends ClassLoader {

                @Override
                protected Class findClass(String name) throws ClassNotFoundException {

                  if (name.endsWith(CLASS_NAME)) {

     //copy the ASM code in the dump() methods's implementation here

                        ....


                        ....
                        cw.visitEnd();

                        byte[] b = cw.toByteArray();
                        return defineClass(name, b, 0, b.length);

              } return super.findClass(name);
  • Invoke you custom class loader's loadClass method by passing the fully qualified class name of you dynamic class(HelloWorld.java in this case) as follows, to get the instance of your dynamic class. After that you can use reflection to create and instance of the class and use it to call methods, as shown below.
    Class helloWorldClass = 
        helloWorldClassLoader.loadClass(.class.getPackage().getName() + "." + "HelloWorld");
    
    //this will call the hello method of the HelloWorld class
    
    helloWorldClass.getDeclaredMethod("hello").invoke(helloWorldClass.newInstance());
    

No comments:

Post a Comment