0

I have to modify the following class at runtime to print the value of instance variable 'count' at the end of each method.

package test.hib.javaassist;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class JavaAssistTest {

    int count;


    public void doSomething1(){
        count++;
    }

    public void doSomething2(){
        count++;
    }

    public void doSomething3(){
        count++;
    }

    public void doSomething4(){
        count++;
    }

}

Following is the main class, where i am trying to alter the byte code with the help of javaassist.

package test.hib.javaassist;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class Main {

    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("test.hib.javaassist.JavaAssistTest");
        CtMethod[] methods = cc.getDeclaredMethods();

        for(CtMethod method : methods){
            if(! (method.getName().equals("main"))){
                method.insertAfter("{System.out.println(count);}");
                //method.insertAfter("System.out.println($type);");
            }
        }

        cc.writeFile();

        System.out.println("Completed editting");

        JavaAssistTest test = new JavaAssistTest();
        test.doSomething1();
        test.doSomething2();
        test.doSomething3();
        test.doSomething4();

        System.out.println("Finished");

    }

}

Currently it is printing

Completed editting
Finished

i want it to print

Completed editting
1
2
3
4
Finished

Can you pointout the mistake in my code?

Renjith
  • 1,122
  • 1
  • 19
  • 45
  • I'd imagine (just guessing) that the class was already loaded, so changes to the .class file won't be reflected in the runtime of the current JVM. You can try reloading the class as described e.g. here: http://stackoverflow.com/a/3971771/281108 – Ismail Badawi Nov 27 '16 at 06:31
  • hmm.... looks lil complicated. I tried a different way of loading the class. Class testClass = Loader.getSystemClassLoader().loadClass("test.hib.javaassist.JavaAssistTest"); JavaAssistTest test = (JavaAssistTest)testClass.newInstance(); But no change in results !! – Renjith Nov 27 '16 at 06:58
  • I'm pretty sure that will not reload the class if it was already loaded. – Ismail Badawi Nov 27 '16 at 06:59
  • Loader doc says 'Unlike a regular class loader, this class loader obtains bytecode from a ClassPool.' – Renjith Nov 27 '16 at 07:03

1 Answers1

1

The Javassist API is a bit confusing here. Have a look at the implementation of writeFile. It is a shortcut for writeFile(".") which saves the class in your program directory and not on the class path. You expect it to override the original class file what this method does not do. Your system class loader will not be able to locate the updated class file.

You can either manually save the class file at the right location using writeFile(String) or force the loading of the modified class by calling cc.toClass() which forces the loading of the modified class before your class loader looks it up manually.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thank you !! cc.toClass() worked. In the end, i have to use this in an application deployed in Tomcat Server. But it is given in the Java doc that the same cannot be applied for applications running on the application server. I have to try using 'toClass(java.lang.ClassLoader loader)'. – Renjith Nov 30 '16 at 02:59