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 This creates a If you have two To modify the definition of a class, the users must first obtain a
reference to the The default The search path that the users can add is not only a directory but also
a URL:
This program adds "http://www.javassist.org:80/java/" to the class search
path. This URL is used only for searching classes belonging to a
package Furthermore, you can directly give a byte array
to a The obtained If you do not know the fully-qualified name of the class, then you
can use The users can extend the class search path. They can define a new
class implementing 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 Once a To avoid this restriction, you should call If
A
This specification of You must not call any method on that
If
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"));
cc.writeFile("test.Rectangle");
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 unless two independent ClassPool
are created.
This is a significant feature for consistent program
transformaiton. To create multiple
instances of ClassPool
, write the following code:
ClassPool cp = new ClassPool();
cp.appendSystemPath(); // or append another path by appendClassPath()
ClassPool
object that behaves as the
default ClassPool
returned by
ClassPool.getDefault()
does.
ClassPool.getDefault()
is a singleton factory method
provided for convenience.
ClassPool
objects, then you can
obtain, from each ClassPool
, a distinct
CtClass
object representing the same class file. You can
differently modify these CtClass
objects to generate
different versions of the class.
CtClass
object representing that class.
get()
in ClassPool
is used for this purpose.
In the case of the program shown at the beginning, 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 writeFile()
in CtClass()
is
finally called.
writeFile()
translates the CtClass
object
into a class file and writes it on a local disk.
Javassist also provides a method for directly obtaining the
modified bytecode. To obtain the bytecode, call toBytecode()
:
byte[] b = cc.toBytecode();
ClassPool
returned
by a static method ClassPool.getDefault()
searches the same path that the underlying JVM (Java virtual machine) has.
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.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);
org.javassist
.
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
.
The ClassPool
reads a class file from the given
ByteArrayClassPath
if get()
is called
and the class name given to get()
is equal to
one specified by name
.
makeClass()
in ClassPool
:
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
a 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.
ClassPath
interface and give an
instance of that class to insertClassPath()
in
ClassPool
. This allows a non-standard resource to be
included in the search path.
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.get("Point");
cc.setName("Pair");
CtClass
object
for class Point
. Then it gives a new name Pair
to that CtClass
object.
If get("Point")
is later called on the ClassPool
object again, then a class file Point.class
is read again and
a new CtClass
object for class Point
is constructed
again. See the followings:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("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.
CtClass
object is converted into a class file
by writeFile()
or toBytecode()
, Javassist
rejects further modifications of that CtClass
object.
Hence, after the CtClass
object representing Point
class is converted into a class file, you cannot define Pair
class as a copy of Point
since executing setName()
on Point
is rejected.
getAndRename()
in ClassPool
. For example,
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.getAndRename("Point", "Pair");
getAndRename()
is called, the ClassPool
reads Point.class
for creating a new CtClass
object representing Pair
class. getAndRename()
can be executed after writeFile()
or toBytecode()
is called on the the ClassPool
representing Point
class.
3. ClassPool
ClassPool
object is a container of CtClass
objects. Once a CtClass
object is created, it is
recorded in a ClassPool
for ever. This is because a
compiler may need to access the CtClass
object later when
it compiles source code that refers to the class represented by that
CtClass
. If the class definition represented by that
CtClass
object is different from that of the original class
file, the compiler cannot correctly compile the source code without
the CtClass
object.
ClassPool
may cause huge memory
consumption if the number of CtClass
objects becomes large.
To avoid this problem, you can explicitly remove an unnecessary
CtClass
object from the ClassPool
. If you
call detach()
on a CtClass
object, then that
CtClass
object is removed from the ClassPool
.
For example,
CtClass cc = ... ;
cc.writeFile();
cc.detach();
CtClass
object after detach()
is called.
ClassPool
objects can be cascaded like
java.lang.ClassLoader
. For example,
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.get()
is called, the child ClassPool
first delegates to the parent ClassPool
. If the parent
ClassPool
fails to find a class file, then the child
ClassPool
attempts to find a class file.
If child.childFirstLookup
is true, the child
ClassPool
attempts to find a class file before delegating
to the parent ClassPool
. For example,
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.childFirstLookup = true; // changes the behavior of the child.