Shigeru Chiba
Javassist is a class library for dealing with Java bytecode.
Java bytecode is stored in a binary file called a class file.
Each class file contains one Java class or interface.
The class This program first obtains a The To modify the definition of a class, the users must first obtain a
reference to the Note that There is also This method is a convenient method for invoking Javassist also provides a method for directly obtaining the
modified bytecode. To do this, call The contents of the class file for The default The search path that the users can add is not only a directory but also
a URL:
This program adds "http://www.foo.com:80/java/" to the class search
path. This URL is used only for searching classes belonging to a
package You can directly give a byte array to a The obtained Since If you want to directly construct a To define a new class from scratch, This program defines a class A new class can be also defined as a copy of an existing class.
The program below does that:
This program first obtains the If what classes are modified is known in advance,
the easiest way for modifying the classes is as follows:
If whether a class is modified or not is determined at load time,
the users can write an event listener so that it is notified
when a class is loaded into the JVM.
A class loader ( The method Note that All the classes written out by The two methods To register an event listener to a This program connects two
1. Reading bytecode
Javassist.CtClass
is an abstract
representation of a class file. A CtClass
(compile-time
class) object is a handle for dealing with a class file. The
following program is a very simple example:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
pool.writeFile("test.Rectangle"); // or simply, cc.writeFile()
ClassPool
object,
which controls bytecode modification with Javassist.
The ClassPool
object is a container of CtClass
object representing a class file.
It reads a class file on demand for constructing a CtClass
object and contains the constructed object until it is written out
to a file or an output stream.
ClassPool
object is used to maintain one-to-one
mapping between classes and CtClass
objects. Javassist
never allows two distinct CtClass
objects to represent
the same class. This is a crucial feature to consistent program
transformaiton. If you need, however, you can deal with multiple
instances of ClassPool
at the same time. To create a new
instance of ClassPool
, write the following code:
ClassPool cp = new ClassPool(null);
cp.appendSystemPath();
cp.insertClassPath("."); // or something appropriate
ClassPool.getDefault()
is just a singleton factory
method provided for convenience.
CtClass
object representing that class.
ClassPool.get()
is used for this purpose.
In the case of the program above, the CtClass
object
representing a class test.Rectangle
is obtained from
the ClassPool
object
and it is assigned
to a variable cc
. Then it is modified so that
the superclass of test.Rectangle
is changed into
a class test.Point
.
This change is reflected on the original class file when
ClassPool.writeFile()
is finally called.
writeFile()
is a method declared in not
CtClass
but ClassPool
.
If this method is called, the ClassPool
finds a CtClass
object specified with a class name
among the objects that the ClassPool
contains.
Then it translates that CtClass
object into a class file
and writes it on a local disk.
writeFile()
defined in CtClass
.
Thus, the last line in the program above can be rewritten into:
cc.writeFile();
writeFile()
in ClassPool
with the name of the class represented by
cc
.
write()
:
byte[] b = pool.write("test.Rectangle");
test.Rectangle
are
assigned to a variable b
in the form of byte array.
writeFile()
also internally calls write()
to obtain the byte array written in a class file.
ClassPool
returned
by a static method ClassPool.getDefault()
searches the same path as the underlying JVM.
The users can expand this class search path if needed.
For example, the following code adds a directory
/usr/local/javalib
to the search path:
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");
ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.foo.com", 80, "/java/", "com.foo.");
pool.insertClassPath(cp);
com.foo
.
ClassPool
object
and construct a CtClass
object from that array. To do this,
use ByteArrayClassPath
. For example,
ClassPool cp = ClassPool.getDefault();
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc = cp.get(name);
CtClass
object represents
a class defined by the class file specified by b
.
ClassPath
is an interface, the users can define
a new class implementing this interface and they can add an instance
of that class so that a class file is obtained from a non-standard resource.
CtClass
object
from a class file but you do not know the fully-qualified name
of the class, then
you can use makeClass()
in CtClass
:
ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);
makeClass()
returns the CtClass
object
constructed from the given input stream. You can use
makeClass()
for eagerly feeding class files to
the ClassPool
object. This might improve performance
if the search path includes a large jar file. Since
the ClassPool
object reads a class file on demand,
it might repeatedly search the whole jar file for every class file.
makeClass()
can be used for optimizing this search.
The CtClass
constructed by makeClass()
is kept in the ClassPool
object and the class file is never
read again.
2. Defining a new class
makeClass()
must be called on a ClassPool
.
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
Point
including no members.
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
cc.setName("Pair");
CtClass
object
for class Point
. Then it gives a new name Pair
to that CtClass
object.
If get("Point")
is called on the ClassPool
object, then a class file Point.class
is read again and
a new CtClass
object for class Point
is constructed
again.
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
CtClass cc1 = pool.get("Point"); // cc1 is identical to cc.
cc.setName("Pair");
CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc.
CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc.
3. Modifying a class at load time
CtClass
object by calling
ClassPool.get()
,
ClassPool.write()
or writeFile()
.
java.lang.ClassLoader
) working with
Javassist must call ClassPool.write()
for obtaining
a class file. The users can write an event listener so that it is
notified when the class loader calls ClassPool.write()
.
The event-listener class must implement the following interface:
public interface Translator {
public void start(ClassPool pool)
throws NotFoundException, CannotCompileException;
public void onWrite(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException;
}
start()
is called when this event listener
is registered to a ClassPool
object.
The method onWrite()
is called when write()
(or similar methods) is called on the ClassPool
object.
The second parameter of onWrite()
is the name of the class
to be written out.
start()
or onWrite()
do not have
to call write()
or writeFile()
. For example,
public class MyAnotherTranslator implements Translator {
public void start(ClassPool pool)
throws NotFoundException, CannotCompileException {}
public void onWrite(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException
{
CtClass cc = pool.get(classname);
cc.setModifiers(Modifier.PUBLIC);
}
}
write()
are made public
just before their definitions are translated into an byte array.
start()
and onWrite()
can modify not only a CtClass
object specified by
the given classname
but also
any CtClass
objects contained
in the given ClassPool
.
They can call ClassPool.get()
for obtaining any
CtClass
object.
If a modified CtClass
object is not written out immediately,
the modification is recorded until that object is written out.
ClassPool
,
it must be passed to a constructor of ClassPool
.
Only a single event listener can be registered.
If more than one event listeners are needed, multiple
ClassPool
s should be connected to be a single
stream. For example,
Translator t1 = new MyTranslator();
ClassPool c1 = new ClassPool(t1);
Translator t2 = new MyAnotherTranslator();
ClassPool c2 = new ClassPool(c1, t2);
ClassPool
s.
If a class loader calls write()
on c2
,
the specified class file is first modified by t1
and
then by t2
. write()
returns the resulting
class file.
First, onWrite()
on t1
is called since
c2
obtains a class file by calling write()
on c1
. Then onWrite()
on t2
is called. If onWrite()
called on t2
obtains a CtClass
object from c2
, that
CtClass
object represents the class file that
t1
has modified.