In this issue, I am going to show how to inject the byte-code at runtime using Javassist.

Initial target

In my team, we wrote some Java method to retrieve users information via Soap and CORBA, the Soap and CORBA libraries were generated by another team, we have no privilege to change the behaviors and source code of those library. As you know, both CORBA and SOAP needs a server to handle requests, it means that we could not running a single test case when no server available.

Some theory told that we should put those test cases into system integration level, rather than now unit test. I think, the theory makes sense, but I doubt it could resolve all problems. So I tried to figure out  how to resolve this in unit test level.

The first solution always be mock, after a long time journey in http://www.jmock.org/, I found it’s good, but can not fix our issue. our pseudo code is:

public void Fun

{

CORBA.Fun(…)

}

I can not found how to mock a static method without change the original source code, after all, the mocks could not access the classes/objects which reside in the method, those objects only could be see by the method, but not outside source code.

Why hijack it

I am a lazy man, I don’t want those thing too complex, especial on unit test, if we spend more than 25% of efforts on unit test, I think it is worthless, it brings more complexities into our development.

I was trying to found hack the source code of CORBA.Fun, my target was make sure the method act as what I want, and more important, without any business logic source code change.

I want that the programmers and unit test writers are separately, so the source code quality should be better even though it is painful for the team.

How to hijack it

I checked ASM(http://asm.ow2.org ), cglib(http://cglib.sourceforge.net/ ), Dynamic Proxy (implemented by JDK), and Jreload (http://code.google.com/p/jreloader/), eventually, I choose Javassist as my helper tool, here is the sample source code, it is really simple.

The service provider class(CORBA, SOAP, etc.)

package testasm.core;

public class ExternalHandler
{
    public ExternalHandler()
    {
    }

    public static int TestStatic(String str)
    {
        System.out.println("[Static method] " + str);
        return 9999;
    }

    public int TestInstance(String str)
    {
        System.out.println("[Instance method] " + str);
        return 9999;
    }
}

The consummer class

package testasm.core;

public class Comsumer
{
    public Comsumer()
    {
    }

    public int Foo()
    {
        ExternalHandler.TestStatic("Hello Static");
        ExternalHandler a = new ExternalHandler();
        return a.TestInstance("Hello Instance");
    }
}

Test class

package testasm.core;

import javassist.*;
import javassist.util.HotSwapper;

public class HelloWorld
{
    public HelloWorld()
    {
    }
    public static void TestAppender(String methodName,String str)
    {
        System.out.println("***["+methodName+"] This line is injected via method-invoking, the parameter is: "+str);
    }
    private static void HijactLocalClass()
        throws Exception
    {
        final String className="testasm.core.ExternalHandler";
        final String methodName="TestInstance";
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(className);
        if(null==cc)
        {
            throw new Exception("Could not found the class named "+className);
        }
        CtMethod m = cc.getDeclaredMethod(methodName);
        if(null==m)
        {
            throw new Exception("Could not found the method named "+methodName+" in the class "+className);
        }
        m.insertBefore("testasm.core.HelloWorld.TestAppender(\""+methodName+"\",$1);"
                       + "System.out.println(\"&&&["+methodName+"] This line is injected at the begin of the method, the parameter of the function is: \"+$1);"
                       + "return 10;");
        cc.toClass();
    }
    private static void HijactRemoteClass()
           throws Exception
       {

//           final String className=com.jeasonzhao.commons.utils.Guid.class.getName();
           final String className="com.jeasonzhao.commons.utils.Guid";
           final String methodName="newGuid";
           ClassPool pool = ClassPool.getDefault();
           CtClass cc = pool.get(className);
           if(null==cc)
           {
               throw new Exception("Could not found the class named "+className);
           }
           CtMethod m = cc.getDeclaredMethod(methodName);
           if(null==m)
           {
               throw new Exception("Could not found the method named "+methodName+" in the class "+className);
           }
           m.insertBefore("return \"Are you sure that you want generate a GUID?\";");
           cc.toClass();
       }

    public static void main(String[] argvs)
        throws Exception
    {
        ExternalHandler a=new ExternalHandler();
        System.out.println("-------------------------------------------------------------------------");
        HijactLocalClass();
        Comsumer c = new Comsumer();
        int x = c.Foo();
        System.out.println("$$$$$$$$$$$$     The return value of Foo is: " + x);

        System.out.println("-------------------------------------------------------------------------");
        HijactRemoteClass();
        for(int n=0;n<10;n++)
        {
            System.out.println(n+"  " + com.jeasonzhao.commons.utils.Guid.newGuid());
        }
    }
}

Shortcomings

If the classes you want to hijack have been loaded into JVM before hijack it, the JVM should report a duplicated class error.


Jeason Zhao (沈胜衣,斛律光) ------雪饮再现,一个人的江湖
我知道我是谁,我是沈胜衣,默默的活着,就像空气。