1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
|
<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>
|