怎么去使用javaJavassist

来源:互联网 发布:网络与新媒体专业就业 编辑:程序博客网 时间:2024/04/19 08:35

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:

    CtClass cc = CtClass.forName("test.Rectangle");cc.setSuperclass(CtClass.forName("test.Point"));cc.compile();

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():

    byte[] b = cc.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:

    ClassPath cp = CtClass.getClassPath();cp.addPath("/usr/local/javalib");

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:

    ClassPath cp = CtClass.getClassPath();cp.addPath("www.foo.com", 80, "/java/", "com.foo.");

This program adds "http://www.foo.com:80/java/" to the class search path. This URL is used only for searching classes belonging to a package com.foo.


2. Defining a new class

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

    CtClass cc = new CtNewClass();cc.setName("Empty");cc.compile();

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 fileEmpty.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:

    CtClass cc = new CtClass("Person");cc.setName("Human");cc.compile();

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

    new CtClass("Person")

is not identical to the object returned by

    CtClass.forName("Person")

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:

    CtClass a = new CtClass("Animal");a.setSuperclass(CtClass.forName("Organism"));CtClass a2 = CtClass.forName("Cat").getSuperclass();System.out.println(a2.getSuperclass().getName());

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:

    import javassist.*;public class SimpleLoader extends ClassLoader {        /* Call MyApp.main().     */    public static void main(String[] args) throws Throwable {        SimpleLoader s = new SimpleLoader();        Class c = s.loadClass("MyApp");        c.getDeclaredMethod("main", new Class[] { String[].class })         .invoke(null, new Object[] { args });    }        /* Finds a specified class.  The bytecode for that class can be modified.     */    protected Class findClass(String name) throws ClassNotFoundException {        try {            CtClass cc = CtClass.forName(name);            // modify cc here            byte[] b = cc.toBytecode();            return defineClass(name, b, 0, b.length);        } catch (CannotCompileException e) {            throw new ClassNotFoundException();        }    }}

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

    % java SimpleLoader

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:

    % java javassist.Run MyLoader MyApp arg1 arg2...

javassist.Run first instantiates MyLoader. The class MyLoader must provide a constructor receiving no parameters. Then it creates a Loader object and registers the MyLoaderobject 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:

    public class MyMain {  public static void main(String[] args) throws Throwable {     Loader cl = (Loader)Main.class.getClassLoader();     UserClassPath myLoader = new MyLoader();     cl.getClassPath().addPath(myLoader);     cl.run("MyApp", args);  }}

This program is executed as follows:

    % java javassist.Loader MyMain arg1 arg2...

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,

    public class MyMain {  public static void main(String[] args) throws Throwable {     Loader cl = (Loader)Main.class.getClassLoader();     ClassPath cp = cl.getClassPath();     UserClassPath myLoader = new MyLoader();     cp.addPath(myLoader);     UserClassPath myLoader2 = new MyLoader2();     cp.addPath(myLoader2);     cl.run("MyApp", args);  }}

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:

    InputStream openClassfile(String classname)InputStream filterClassfile(String classname, InputStream in)

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:

    InputStream openClassfile(String classname) throws NotFoundException {    return null;}

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

    InputStream filterClassfile(String classname, InputStream in)    throws IOException, CannotCompileException{    /* Either of the following two lines is wrong:     * CtClass cc = CtClass.forName(classname);     * CtClass cc = new CtClass(classname);     */    CtClass cc = new CtClass(in);    /* modify cc here */    return cc.getInputStream();}

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:

    CtClass cc = CtClass.forName("java.lang.String");cc.addField(new CtNewField(CtClass.intType, "hiddenValue"));cc.compile();

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

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

    % java -Xbootclasspath/p:. MyApp arg1 arg2...

Suppose that the definition of MyApp is as follows:

    public class MyApp {    public static void main(String[] args) throws Exception {        System.out.println(String.class.getField("hiddenValue").getName());    }}

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.
原创粉丝点击