How to use Javassist

Copyright (C) 2000 Shigeru Chiba, All rights reserved.

1. Reading bytecode

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 Javassist.CtClass is an abstract representation of a class file. A CtClass object is a handle for dealing with a class file. The following program is a very simple example:

This program first obtains a CtClass object representing a class test.Rectangle. The class file defining test.Rectangle is read through the current class loader and the bytecode is stored in the CtClass object. Then this program modifies the bytecode so that the superclass of test.Rectangle changes into a class test.Point. Finally, this program writes the modified bytecode back into the class file.

Javassist also provides a method for directly obtaining the modified bytecode. To do this, call toBytecode():

To expand the class search path used by the constructor of CtClass, the static method CtClass.getClassPath() must be called. The returned object represents a class search path. An additional search path can be added to this object:

This program adds /usr/local/javalib to the search path. The following creation of CtClass objects uses this added path.

The search path that can be added is not only a directory but also a URL:

This program adds "" to the class search path. This URL is used only for searching classes belonging to a package

2. Defining a new class

To define a new class from scratch, the class CtNewClass must be instantiated.

The class CtNewClass is a subclass of CtClass. This program defines a class Empty including no members. The bytecode of the Empty class is stored in a class file Empty.class.

A new class can be also defined as a copy of an existing class. To do this, the class CtClass must be directly instantiated and given a new name:

This program first reads the class file defining a class Person. Then it modifies the bytecode so that the class name is changed into Human.

The object created by

is not identical to the object returned by

The new operator creates a new copy of the class definition, which is intended to be renamed for defining a new class. Therefore, changes on that created object does not affect the rest of the program. Suppose that a class Cat inherits from a class Animal and Animal inherits from a class Creature. The following program:

does not print Organism but Creature.

setName() can be called only on the CtClass objects directly instantiated by the new operator. It cannot be called on the CtClass objects returned by forName().

3. 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.

3.1 Writing a Class Loader

A simple class loader using Javassist is as follows:

The class MyApp is an application program. To execute this program, do as follows:

The class loader loads the class MyApp and calls MyApp.main() with the command line parameters.

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 MyApp class in a name space separated from the name space that the class SimpleLoader belongs to. Hence, the MyApp class cannot directly access the class SimpleLoader.

3.2 Using javassist.Loader

Javassist provides a class loader javassist.Loader. This class loader uses a javassist.ClassPath object for reading bytecode. To intercept class loading and modify bytecode, a class implementing the interface javassist.UserClassPath must be defined and its instance must be registered to the ClassPath object.

Suppose that an instance of MyLoader implementing UserClassPath performs modification of class files. To run an application class MyApp with the MyLoader object, do as follows:

javassist.Run first instantiates MyLoader. The class MyLoader must provide a constructor receiving no parameters. Then it creates a Loader object and registers the MyLoader object to it. Finally, it makes the Loader object load and run the application class MyApp. The bytecode of the application program is modified by the MyLoader object.

Instead of using javassist.Run, you can define a start-up program like this:

This program is executed as follows:

This program execution is equivalent to the execution using javassist.Run.

Although MyMain registers only one UserClassPath object to Loader, you can also extend MyMain so that it registers multiple UserClassPath objects. For example,

Registering multiple UserClassPath objects is not supported by javassist.Run.

3.3 Writing a class implementing the interface UserClassPath

UserClassPath is an interface providing two methods:

openClassfile() is a method for reading a class file from a non-standard resource. If you do not need to use a non-standard resource, this method should be:

filterClassfile() modifies bytecode when it is loaded into the JVM. Its definition is typically something like this:

This method receives an input stream for reading a class file defining a class specified by classname. If no modification is needed, this method returns the received input stream without any changes.

Note that a CtClass object is directly instantiated with the input stream in; CtClass.forName() is not called. This is because CtClass.forName() uses ClassPath and thus it recursively calls filterClassfile(). If an input stream is passed to a constructor of CtClass, the constructor does not use ClassPath for obtaining a class file.

3.4 Modifying a system class

The system classes like java.lang.String cannot be loaded by a class loader other than the system class loader. Therefore, SimpleLoader or javassist.Loader cannot modify the system classes at loading time.

If your application needs to do that, the system classes must be statically modified. For example, the following program adds a new field hiddenValue to java.lang.String:

This program produces a file "./java/lang/String.class".

To run your program MyApp with this modified String class, do as follows:

Suppose that the definition of MyApp is as follows:

If the modified String class is correctly loaded, MyApp prints hiddenValue.

Note: Applications that use this technique for the purpose of overriding a system class in rt.jar should not be deployed as doing so would contravene the Java 2 Runtime Environment binary code license.

4. Introspection and Customization

CtClass provides methods for introspection. The introspective ability of Javassist is compatible with that of the Java reflection API. CtClass provides getName(), getSuperclass(), getMethods(), and so on.

CtClass also provides methods for modifying a class definition. It allows to add a new field, constructor, and method. Instrumenting a method body is also possible. For more details, see javassist.CodeConverter.

Java(TM) is a trademark of Sun Microsystems, Inc.