Continuing from Java Dynamic Instrumentation #1, this post will cover some more advanced features of the Javassist API.
In the previous post, we went over the ClassPool
, CtClass
, and CtMethod
classes and how to add code to the beginning and end of a method. In this post, we will cover the dynamic creation and addition of classes, methods, and fields.
In this post, we are going to start with a fairly complex piece of code, and I will then break it down into steps.
HelloWorld.java:
Inst3.java:
$ javac -classpath /path/to/javassist.jar: Inst3.java HelloWorld.java
$ java -cp /path/to/javassist.jar: Inst3
Hello World!
Goodbye world
-------
Hello New World
TestName
Compared to the code from the previous post, only a few lines are the same/similar, while the majority of the code is new. The first new lines of code are the pair consisting of CtClass nhw_ctc = pool.makeClass("NewHelloWorld")
and nhw_ctc.setSuperclass(hw_ctc)
. They create a new class called NewHelloWorld
and set it to extend from the HelloWorld
class respectively.
The next two lines, `CtField f1 = CtField.make("private String name;", nhw_ctc)` and `nhw_ctc.addField(f1)` are used to create and add a new field, `private String name`, to the `NewHelloWorld` class.
Next is a block of code:
This block creates and adds a constructor for NewHelloWorld
that will the field we just created for every new NewHelloWorld
instance.
Next are two blocks of code:
These create two methods and add them both to the NewHelloWorld
class. However, one of the methods, void do1()
already exists in the super class, HelloWorld
. The addition of this new void do1()
method causes it to override the one in the super class. Therefore any calls to this method in NewHelloWorld
will use the new void do1()
instead of the original one.
Now, knowing all of this, the output of the code can be easily explained.</p
Hello World!
Goodbye world
-------
Hello New World
TestName
The first two lines are what they are because we did not modify the HelloWorld
class. However, in the new class NewHelloWorld
, we have overrided the void do1()
method such that the new one calls System.out.println("Hello New World")
and then calls the other new method that we added to NewHelloWorld
, void do3()
. do3()
simply prints out the value of the new field, name
, that was added to NewHelloWorld
. If you remember, the new constructor that we added to NewHelloWorld
sets name
to the string “TestName”.
While this all may seem easy now (hopefully), it is important to note the ordering of the code. Because each piece of code that we added is compiled in the corresponding make(java.lang.String src, CtClass declaring)
method, the referenced values in the lines of code must already exist before each of these make
methods are run, or else a CannotCompileException
will be thrown. Because of this, the constructor and do3()
had to be created after the field was added because they referenced said field; and do1()
had to be created after do3()
was added because it made a call to do3()
.