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 From the implementation viewpoint, The The default
This statement registers the class path that was used for loading
the class of the object that
You can register a directory name as the class search path.
For example, the following code adds a directory
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 Note that Therefore, if The This creates a If you have two 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();
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 records the
constructed object for responding later accesses.
To modify the definition of a class, the users must first obtain
from a ClassPool
object
a reference to a CtClass
object representing that class.
get()
in ClassPool
is used for this purpose.
In the case of the program shown above, the
CtClass
object representing a class
test.Rectangle
is obtained from the
ClassPool
object and it is assigned to a variable
cc
.
ClassPool
is a hash
table of CtClass
objects, which uses the class names as
keys. get()
in ClassPool
searches this hash
table to find a CtClass
object associated with the
specified key. If such a CtClass
object is not found,
get()
reads a class file to construct a new
CtClass
object, which is recorded in the hash table and
then returned as the resulting value of get()
.
CtClass
object obtained from a ClassPool
object can be modified.
In the example above, 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();
Class search path
ClassPool
returned
by a static method ClassPool.getDefault()
searches the same path that the underlying JVM (Java virtual machine) has.
If a program is running on a web application server such as JBoss and Tomcat,
the ClassPool
object may not be able to find user classes
since such a web application server uses multiple class loaders as well as
the system class loader. In that case, an additional class path must be
registered to the ClassPool
. Suppose that pool
refers to a ClassPool
object:
pool.insertClassPath(new ClassClassPath(this.getClass()));
this
refers to.
You can use any Class
object as an argument instead of
this.getClass()
. The class path used for loading the
class represented by that Class
object is registered.
/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 calls setName()
to
give a new name Pair
to that CtClass
object.
After this call, all occurrences of the class name in the class
definition represented by that CtClass
object are changed
from Point
to Pair
. The other part of the
class definition does not change.
setName()
in CtClass
changes a
record in the ClassPool
object. From the implementation
viewpoint, a ClassPool
object is a hash table of
CtClass
objects. setName()
changes
the key associated to the CtClass
object in the hash
table. The key is changed from the original class name to the new
class name.
get("Point")
is later called on the
ClassPool
object again, then it never returns the
CtClass
object that the variable cc
refers to.
The ClassPool
object reads
a class file
Point.class
again and it constructs a new CtClass
object for class Point
.
This is because the CtClass
object associated with the name
Point
does not exist any more.
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.
cc1
and cc2
refer to the same instance of
CtClass
that cc
does whereas
cc3
does not. Note that, after
cc.setName("Pair")
is executed, the CtClass
object that cc
and cc1
refer to represents
the Pair
class.
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
transformation. 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 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.
The following code snippet is wrong:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.writeFile();
cc.setName("Pair"); // wrong since writeFile() has been called.
getAndRename()
in ClassPool
. For example,
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.writeFile();
CtClass cc2 = pool.getAndRename("Point", "Pair");
getAndRename()
is called, the ClassPool
first reads Point.class
for creating a new CtClass
object representing Point
class. However, it renames that
CtClass
object from Point
to Pair
before
it records that CtClass
object in a hash table.
Thus getAndRename()
can be executed after writeFile()
or toBytecode()
is called on the the CtClass
object 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.