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.