123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
- <title>Javassist Tutorial</title>
- <link rel="stylesheet" type="text/css" href="brown.css">
- </head>
- <body>
-
- <b>
- <font size="+3">
- Getting Started with Javassist
- </font>
-
- <p><font size="+2">
- Shigeru Chiba
- </font>
- </b>
-
- <p><div align="right"><a href="tutorial2.html">Next page</a></div>
-
- <ul>1. <a href="#read">Reading bytecode</a>
- <br>2. <a href="#def">Defining a new class</a>
- <br>3. <a href="#mod">Modifying a class at load time</a>
- <br>4. <a href="#load">Class loader</a>
- <br>5. <a href="tutorial2.html#intro">Introspection and customization</a>
- </ul>
-
- <p><br>
-
- <a name="read">
- <h2>1. Reading bytecode</h2>
-
- <p>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.
-
- <p>The class <code>Javassist.CtClass</code> is an abstract representation
- of a class file. A <code>CtClass</code> object is a handle for dealing
- with a class file. The following program is a very simple example:
-
- <ul><pre>
- ClassPool pool = ClassPool.getDefault();
- CtClass cc = pool.get("test.Rectangle");
- cc.setSuperclass(pool.get("test.Point"));
- pool.writeFile("test.Rectangle"); // or simply, cc.writeFile()
- </pre></ul>
-
- <p>This program first obtains a <code>ClassPool</code> object,
- which controls bytecode modification with Javassist.
- The <code>ClassPool</code> object is a container of <code>CtClass</code>
- object representing a class file.
- It reads a class file on demand for constructing a <code>CtClass</code>
- object and contains the constructed object until it is written out
- to a file or an output stream.
-
- <p>The <code>ClassPool</code> object is used to maintain one-to-one
- mapping between classes and <code>CtClass</code> objects. Javassist
- never allows two distinct <code>CtClass</code> objects to represent
- the same class. This is a crucial feature to consistent program
- transformaiton.
-
- <p>To modify the definition of a class, the users must first obtain a
- reference to the <code>CtClass</code> object representing that class.
- <code>ClassPool.get()</code> is used for this purpose.
- In the case of the program above, the <code>CtClass</code> object
- representing a class <code>test.Rectangle</code> is obtained from
- the <code>ClassPool</code> object
- and it is assigned
- to a variable <code>cc</code>. Then it is modified so that
- the superclass of <code>test.Rectangle</code> is changed into
- a class <code>test.Point</code>.
- This change is reflected on the original class file when
- <code>ClassPool.writeFile()</code> is finally called.
-
- <p>Note that <code>writeFile()</code> is a method declared in not
- <code>CtClass</code> but <code>ClassPool</code>.
- If this method is called, the <code>ClassPool</code>
- finds a <code>CtClass</code> object specified with a class name
- among the objects that the <code>ClassPool</code> contains.
- Then it translates that <code>CtClass</code> object into a class file
- and writes it on a local disk.
-
- <p>There is also <code>writeFile()</code> defined in <code>CtClass</code>.
- Thus, the last line in the program above can be rewritten into:
-
- <ul><pre>cc.writeFile();</pre></ul>
-
- <p>This method is a convenient method for invoking <code>writeFile()</code>
- in <code>ClassPool</code> with the name of the class represented by
- <code>cc</code>.
-
- <p>Javassist also provides a method for directly obtaining the
- modified bytecode. To do this, call <code>write()</code>:
-
- <ul><pre>
- byte[] b = pool.write("test.Rectangle");
- </pre></ul>
-
- <p>The contents of the class file for <code>test.Rectangle</code> are
- assigned to a variable <code>b</code> in the form of byte array.
- <code>writeFile()</code> also internally calls <code>write()</code>
- to obtain the byte array written in a class file.
-
- <p>The default <code>ClassPool</code> returned
- by a static method <code>ClassPool.getDefault()</code>
- searches the same path as the underlying JVM.
- The users can expand this class search path if needed.
- For example, the following code adds a directory
- <code>/usr/local/javalib</code>
- to the search path:
-
- <ul><pre>
- ClassPool pool = ClassPool.getDefault();
- pool.insertClassPath("/usr/local/javalib");
- </pre></ul>
-
- <p>The search path that the users can add is not only a directory but also
- a URL:
-
- <ul><pre>
- ClassPool pool = ClassPool.getDefault();
- ClassPath cp = new URLClassPath("www.foo.com", 80, "/java/", "com.foo.");
- pool.insertClassPath(cp);
- </pre></ul>
-
- <p>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 <code>com.foo</code>.
-
- <p>You can directly give a byte array to a <code>ClassPool</code> object
- and construct a <code>CtClass</code> object from that array. To do this,
- use <code>ByteArrayClassPath</code>. For example,
-
- <ul><pre>
- ClassPool cp = ClassPool.getDefault();
- byte[] b = <em>a byte array</em>;
- String name = <em>class name</em>;
- cp.insertClassPath(new ByteArrayClassPath(name, b));
- CtClass cc = cp.get(name);
- </pre></ul>
-
- <p>The obtained <code>CtClass</code> object represents
- a class defined by the class file specified by <code>b</code>.
-
- <p>Since <code>ClassPath</code> is an interface, the users can define
- a new class implementing this interface and they can add an instance
- of that class so that a class file is obtained from a non-standard resource.
-
- <p>If you want to directly construct a <code>CtClass</code> object
- from a class file but you do not know the fully-qualified name
- of the class, then
- you can use <code>makeClass()</code> in <code>CtClass</code>:
-
- <ul><pre>
- ClassPool cp = ClassPool.getDefault();
- InputStream ins = <em>an input stream for reading a class file</em>;
- CtClass cc = cp.makeClass(ins);
- </pre></ul>
-
- <p><code>makeClass()</code> returns the <code>CtClass</code> object
- constructed from the given input stream. You can use
- <code>makeClass()</code> for eagerly feeding class files to
- the <code>ClassPool</code> object. This might improve performance
- if the search path includes a large jar file. Since
- the <code>ClassPool</code> object reads a class file on demand,
- it might repeatedly search the whole jar file for every class file.
- <code>makeClass()</code> can be used for optimizing this search.
- The <code>CtClass</code> constructed by <code>makeClass()</code>
- is kept in the <code>ClassPool</code> object and the class file is never
- read again.
-
- <p><br>
-
- <a name="def">
- <h2>2. Defining a new class</h2>
-
- <p>To define a new class from scratch, <code>makeClass()</code>
- must be called on a <code>ClassPool</code>.
-
- <ul><pre>
- ClassPool pool = ClassPool.getDefault();
- CtClass cc = pool.makeClass("Point");
- </pre></ul>
-
- <p>This program defines a class <code>Point</code>
- including no members.
-
- <p>A new class can be also defined as a copy of an existing class.
- The program below does that:
-
- <ul><pre>
- ClassPool pool = ClassPool.getDefault();
- CtClass cc = pool.makeClass("Point");
- cc.setName("Pair");
- </pre></ul>
-
- <p>This program first obtains the <code>CtClass</code> object
- for class <code>Point</code>. Then it gives a new name <code>Pair</code>
- to that <code>CtClass</code> object.
- If <code>get("Point")</code> is called on the <code>ClassPool</code>
- object, then a class file <code>Point.class</code> is read again and
- a new <code>CtClass</code> object for class <code>Point</code> is constructed
- again.
-
- <ul><pre>
- ClassPool pool = ClassPool.getDefault();
- CtClass cc = pool.makeClass("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.
- </pre></ul>
-
- <p><br>
-
- <a name="mod">
- <h2>3. Modifying a class at load time</h2>
-
- <p>If what classes are modified is known in advance,
- the easiest way for modifying the classes is as follows:
-
- <ul><li>1. Get a <code>CtClass</code> object by calling
- <code>ClassPool.get()</code>,
- <li>2. Modify it, and
- <li>3. Call <code>ClassPool.write()</code> or <code>writeFile()</code>.
- </ul>
-
- <p>If whether a class is modified or not is determined at load time,
- the users can write an event listener so that it is notified
- when a class is loaded into the JVM.
- A class loader (<code>java.lang.ClassLoader</code>) working with
- Javassist must call <code>ClassPool.write()</code> for obtaining
- a class file. The users can write an event listener so that it is
- notified when the class loader calls <code>ClassPool.write()</code>.
- The event-listener class must implement the following interface:
-
- <ul><pre>public interface Translator {
- public void start(ClassPool pool)
- throws NotFoundException, CannotCompileException;
- public void onWrite(ClassPool pool, String classname)
- throws NotFoundException, CannotCompileException;
- }</pre></ul>
-
- <p>The method <code>start()</code> is called when this event listener
- is registered to a <code>ClassPool</code> object.
- The method <code>onWrite()</code> is called when <code>write()</code>
- (or similar methods) is called on the <code>ClassPool</code> object.
- The second parameter of <code>onWrite()</code> is the name of the class
- to be written out.
-
- <p>Note that <code>start()</code> or <code>onWrite()</code> do not have
- to call <code>write()</code> or <code>writeFile()</code>. For example,
-
- <ul><pre>public class MyAnotherTranslator implements Translator {
- public void start(ClassPool pool)
- throws NotFoundException, CannotCompileException {}
- public void onWrite(ClassPool pool, String classname)
- throws NotFoundException, CannotCompileException
- {
- CtClass cc = pool.get(classname);
- cc.setModifiers(Modifier.PUBLIC);
- }
- }</pre></ul>
-
- <p>All the classes written out by <code>write()</code> are made public
- just before their definitions are translated into an byte array.
-
- <p><center><img src="overview.gif" alt="overview"></center>
-
- <p>The two methods <code>start()</code> and <code>onWrite()</code>
- can modify not only a <code>CtClass</code> object specified by
- the given <code>classname</code> but also
- <em>any</em> <code>CtClass</code> objects contained
- in the given <code>ClassPool</code>.
- They can call <code>ClassPool.get()</code> for obtaining any
- <code>CtClass</code> object.
- If a modified <code>CtClass</code> object is not written out immediately,
- the modification is recorded until that object is written out.
-
- <p><center><img src="sequence.gif" alt="sequence diagram"></center>
-
- <p>To register an event listener to a <code>ClassPool</code>,
- it must be passed to a constructor of <code>ClassPool</code>.
- Only a single event listener can be registered.
- If more than one event listeners are needed, multiple
- <code>ClassPool</code>s should be connected to be a single
- stream. For example,
-
- <ul><pre>Translator t1 = new MyTranslator();
- ClassPool c1 = new ClassPool(t1);
- Translator t2 = new MyAnotherTranslator();
- ClassPool c2 = new ClassPool(c1, t2);</pre></ul>
-
- <p>This program connects two <code>ClassPool</code>s.
- If a class loader calls <code>write()</code> on <code>c2</code>,
- the specified class file is first modified by <code>t1</code> and
- then by <code>t2</code>. <code>write()</code> returns the resulting
- class file.
-
- First, <code>onWrite()</code> on <code>t1</code> is called since
- <code>c2</code> obtains a class file by calling <code>write()</code>
- on <code>c1</code>. Then <code>onWrite()</code> on <code>t2</code>
- is called. If <code>onWrite()</code> called on <code>t2</code>
- obtains a <code>CtClass</code> object from <code>c2</code>, that
- <code>CtClass</code> object represents the class file that
- <code>t1</code> has modified.
-
- <p><center><img src="two.gif" alt="two translators"></center>
-
- <p><br>
-
- <a name="load">
- <h2>4. Class loader</h2>
-
- <p>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.
-
- <p><br>
-
- <h3>4.1 Using <code>javassist.Loader</code></h3>
-
- <p>Javassist provides a class loader
- <code>javassist.Loader</code>. This class loader uses a
- <code>javassist.ClassPool</code> object for reading a class file.
-
- <p>For example, <code>javassist.Loader</code> can be used for loading
- a particular class modified with Javassist.
-
- <ul><pre>
- 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();
- :
- }
- }
- </pre></ul>
-
- <p>This program modifies a class <code>test.Rectangle</code>. The
- superclass of <code>test.Rectangle</code> is set to a
- <code>test.Point</code> class. Then this program loads the modified
- class into the JVM, and creates a new instance of the
- <code>test.Rectangle</code> class.
-
- <p>The users can use a <code>javassist.Translator</code> object
- for modifying class files.
- Suppose that an instance of a class <code>MyTranslator</code>,
- which implements
- <code>javassist.Translator</code>, performs modification of class files.
- To run an application class <code>MyApp</code> with the
- <code>MyTranslator</code> object, write a main class:
-
- <ul><pre>
- import javassist.*;
-
- public class Main2 {
- public static void main(String[] args) throws Throwable {
- Translator t = new MyTranslator();
- ClassPool pool = ClassPool.getDefault(t);
- Loader cl = new Loader(pool);
- cl.run("MyApp", args);
- }
- }
- </pre></ul>
-
- <p>To run this program, do:
-
- <ul><pre>
- % java Main <i>arg1</i> <i>arg2</i>...
- </pre></ul>
-
- <p>The class <code>MyApp</code> and the other application classes
- are translated by <code>MyTranslator</code>.
-
- <p>Note that <em>application</em> classes like <code>MyApp</code> cannot
- access the <em>loader</em> classes such as <code>Main</code>,
- <code>MyTranslator</code> and <code>ClassPool</code> because they
- are loaded by different loaders. The application classes are loaded
- by <code>javassist.Loader</code> whereas the loader classes such as
- <code>Main</code> are by the default Java class loader.
-
- <p>In Java, for security reasons, a single class file may be loaded
- into the JVM by two distinct class loaders so that two different
- classes would be created. For example,
-
- <ul><pre>class Point {
- int x, y;
- }
-
- class Box {
- Point base;
- Point getBase() { return base; }
- }
-
- class Window {
- Point size;
- Point getSize() { return size; }
- }</pre></ul>
-
- <p>Suppose that a class <code>Box</code> is loaded by a class loader
- <code>L1</code> while a class <code>Window</code> is loaded by a class
- loader <code>L2</code>. Then, the obejcts returned by
- <code>getBase()</code> and <code>getSize()</code> are not instances of
- the same class <code>Point</code>.
- <code>getBase()</code> returns an instance of the class <code>Point</code>
- loaded by <code>L1</code> whereas <code>getSize()</code> returns an
- instance of <code>Point</code> loaded by <code>L2</code>. The two versions
- of the class <code>Point</code> are distinct. They belong to different
- name spaces. For more details, see the following paper:
-
- <ul>Sheng Liang and Gilad Bracha,
- "Dynamic Class Loading in the Java Virtual Machine",
- <br><i>ACM OOPSLA'98</i>, pp.36-44, 1998.</ul>
-
- <p>To avoid this problem, the two class loaders <code>L1</code> and
- <code>L2</code> must delegate the loading operation of the class
- <code>Point</code> to another class loader, <code>L3</code>, which is
- a parent class loader of <code>L1</code> and <code>L2</code>.
- <code>delegateLoadingOf()</code> in <code>javassist.Loader</code>
- is a method for specifying what classes should be loaded by the
- parent loader.
-
- <p>If <code>L1</code> is the parent class loader of <code>L2</code>,
- that is, if <code>L1</code> loads the class of <code>L2</code>,
- then <code>L2</code> can delegate the loading operation of
- <code>Point</code> to <code>L1</code> for avoiding the problem above.
- However, this technique does not work in the case below:
-
- <ul><pre>class Point { // loaded by L1
- Window win;
- int x, y;
- }
-
- class Box { // loaded by L1
- Point base;
- Point getBase() { return base; }
- }
-
- class Window { // loaded by L2
- Point size;
- Point getSize() { size.win = this; return size; }
- }</pre></ul>
-
- <p>Since all the classes included in a class definition loaded by
- a class loader <code>L1</code> are also loaded by <code>L1</code>,
- the class of the field <code>win</code> in <code>Point</code> is
- now the class <code>Window</code> loaded by <code>L1</code>.
- Thus <code>size.win = this</code> in <code>getSize()</code> raises
- a runtime exception because of type mismatch; the type of
- <code>size.win</code> is the class <code>Point</code> loaded by
- <code>L1</code> whereas the type of <code>this</code> is the class
- <code>Point</code> loaded by <code>L2</code>.
-
- <p><br>
-
- <h3>4.2 Writing a class loader</h3>
-
- <p>A simple class loader using Javassist is as follows:
-
- <ul><pre>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 });
- }
-
- private ClassPool pool;
-
- public SimpleLoader() throws NotFoundException {
- pool = ClassPool.getDefault();
- pool.insertClassPath("./class"); // <em>MyApp.class must be there.</em>
- }
-
- /* 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);
- // <em>modify the CtClass object here</em>
- byte[] b = pool.write(name);
- 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();
- }
- }
- }</pre></ul>
-
- <p>The class <code>MyApp</code> is an application program.
- To execute this program, first put the class file under the
- <code>./class</code> directory, which must <em>not</em> be included
- in the class search path. The directory name is specified by
- <code>insertClassPath()</code> in the constructor.
- You can choose a different name instead of <code>./class</code> if you want.
- Then do as follows:
-
- <ul><code>% java SimpleLoader</code></ul>
-
- <p>The class loader loads the class <code>MyApp</code>
- (<code>./class/MyApp.class</code>) and calls
- <code>MyApp.main()</code> with the command line parameters.
- Note that <code>MyApp.class</code> must not be under the directory
- that the system class loader searches. Otherwise, the system class
- loader, which is the parent loader of <code>SimpleLoader</code>,
- loads the class <code>MyApp</code>.
-
- <p>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
- <code>MyApp</code> class in a name space separated from the name space
- that the class <code>SimpleLoader</code> belongs to because the two
- classes are loaded by different class loaders.
- Hence, the
- <code>MyApp</code> class cannot directly access the class
- <code>SimpleLoader</code>.
-
- <p><br>
-
- <h3>4.3 Modifying a system class</h3>
-
- <p>The system classes like <code>java.lang.String</code> cannot be
- loaded by a class loader other than the system class loader.
- Therefore, <code>SimpleLoader</code> or <code>javassist.Loader</code>
- shown above cannot modify the system classes at loading time.
-
- <p>If your application needs to do that, the system classes must be
- <em>statically</em> modified. For example, the following program
- adds a new field <code>hiddenValue</code> to <code>java.lang.String</code>:
-
- <ul><pre>ClassPool pool = ClassPool.getDefault();
- CtClass cc = pool.get("java.lang.String");
- cc.addField(new CtField(CtClass.intType, "hiddenValue", cc));
- pool.writeFile("java.lang.String", ".");</pre></ul>
-
- <p>This program produces a file <code>"./java/lang/String.class"</code>.
-
- <p>To run your program <code>MyApp</code>
- with this modified <code>String</code> class, do as follows:
-
- <ul><pre>
- % java -Xbootclasspath/p:. MyApp <i>arg1</i> <i>arg2</i>...
- </pre></ul>
-
- <p>Suppose that the definition of <code>MyApp</code> is as follows:
-
- <ul><pre>public class MyApp {
- public static void main(String[] args) throws Exception {
- System.out.println(String.class.getField("hiddenValue").getName());
- }
- }</pre></ul>
-
- <p>If the modified <code>String</code> class is correctly loaded,
- <code>MyApp</code> prints <code>hiddenValue</code>.
-
- <p><i>Note: Applications that use this technique for the purpose of
- overriding a system class in <code>rt.jar</code> should not be
- deployed as doing so would contravene the Java 2 Runtime Environment
- binary code license.</i>
-
- <p><br>
-
- <a href="tutorial2.html">Next page</a>
-
- <hr>
- Java(TM) is a trademark of Sun Microsystems, Inc.<br>
- Copyright (C) 2000-2003 by Shigeru Chiba, All rights reserved.
- </body>
- </html>
|