|
YOUR FEEDBACK
Did you read today's front page stories & breaking news?
SYS-CON.TV |
TOP THREE LINKS YOU MUST CLICK ON Product Reviews Javassist: Java Bytecode Engineering Made Simple
For source-level abstraction
By: Shigeru Chiba
Jan. 8, 2004 12:00 AM
Javassist is a powerful new library in the field of bytecode engineering. It allows developers to add a new method to a compiled class, modify a method body, and so forth. Unlike other similar libraries, Javassist enables this without knowledge of Java bytecode or the structure of a class file. Bytecode engineering is used to manipulate and modify compiled Java classes and to programmatically create new classes. This engineering can happen either at runtime or at compile time. Some technologies use bytecode manipulation to optimize or enhance existing Java classes. Other technologies use it to make themselves easier to use or to avoid cumbersome code generators. For instance, the JDO 1.0 (Java Data Objects) specification requires bytecode enhancement of precompiled Java to add database persistence code to simple Java classes. In Aspect-Oriented Programming, some of the new frameworks use bytecode engineering to enhance Java classes with cross-cutting functionality. EJB containers, like JBoss, dramatically speed up the development cycle by dynamically creating Java classes at runtime to avoid the costly code generation/precompilation step required by EJBs. Even the JDK does bytecode manipulation within its java.lang.reflect.Proxy class. Bytecode manipulation has been a complex and often unacceptable task for framework developers because of the huge implementation costs. Learning bytecode is very similar to learning an assembly language. The learning curve can be very steep for developers. Not only that but code written to engineer bytecode is a challenge to maintain, as it's difficult to read and follow. Javassist is a library for simplifying bytecode manipulation. It allows developers to fully exploit bytecode manipulation with little or no knowledge of bytecode, giving them a degree of fine-grained control. API Parallel to the Reflection API 1. ClassPool pool = ClassPool.getDefault(); The ClassPool object is a factory of CtClass objects. It searches for a class file in the specified class path and creates a CtClass object as a singleton for each class. The get method in ClassPool returns the CtClass object representing the class with the given name. The Javassist API does not provide methods for creating a new instance (the newInstance method), invoking a method (the invoke method), or accessing a field value (the get and set methods) since CtClass objects represent classes that have not been loaded. On the other hand, the API provides methods for changing class definitions. For example, setSuperclass in CtClass changes the super class of the class. The reflection API does not support such changes. For example: 4. pt.setSuperclass(pool.get("Figure")); This modifies the definition of the Point class so that it extends the Figure class. For consistency, this example assumes that the Figure class is compatible with the original super class. Adding a new method to the Point class is also possible: 5. CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", pt); The CtMethod object that represents the added method is created from the given source text. Developers do not have to write a sequence of virtual machine instructions by hand. Instead, Javassist compiles the given source text in Java with a custom compiler included in Javassist. Finally, to reflect all the changes above, the writeFile method is called: 7. pt.writeFile(); The writeFile method in CtClass writes the modified definition of the class to a class file. Javassist can work with a ClassLoader, as we'll discuss later. Javassist is not the first class library for writing a bytecode translator. For example, Jakarta BCEL is a popular library for bytecode engineering. However, you can't use Jakarta BCEL with source-level vocabulary. If you add a new method to a compiled class, you must specify a method body by a sequence of bytecode instructions. On the other hand, Javassist allows you to specify it by source text as shown above. Instrumenting a Method Body The design of the Javassist API for instrumenting a method body is based on the idea of Aspect-Oriented Programming (AOP). Javassist allows the identification of some expressions, such as method calls and field accesses, in a method body and then substitutes a code fragment for the expression. For example, Listing 1 first obtains a CtMethod object that represents a draw method in the Screen class; then it searches the body of the draw method for method calls to move in Point class. All the occurrences of the method calls to move are replaced with the following block statement: { System.out.println("move"); $_ = $proceed($$); } so that a message is printed out before a move is called. The statement, written in special syntax: $_ = $proceed($$); executes the original method call with the original parameters. The instrument method in CtMethod searches the method body and, when it finds a method-call expression, calls the edit method on the given ExprEditor object. The parameter to the edit method is a Method-Call object representing the found expression. Since this object provides methods for getting static properties of the expression, the edit method first examines the name of the called method and, if the method is moved in Point, it replaces the method-call expression with the block statement printing a message. The block statement is compiled into bytecode by Javassist before the replacement. Special Variables { System.out.println("move"); $_ = $proceed($1, 0); } then the second parameter to the move method would be zero. Another special variable $args is an Object array containing all the parameters to the originally called method. If the type of a parameter is a primitive type, then the wrapper object containing that parameter value is stored in the array. For example, if the parameter is an int value, the java.lang.Integer object containing that int value is stored in the array. $args is useful when the parameters are used to call a method through the invoke method in java.lang. reflect.Method. Javassist also allows the insertion of a code fragment at the beginning or end of a method body. For example, the insertBefore method inserts the given block statement at the beginning of the method body. 1. ClassPool pool = ClassPool.getDefault(); This example inserts a code fragment at the beginning of the draw method so that the values of the two parameters to draw are printed out. $1 and $2 are special variables representing the first and second parameters to draw. Special variables starting with $ are also available in the source text passed to the setBody method in CtMethod. The following example shows how to add a wrapper method: 1. CtClass cc = sloader.get("Point"); This program first makes a copy of the move method in the Point class, then it renames the original move method to move_ orig. It then changes the body of the copy of the move method so the copy will be a wrapper method named move, which prints a message and invokes the move_orig method. The second parameter to setBody specifies the target object of the $proceed call, and the third parameter specifies the name of the method called by $proceed. Javassist also has a multitude of other helper objects and functions that allow you to do things like replace field access, redirect method calls, and simplify how you insert code before or after a method. Check out www.javassist.org for more information and tutorials. Performance Overhead ClassLoader The simplest way to modify and load a class at runtime is to call the toClass method in CtClass instead of the writeFile method. In the previous examples, the modified class files have been written out on a disk. However, the toClass method loads the class by a custom ClassLoader of Javassist and it returns the java.lang.Class object that represents the loaded class. Listing 2 dynamically defines the Hello class from scratch and adds the say() method, then it loads the Hello class and calls that method. The definition of the IHello interface is as follows: 1. public interface IHello { Tips on ClassLoaders : This change makes the cast operation at line 9 throw a ClassCastException, since the IHello interface appearing in the definition of the Hello class is different from the IHello interface at line 9. To understand this situation, you must know that multiple ClassLoaders can coexist in Java and they form a tree structure. Each ClassLoader except the root has a parent ClassLoader, which normally loads the class of that child ClassLoader. The request to load a class is delegated along this hierarchy of ClassLoaders. If a class file is loaded by two distinct ClassLoaders, it becomes two distinct classes with the same name and definition. Since the two classes are not identical, an instance of one class is not assignable to a variable of the other class. In the previous example, IHello is loaded at line 7 by a custom ClassLoader of Javassist (let's call it LJ). Since the loaded classes and interfaces are cached in the ClassLoader, the IHello interface loaded by LJ is used as the interface implemented by the Hello class when it's loaded by LJ at line 8. On the other hand, IHello at line 9 represents the interface that's loaded by a ClassLoader that loads the class including line 9 (let's call it LP). Since this ClassLoader LP also loads the class of LJ, it's the parent ClassLoader of LJ. Since the object created by h.newInstance() has the IHello interface loaded by LJ but not LP, the cast operation at line 9 fails and throws an exception. Note that if line 7 were removed, LJ would delegate at line 8 to the parent ClassLoader LP to load IHello, and thus the Hello class loaded by LJ would implement IHello loaded by LP. Thus the cast operation at line 9 would succeed. To avoid this ClassCastException problem, Javassist provides another ClassLoader that allows developers to fully control the classloading behavior. Developers can define an event listener that is notified every time a client requests to load a class so that the listener can properly modify the class. Summary YOUR FEEDBACK
LATEST JAVA STORIES & POSTS
SUBSCRIBE TO THE WORLD'S MOST POWERFUL NEWSLETTERS SUBSCRIBE TO OUR RSS FEEDS & GET YOUR SYS-CON NEWS LIVE!
|
SYS-CON FEATURED WHITEPAPERS MOST READ THIS WEEK SPONSORED BY INFRAGISTICS
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||