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 You can directly load the To define a new class from scratch, This program defines a class If a A frozen After If To disallow pruning a particular 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
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 Note that
If a program is running on a web application server,
creating multiple instances of
Multiple
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 If what classes must be 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 must make Javassist collaborate with a class loader.
Javassist can be used with a class loader so that bytecode can be
modified at load time. The users of Javassist can define their own
version of class loader but they can also use a class loader provided
by Javassist.
The The following program shows how to use Note that the program above depends on the fact that the
then the original If the program is running on some application server such as
JBoss and Tomcat, the context class loader used by
would work. You should give In Java, multiple class loaders can coexist and
each class loader creates its own name space.
Different class loaders can load different class files with the
same class name. The loaded two classes are regarded as different
ones. This feature enables us to run multiple application programs
on a single JVM even if these programs include different classes
with the same name.
If the same class file is loaded by two distinct class loaders,
the JVM makes two distinct classes with the same name and definition.
The two classes are regarded as different ones.
Since the two classes are not identical, an instance of one class is
not assignable to a variable of the other class. The cast operation
between the two classes fails
and throws a For example, the following code snippet throws an exception:
The Multiple class loaders form a tree structure.
Each class loader except the bootstrap loader has a
parent class loader, which has normally loaded the class of that child
class loader. Since the request to load a class can be delegated along this
hierarchy of class loaders, a class may be loaded by a class loader that
you do not request the class loading.
Therefore, the class loader that has been requested to load a class C
may be different from the loader that actually loads the class C.
For distinction, we call the former loader the initiator of C
and we call the latter loader the real loader of C.
Furthermore, if a class loader CL requested to load a class C
(the initiator of C) delegates
to the parent class loader PL, then the class loader CL is never requested
to load any classes referred to in the definition of the class C.
CL is not the initiator of those classes.
Instead, the parent class loader PL becomes their initiators
and it is requested to load them.
The classes that the definition of a class C referes to are loaded by
the real loader of C.
To understand this behavior, let's consider the following example.
Suppose that a class Next, let's consider a slightly modified example.
Now, the definition of
If L does not delegate to PL when This behavior is somewhat inconvenient but necessary.
If the following statement:
did not throw an exception,
then the programmer of
For more details of class loaders in Java, the following paper would
be helpful:
Javassist provides a class loader
For example, This program modifies a class If the users want to modify a class on demand when it is loaded,
the users can add an event listener to a The method For example, the following event listener changes all classes
to public classes just before they are loaded.
Note that To run an application class To run this program, do:
The class Note that application classes like This search order allows loading modified classes by Javassist.
However, it delegates to the parent class loader if it fails
to find modified classes for some reason. Once a class is loaded by
the parent class loader, the other classes referred to in that class will be
also loaded by the parent class loader and thus they are never modified.
Recall that all the classes referred to in a class C are loaded by the
real loader of C.
If your program fails to load a modified class, you should
make sure whether all the classes using that class have been loaded by
A simple class loader using Javassist is as follows:
The class The class loader loads the class This is the simplest way of using Javassist. However, if you write
a more complex class loader, you may need detailed knowledge of
Java's class loading mechanism. For example, the program above puts the
The system classes like If your application needs to do that, the system classes must be
statically modified. For example, the following program
adds a new field This program produces a file To run your program Suppose that the definition of If the modified Note: Applications that use this technique for the purpose of
overriding a system class in If the JVM is launched with the JPDA (Java Platform Debugger
Architecture) enabled, a class is dynamically reloadable. After the
JVM loads a class, the old version of the class definition can be
unloaded and a new one can be reloaded again. That is, the definition
of that class can be dynamically modified during runtime. However,
the new class definition must be somewhat compatible to the old one.
The JVM does not allow schema changes between the two versions.
They have the same set of methods and fields.
Javassist provides a convenient class for reloading a class at runtime.
For more information, see the API documentation of
1. Reading and writing bytecode
Javassist.CtClass
is an absatract
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
.
The ClassPool
object returned by getDfault()
searches the default system search path.
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
(details of how to modify
a CtClass
will be presented later).
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();
CtClass
as well:
Class clazz = cc.toClass();
toClass()
requests the context class loader for the current
thread to load the class file represented by the CtClass
. It
returns a java.lang.Class
object representing the loaded class.
For more details, please see this section below.
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. This is for warning the developers when they attempt
to modify a class file that has been already loaded since the JVM does
not allow reloading a class.
CtClass
can be defrost so that
modifications of the class definition will be permitted. For example,
CtClasss cc = ...;
:
cc.writeFile();
cc.defrost();
cc.setSuperclass(...); // OK since the class is not frozen.
defrost()
is called, the CtClass
object can be modified again.
ClassPool.doPruning
is set to true
,
then Javassist prunes the data structure contained
in a CtClass
object
when Javassist freezes that object.
To reduce memory
consumption, pruning discards unnecessary attributes
(attribute_info
structures) in that object.
For example, Code_attribute
structures (method bodies)
are discarded.
Thus, after a
CtClass
object is pruned, the bytecode of a method is not
accessible except method names, signatures, and annotations.
The pruned CtClass
object cannot be defrost again.
The default value of ClassPool.doPruning
is false
.
CtClass
,
stopPruning()
must be called on that object in advance:
CtClasss cc = ...;
cc.stopPruning(true);
:
cc.writeFile(); // convert to a class file.
// cc is not pruned.
CtClass
object cc
is not pruned.
Thus it can be defrost after writeFile()
is called.
Note:
While debugging, you might want to temporarily stop pruning and freezing
and write a modified class file to a disk drive.
debugWriteFile()
is a convenient method
for that purpose. It stops pruning, writes a class file, defrosts it,
and turns pruning on again (if it was initially on).
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
. For example, to load a class
org.javassist.test.Main
, its class file will be obtained from:
http://www.javassist.org:80/java/org/javassist/test/Main.class
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.
However, you can call get()
on ClassPool
to make a new instance of CtClass
representing
the same class. If you call get()
, the ClassPool
reads a class file again and newly creates a CtClass
object, which is returned by get()
.
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(true);
// if needed, append an extra search 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. It creates a ClassPool
object in
the same way shown above although it keeps a single instance of
ClassPool
and reuses it.
A ClassPool
object returned by getDefault()
does not have a special role. getDefault()
is a convenience
method.
new ClassPool(true)
is a convenient constructor,
which constructs a ClassPool
object and appends the system
search path to it. Calling that constructor is
equivalent to the following code:
ClassPool cp = new ClassPool();
cp.appendSystemPath(); // or append another path by appendClassPath()
Cascaded ClassPools
ClassPool
might be necessary;
an instance of ClassPool
should be created
for each class loader (i.e. container).
The program should create a ClassPool
object by not calling
getDefault()
but a constructor of ClassPool
.
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(true);
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.
3. Class loader
CtClass
object by calling
ClassPool.get()
,
writeFile()
or toBytecode()
on that CtClass
object to obtain a modified class file.
3.1 The
toClass
method in CtClass
CtClass
provides a convenience method
toClass()
, which requests the context class loader for
the current thread to load the class represented by the CtClass
object. To call this method, the caller must have appropriate permission;
otherwise, a SecurityException
may be thrown.
toClass()
:
public class Hello {
public void say() {
System.out.println("Hello");
}
}
public class Test {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("Hello");
CtMethod m = cc.getDeclaredMethod("say");
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
Class c = cc.toClass();
Hello h = (Hello)c.newInstance();
h.say();
}
}
Test.main()
inserts a call to println()
in the method body of say()
in Hello
. Then
it constructs an instance of the modified Hello
class
and calls say()
on that instance.
Hello
class is never loaded before toClass()
is invoked. If not, the JVM would load the original
Hello
class before toClass()
requests to
load the modified Hello
class. Hence loading the
modified Hello
class would be failed
(LinkageError
is thrown). For example, if
main()
in Test
is something like this:
public static void main(String[] args) throws Exception {
Hello orig = new Hello();
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("Hello");
:
}
Hello
class is loaded at the first
line of main
and the call to toClass()
throws an exception since the class loader cannot load two different
versions of the Hello
class at the same time.
toClass()
might be inappropriate. In this case, you
would see an unexpected ClassCastException
. To avoid
this exception, you must explicitly give an appropriate class loader
to toClass()
. For example, if bean
is your
session bean object, then the following code:
CtClass cc = ...;
Class c = cc.toClass(bean.getClass().getClassLoader());
toClass()
the class loader
that has loaded your program (in the above example, the class of
the bean
object).
toClass()
is provided for convenience. If you need
more complex functionality, you should write your own class loader.
3.2 Class loading in Java
Note: The JVM does not allow dynamically reloading a class.
Once a class loader loads a class, it cannot reload a modified
version of that class during runtime. Thus, you cannot alter
the definition of a class after the JVM loads it.
However, the JPDA (Java Platform Debugger Architecture) provides
limited ability for reloading a class.
See Section 3.6.
ClassCastException
.
MyClassLoader myLoader = new MyClassLoader();
Class clazz = myLoader.loadClass("Box");
Object obj = clazz.newInstance();
Box b = (Box)obj; // this always throws ClassCastException.
Box
class is loaded by two class loaders.
Suppose that a class loader CL loads a class including this code snippet.
Since this code snippet refers to MyClassLoader
,
Class
, Object
, and Box
,
CL also loads these classes (unless it delegates to another class loader).
Hence the type of the variable b
is the Box
class loaded by CL.
On the other hand, myLoader
also loads the Box
class. The object obj
is an instance of
the Box
class loaded by myLoader
.
Therefore, the last statement always throws a
ClassCastException
since the class of obj
is
a different verison of the Box
class from one used as the
type of the variable b
.
public class Point { // loaded by PL
private int x, y;
public int getX() { return x; }
:
}
public class Box { // the initiator is L but the real loader is PL
private Point upperLeft, size;
public int getBaseX() { return upperLeft.x; }
:
}
public class Window { // loaded by a class loader L
private Box box;
public int getBaseX() { return box.getBaseX(); }
}
Window
is loaded by a class loader L.
Both the initiator and the real loader of Window
are L.
Since the definition of Window
refers to Box
,
the JVM will request L to load Box
.
Here, suppose that L delegates this task to the parent class loader PL.
The initiator of Box
is L but the real loader is PL.
In this case, the initiator of Point
is not L but PL
since it is the same as the real loader of Box
.
Thus L is never requested to load Point
.
public class Point {
private int x, y;
public int getX() { return x; }
:
}
public class Box { // the initiator is L but the real loader is PL
private Point upperLeft, size;
public Point getSize() { return size; }
:
}
public class Window { // loaded by a class loader L
private Box box;
public boolean widthIs(int w) {
Point p = box.getSize();
return w == p.getX();
}
}
Window
also refers to
Point
. In this case, the class loader L must
also delegate to PL if it is requested to load Point
.
You must avoid having two class loaders doubly load the same
class. One of the two loaders must delegate to
the other.
Point
is loaded, widthIs()
would throw a ClassCastException.
Since the real loader of Box
is PL,
Point
referred to in Box
is also loaded by PL.
Therefore, the resulting value of getSize()
is an instance of Point
loaded by PL
whereas the type of the variable p
in widthIs()
is Point
loaded by L.
The JVM regards them as distinct types and thus it throws an exception
because of type mismatch.
Point p = box.getSize();
Window
could break the encapsulation
of Point
objects.
For example, the field x
is private in Point
loaded by PL.
However, the Window
class could
directly access the value of x
if L loads Point
with the following definition:
public class Point {
public int x, y; // not private
public int getX() { return x; }
:
}
Sheng Liang and Gilad Bracha,
"Dynamic Class Loading in the Java Virtual Machine",
ACM OOPSLA'98, pp.36-44, 1998.
3.3 Using
javassist.Loader
javassist.Loader
. This class loader uses a
javassist.ClassPool
object for reading a class file.
javassist.Loader
can be used for loading
a particular class modified with Javassist.
import javassist.*;
import test.Rectangle;
public class Main {
public static void main(String[] args) throws Throwable {
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader(pool);
CtClass ct = pool.get("test.Rectangle");
ct.setSuperclass(pool.get("test.Point"));
Class c = cl.loadClass("test.Rectangle");
Object rect = c.newInstance();
:
}
}
test.Rectangle
. The
superclass of test.Rectangle
is set to a
test.Point
class. Then this program loads the modified
class, and creates a new instance of the
test.Rectangle
class.
javassist.Loader
.
The added event listener is
notified when the class loader loads a class.
The event-listener class must implement the following interface:
public interface Translator {
public void start(ClassPool pool)
throws NotFoundException, CannotCompileException;
public void onLoad(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException;
}
start()
is called when this event listener
is added to a javassist.Loader
object by
addTranslator()
in javassist.Loader
. The
method onLoad()
is called before
javassist.Loader
loads a class. onLoad()
can modify the definition of the loaded class.
public class MyTranslator implements Translator {
void start(ClassPool pool)
throws NotFoundException, CannotCompileException {}
void onLoad(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException
{
CtClass cc = pool.get(classname);
cc.setModifiers(Modifier.PUBLIC);
}
}
onLoad()
does not have to call
toBytecode()
or writeFile()
since
javassist.Loader
calls these methods to obtain a class
file.
MyApp
with a
MyTranslator
object, write a main class as following:
import javassist.*;
public class Main2 {
public static void main(String[] args) throws Throwable {
Translator t = new MyTranslator();
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader();
cl.addTranslator(pool, t);
cl.run("MyApp", args);
}
}
% java Main2 arg1 arg2...
MyApp
and the other application classes
are translated by MyTranslator
.
MyApp
cannot
access the loader classes such as Main2
,
MyTranslator
, and ClassPool
because they
are loaded by different loaders. The application classes are loaded
by javassist.Loader
whereas the loader classes such as
Main2
are by the default Java class loader.
javassist.Loader
searches for classes in a different
order from java.lang.ClassLoader
.
ClassLoader
first delegates the loading operations to
the parent class loader and then attempts to load the classes
only if the parent class loader cannot find them.
On the other hand,
javassist.Loader
attempts
to load the classes before delegating to the parent class loader.
It delegates only if:
get()
on
a ClassPool
object, or
delegateLoadingOf()
to be loaded by the parent class loader.
javassist.Loader
.
3.4 Writing a class loader
import javassist.*;
public class SampleLoader extends ClassLoader {
/* Call MyApp.main().
*/
public static void main(String[] args) throws Throwable {
SampleLoader s = new SampleLoader();
Class c = s.loadClass("MyApp");
c.getDeclaredMethod("main", new Class[] { String[].class })
.invoke(null, new Object[] { args });
}
private ClassPool pool;
public SampleLoader() throws NotFoundException {
pool = new ClassPool();
pool.insertClassPath("./class"); // MyApp.class must be there.
}
/* Finds a specified class.
* The bytecode for that class can be modified.
*/
protected Class findClass(String name) throws ClassNotFoundException {
try {
CtClass cc = pool.get(name);
// modify the CtClass object here
byte[] b = cc.toBytecode();
return defineClass(name, b, 0, b.length);
} catch (NotFoundException e) {
throw new ClassNotFoundException();
} catch (IOException e) {
throw new ClassNotFoundException();
} catch (CannotCompileException e) {
throw new ClassNotFoundException();
}
}
}
MyApp
is an application program.
To execute this program, first put the class file under the
./class
directory, which must not be included
in the class search path. Otherwise, MyApp.class
would
be loaded by the default system class loader, which is the parent
loader of SampleLoader
.
The directory name ./class
is specified by
insertClassPath()
in the constructor.
You can choose a different name instead of ./class
if you want.
Then do as follows:
% java SampleLoader
MyApp
(./class/MyApp.class
) and calls
MyApp.main()
with the command line parameters.
MyApp
class in a name space separated from the name space
that the class SampleLoader
belongs to because the two
classes are loaded by different class loaders.
Hence, the
MyApp
class cannot directly access the class
SampleLoader
.
3.5 Modifying a system class
java.lang.String
cannot be
loaded by a class loader other than the system class loader.
Therefore, SampleLoader
or javassist.Loader
shown above cannot modify the system classes at loading time.
hiddenValue
to java.lang.String
:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("java.lang.String");
cc.addField(new CtField(CtClass.intType, "hiddenValue", cc));
pool.writeFile("java.lang.String", ".");
"./java/lang/String.class"
.
MyApp
with this modified String
class, do as follows:
% java -Xbootclasspath/p:. MyApp arg1 arg2...
MyApp
is as follows:
public class MyApp {
public static void main(String[] args) throws Exception {
System.out.println(String.class.getField("hiddenValue").getName());
}
}
String
class is correctly loaded,
MyApp
prints hiddenValue
.
rt.jar
should not be
deployed as doing so would contravene the Java 2 Runtime Environment
binary code license.
3.6 Reloading a class at runtime
javassist.tools.HotSwapper
.
Java(TM) is a trademark of Sun Microsystems, Inc.
Copyright (C) 2000-2007 by Shigeru Chiba, All rights reserved.