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
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
|
<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="#pool">ClassPool</a>
<br>4. <a href="#load">Class loader</a>
<br>5. <a href="tutorial2.html#intro">Introspection and customization</a>
<br>6. <a href="tutorial3.html#intro">Bytecode level API</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> (compile-time
class) 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"));
cc.writeFile("test.Rectangle");
</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 unless two independent <code>ClassPool</code> are created.
This is a significant feature for consistent program
transformation. To create multiple
instances of <code>ClassPool</code>, write the following code:
<ul><pre>
ClassPool cp = new ClassPool();
cp.appendSystemPath(); // or append another path by appendClassPath()
</pre></ul>
<p>This creates a <code>ClassPool</code> object that behaves as the
default <code>ClassPool</code> returned by
<code>ClassPool.getDefault()</code> does.
<code>ClassPool.getDefault()</code> is a singleton factory method
provided for convenience.
<p>If you have two <code>ClassPool</code> objects, then you can
obtain, from each <code>ClassPool</code>, a distinct
<code>CtClass</code> object representing the same class file. You can
differently modify these <code>CtClass</code> objects to generate
different versions of the class.
<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>get()</code> in <code>ClassPool</code> is used for this purpose.
In the case of the program shown at the beginning, 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>writeFile()</code> in <code>CtClass()</code> is
finally called.
<p><code>writeFile()</code> translates the <code>CtClass</code> 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 <code>toBytecode()</code>:
<ul><pre>
byte[] b = cc.toBytecode();
</pre></ul>
<p>The default <code>ClassPool</code> returned
by a static method <code>ClassPool.getDefault()</code>
searches the same path that the underlying JVM (Java virtual machine) has.
<em>If a program is running on a web application server such as JBoss and Tomcat,
the <code>ClassPool</code> object may not be able to find user classes</em>
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 <code>ClassPool</code>. Suppose that <code>pool</code>
refers to a <code>ClassPool</code> object:
<ul><pre>
pool.insertClassPath(new ClassClassPath(this.getClass()));
</pre></ul>
<p>
This statement registers the class path that was used for loading
the class of the object that <code>this</code> refers to.
You can use any <code>Class</code> object as an argument instead of
<code>this.getClass()</code>. The class path used for loading the
class represented by that <code>Class</code> object is registered.
<p>
You can register a directory name as the class search path.
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.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);
</pre></ul>
<p>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 <code>org.javassist</code>.
<p>Furthermore, 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>.
The <code>ClassPool</code> reads a class file from the given
<code>ByteArrayClassPath</code> if <code>get()</code> is called
and the class name given to <code>get()</code> is equal to
one specified by <code>name</code>.
<p>If you do not know the fully-qualified name of the class, then you
can use <code>makeClass()</code> in <code>ClassPool</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
a <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>The users can extend the class search path. They can define a new
class implementing <code>ClassPath</code> interface and give an
instance of that class to <code>insertClassPath()</code> in
<code>ClassPool</code>. This allows a non-standard resource to be
included in the search path.
<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.get("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 later called on the <code>ClassPool</code>
object again, 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. See the followings:
<ul><pre>
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.
</pre></ul>
<p>Once a <code>CtClass</code> object is converted into a class file
by <code>writeFile()</code> or <code>toBytecode()</code>, Javassist
rejects further modifications of that <code>CtClass</code> object.
Hence, after the <code>CtClass</code> object representing <code>Point</code>
class is converted into a class file, you cannot define <code>Pair</code>
class as a copy of <code>Point</code> since executing <code>setName()</code>
on <code>Point</code> is rejected.
<p>To avoid this restriction, you should call <code>getAndRename()</code>
in <code>ClassPool</code>. For example,
<ul><pre>
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.getAndRename("Point", "Pair");
</pre></ul>
<p>If <code>getAndRename()</code> is called, the <code>ClassPool</code>
reads <code>Point.class</code> for creating a new <code>CtClass</code>
object representing <code>Pair</code> class. <code>getAndRename()</code>
can be executed after <code>writeFile()</code> or <code>toBytecode()</code>
is called on the the <code>ClassPool</code> representing <code>Point</code>
class.
<p><br>
<a name="pool">
<h2>3. ClassPool</h2>
<p>
A <code>ClassPool</code> object is a container of <code>CtClass</code>
objects. Once a <code>CtClass</code> object is created, it is
recorded in a <code>ClassPool</code> for ever. This is because a
compiler may need to access the <code>CtClass</code> object later when
it compiles source code that refers to the class represented by that
<code>CtClass</code>. If the class definition represented by that
<code>CtClass</code> object is different from that of the original class
file, the compiler cannot correctly compile the source code without
the <code>CtClass</code> object.
<p>
This specification of <code>ClassPool</code> may cause huge memory
consumption if the number of <code>CtClass</code> objects becomes large.
To avoid this problem, you can explicitly remove an unnecessary
<code>CtClass</code> object from the <code>ClassPool</code>. If you
call <code>detach()</code> on a <code>CtClass</code> object, then that
<code>CtClass</code> object is removed from the <code>ClassPool</code>.
For example,
<ul><pre>
CtClass cc = ... ;
cc.writeFile();
cc.detach();
</pre></ul>
<p>You must not call any method on that
<code>CtClass</code> object after <code>detach()</code> is called.
<p><code>ClassPool</code> objects can be cascaded like
<code>java.lang.ClassLoader</code>. For example,
<ul><pre>
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
</pre></ul>
<p>If <code>child.get()</code> is called, the child <code>ClassPool</code>
first delegates to the parent <code>ClassPool</code>. If the parent
<code>ClassPool</code> fails to find a class file, then the child
<code>ClassPool</code> attempts to find a class file.
If <code>child.childFirstLookup</code> is true, the child
<code>ClassPool</code> attempts to find a class file before delegating
to the parent <code>ClassPool</code>. For example,
<ul><pre>
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.childFirstLookup = true; // changes the behavior of the child.
</pre></ul>
<p><br>
<a name="load">
<h2>4. Class loader</h2>
<p>If what classes must be 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>writeFile()</code> or <code>toBytecode()</code>
on that <code>CtClass</code> object.
</ul>
<p>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.
<p>Using a class loader is not easy. In particular, if you are a
beginner, you should separate your program into an application program
and an instrumentation program. Then you should load only the former
program by a user-defined class loader. The latter one, as well as
the program of the user-defined class loader, should be loaded by the
system class loader.
<p><br>
<h3>4.1 Class loading in Java</h3>
<p>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.
<p>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 <code>ClassCastException</code>.
<p>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 <em>the initiator of C</em>
and we call the latter loader <em>the real loader of C</em>.
<p>
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.
<em>The classes that the definition of a class C referes to are loaded by
the real loader of C.</em>
<p>To understand this behavior, let's consider the following example.
<ul><pre>
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(); }
}</pre></ul>
<p>Suppose that a class <code>Window</code> is loaded by a class loader L.
Both the initiator and the real loader of <code>Window</code> are L.
Since the definition of <code>Window</code> refers to <code>Box</code>,
the JVM will request L to load <code>Box</code>.
Here, suppose that L delegates this task to the parent class loader PL.
The initiator of <code>Box</code> is L but the real loader is PL.
In this case, the initiator of <code>Point</code> is not L but PL
since it is the same as the real loader of <code>Box</code>.
Thus L is never requested to load <code>Point</code>.
<p>Next, let's consider a slightly modified example.
<ul><pre>
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();
}
}</pre></ul>
<p>Now, the definition of <code>Window</code> also refers to
<code>Point</code>. In this case, the class loader L must
also delegate to PL if it is requested to load <code>Point</code>.
<em>You must avoid having two class loaders doubly load the same
class.</em> One of the two loaders must delegate to
the other.
<p>
If L does not delegate to PL when <code>Point</code>
is loaded, <code>widthIs()</code> would throw a ClassCastException.
Since the real loader of <code>Box</code> is PL,
<code>Point</code> referred to in <code>Box</code> is also loaded by PL.
Therefore, the resulting value of <code>getSize()</code>
is an instance of <code>Point</code> loaded by PL
whereas the type of the variable <code>p</code> in <code>widthIs()</code>
is <code>Point</code> loaded by L.
The JVM regards them as distinct types and thus it throws an exception
because of type mismatch.
<p>This behavior is somewhat inconvenient but necessary.
If the following statement:
<ul><pre>
Point p = box.getSize();
</pre></ul>
<p>did not throw an exception,
then the programmer of <code>Window</code> could break the encapsulation
of <code>Point</code> objects.
For example, the field <code>x</code>
is private in <code>Point</code> loaded by PL.
However, the <code>Window</code> class could
directly access the value of <code>x</code>
if L loads <code>Point</code> with the following definition:
<ul><pre>
public class Point {
public int x, y; // not private
public int getX() { return x; }
:
}
</pre></ul>
<p>
For more details of class loaders in Java, the following paper would
be helpful:
<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><br>
<h3>4.2 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, and creates a new instance of the
<code>test.Rectangle</code> class.
<p>If the users want to modify a class on demand when it is loaded,
the users can add an event listener to a <code>javassist.Loader</code>.
The added event listener is
notified when the class loader loads a class.
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 added to a <code>javassist.Loader</code> object by
<code>addTranslator()</code> in <code>javassist.Loader</code>. The
method <code>onWrite()</code> is called before
<code>javassist.Loader</code> loads a class. <code>onWrite()</code>
can modify the definition of the loaded class.
<p>For example, the following event listener changes all classes
to public classes just before they are loaded.
<ul><pre>public class MyTranslator implements Translator {
void start(ClassPool pool)
throws NotFoundException, CannotCompileException {}
void onWrite(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException
{
CtClass cc = pool.get(classname);
cc.setModifiers(Modifier.PUBLIC);
}
}</pre></ul>
<p>Note that <code>onWrite()</code> does not have to call
<code>toBytecode()</code> or <code>writeFile()</code> since
<code>javassist.Loader</code> calls these methods to obtain a class
file.
<p>To run an application class <code>MyApp</code> with a
<code>MyTranslator</code> object, write a main class as following:
<ul><pre>
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);
}
}
</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>Main2</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><code>javassist.Loader</code> searches for classes in a different
order from <code>java.lang.ClassLoader</code>.
<code>ClassLoader</code> 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,
<code>javassist.Loader</code> attempts
to load the classes before delegating to the parent class loader.
It delegates only if:
<ul><li>the classes are not found by calling <code>get()</code> on
a <code>ClassPool</code> object, or
<p><li>the classes have been specified by using
<code>delegateLoadingOf()</code>
to be loaded by the parent class loader.
</ul>
<p>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.
<em>If your program fails to load a modified class,</em> you should
make sure whether all the classes using that class have been loaded by
<code>javassist.Loader</code>.
<p><br>
<h3>4.3 Writing a class loader</h3>
<p>A simple class loader using Javassist is as follows:
<ul><pre>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"); // <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 = 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();
}
}
}</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. Otherwise, <code>MyApp.class</code> would
be loaded by the default system class loader, which is the parent
loader of <code>SampleLoader</code>.
The directory name <code>./class</code> 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 SampleLoader</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.
<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>SampleLoader</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>SampleLoader</code>.
<p><br>
<h3>4.4 The <code>toClass</code> method in <code>CtClass</code></h3>
<p>The <code>CtClass</code> provides a convenience method
<code>toClass</code>, which loads the class by an internal class
loader of Javassist. This method first obtains the class file
representing the modified class and loads it by an instance of
<code>javassist.ClassPool.SimpleLoader</code>.
The following code is the definition of this class loader:
<ul><pre>
public class SimpleLoader extends ClassLoader {
public Class loadClass(String classname, byte[] classfile)
throws ClassFormatError
{
Class c = defineClass(classname, classfile, 0, classfile.length);
resolveClass(c);
return c;
}
};
</pre></ul>
<p><code>loadClass()</code> loads the class specified by
<code>classfile</code>.
Thus, <code>toClass()</code> is equivalent to the following code:
<ul><pre>
CtClass cc = ... ;
ClassPool.SimpleLoader cl = new ClassPool.SimpleLoader();
Class c = cl.loadClass(cc.getName(), cc.toBytecode());
</pre></ul>
<p>Note that this class loader might be too simple for realistic use.
It delegates to the parent class loader unless the class is explicitly
loaded by <code>loadClass()</code> (or <code>toClass()</code> in
<code>CtClass</code>). If you encounter an unexpected
<code>ClassCastException</code>, you should check the class loader of
the object. Call <code>getClass().getClassLoader()</code> on the
object and make sure that the destination class and the source class
of the cast operation have been loaded by the same class loader.
<p><br>
<h3>4.5 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>SampleLoader</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-2004 by Shigeru Chiba, All rights reserved.
</body>
</html>
|