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 To define a new class from scratch, This program defines a class If a When Javassist freezes a To disallow pruning a If a 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
A
For example, suppose that a new method
This specification of You must not call any method on that
Another idea is to occasionally replace a This creates a
If
If A new class can be defined as a copy of an existing class.
The program below does that:
This program first obtains the Note that Therefore, if The To create another copy of the default instance of
If you have two Once a To avoid this restriction, you should call If
1. Reading and writing 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();
Defining a new class
makeClass()
must be called on a ClassPool
.
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
Point
including no members.
Member methods of Point
can be created with
factory methods declared in CtNewMethod
and
appended to Point
with addMethod()
in CtClass
.
makeClass()
cannot create a new interface;
makeInterface()
in ClassPool
can do.
Member methods in an interface can be created with
abstractMethod()
in CtNewMethod
.
Note that an interface method is an abstract method.
Frozen classes
CtClass
object is converted into a class file by
writeFile()
, toClass()
, or
toBytecode()
, Javassist freezes that CtClass
object. Further modifications of that CtClass
object are
not permitted.
CtClass
object, it also
prunes the data structure contained in that object. To reduce memory
consumption, Javassist discards some part of data structure, for
example, the data of method bodies. Thus, after a
CtClass
object is pruned, the bytecode of a method is not
accessible although method names and signatures are accessible.
CtClass
, stopPruning()
must be called in advance:
CtClasss cc = ...;
cc.stopPruning(true);
:
cc.writeFile(); // convert to a class file.
// cc is not pruned.
CtClass
is not pruned, it can be defrost so that
modifications of the class definition can be permitted. For example,
CtClasss cc = ...;
cc.stopPruning(true);
:
cc.writeFile();
cc.defrost();
cc.setSuperclass(...); // OK since the class is not frozen.
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. 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
.
getter()
is added
to a CtClass
object representing Point
class. Later, the program attempts to compile source code including a
method call to getter()
in Point
and use the
compiled code as the body of a method, which will be added to another
class Line
. If the CtClass
object representing
Point
is lost, the compiler cannot compile the method call
to getter()
. Note that the original class definition does
not include getter()
. Therefore, to correctly compile
such a method call, the ClassPool
must contain all the instances of CtClass
all the time of
program execution.
Avoid out of memory
ClassPool
may cause huge memory
consumption if the number of CtClass
objects becomes
amazingly large (this rarely happens since Javassist tries to reduce
memory consumption in various ways). 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
with
a new one and discard the old one. If an old ClassPool
is garbage collected, the CtClass
objects included in
that ClassPool
are also garbage collected.
To create a new instance of ClassPool
, execute the following
code snippet:
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.
Note that ClassPool.getDefault()
is a singleton factory method
provided for convenience. Therefore, the default ClassPool
is never garbage-collected.
Cascaded ClassPools.
ClassPool
objects can be cascaded like
java.lang.ClassLoader
. For example,
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.insertClassPath("./classes");
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
under the ./classes
directory.
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.appendSystemPath(); // the same class path as the default one.
child.childFirstLookup = true; // changes the behavior of the child.
Changing a class name for defining a new class
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.
ClassPool
, which is returned by
ClassPool.getDefault()
, execute the following code
snippet (this code was already shown above):
ClassPool cp = new ClassPool();
cp.appendSystemPath(); // or append another path by appendClassPath()
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.
Renaming a frozen class for defining a new 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.