Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks developerWorks In this article:
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 1: Classes and class loading A look at classes and what goes on as they're loaded by a JVM
A class in binary Bytecode and stacks Loading
the
classes
Conclusions Resources
Document options
Level: Intermediate
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 29 Apr 2003
About the author
Take a look at what goes on behind the scenes of executing your Java application in this new series on the dynamic aspects of Java programming. Enterprise Java expert Dennis Sosnoski gives the scoop on the Java binary class format and what happens to classes inside the JVM. Along the way, he covers class loading issues ranging from the number of classes required for running a simple Java application to the class loader conflicts that can cause problems in J2EE and similar complex architectures.
Rate this page
Related links Java technology technical library
This article kicks off a new series covering a family of topics that I call Java programming dynamics. These topics range from the basic structure of the Java binary class file format, through run-time metadata access using reflection, all the way to modifying and constructing new classes at run time. The common thread running through all this material is the idea that programming the Java platform is much more dynamic than working with languages that compile straight to native code. If you understand these dynamic aspects, you can do things with Java programming that can't be matched in any other mainstream programming language. In this article, I cover some of the basic concepts that underlie these dynamic features of the Java platform. These concepts revolve around the binary format used to represent Java classes, including what happens when these classes are loaded into the JVM. Not only does this material provide a foundation for the rest of the articles in the series, it also demonstrates some very practical concerns for developers working on the Java platform.
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
A class in binary Developers working in the Java language normally don't need to be concerned about the details of what happens to their source code when it's run through a compiler. In this series, I'm covering a lot of behind-the-scenes details involved in going from source code to executing program, though, so I'll start with a look at the binary classes generated by a compiler. The binary class format is actually defined by the JVM specification. Normally these class representations are generated from Java language source code by a compiler, and they're usually stored in files with a .class extension. Neither of these features is essential, though. Other programming languages have been developed that use the Java binary class format, and for some purposes new class representations are constructed and immediately loaded within an executing JVM. As far as a JVM is concerned, the important part is not the source or how it's stored, but the format itself. So what does this class format actually look like? Listing 1 gives the source code for a (very) short class, along with a partial hexadecimal display of the class file output by the compiler: Listing 1. Source and (partial) binary for Hello.java public class Hello { public static void main(String[] args) { System.out.println("Hello, World!"); } } 0000: 0010: 0020: 0030: 0040: 0050: 0060: 0070: 0080: 0090: 00a0: ...
cafe 000d 0013 5601 0016 7472 0c00 6f72 4865 672f 6c61
babe 000e 0100 0004 285b 696e 1500 6c64 6c6c 4f62 6e67
0000 0800 063c 436f 4c6a 673b 1601 2107 6f01 6a65 2f53
002e 0f0a 696e 6465 6176 2956 000d 0017 0010 6374 7973
001a 0010 6974 0100 612f 0c00 4865 0c00 6a61 0100 7465
0a00 0011 3e01 046d 6c61 0700 6c6c 1800 7661 106a 6d01
0600 0700 0003 6169 6e67 0807 6f2c 1901 2f6c 6176 0003
0c09 1207 2829 6e01 2f53 0014 2057 0005 616e 612f 6f75
................ ................ .....<init>...() V...Code...main. ..([Ljava/lang/S tring;)V........ ........Hello, W orld!........... Hello...java/lan g/Object...java/ lang/System...ou
Inside the binary The very first thing in the binary class representation shown in Listing 1 is the "cafe babe" signature that identifies the Java binary class format (and incidentally serves as a lasting -- but largely unrecognized -tribute to the hard working baristas who kept up the spirits of the developers building the Java platform). This signature is just an easy way of verifying that a block of data really does claim to be an instance of the Java class format. Every Java binary class, even one that isn't present on the file system, needs to start with these four bytes. The rest of the data is less entertaining. Following the signature are a pair of class format version numbers (in this case, for minor version 0 and major version 46 -- 0x2e in hexadecimal -- as generated by the 1.4.1 javac), then a count of entries in the constant pool. The entry count (in this case 26, or 0x001a) is followed by the actual constant pool data. This is where all the constants used by the class definition are stored. It includes class and method names, signatures, and strings (which you can recognize in the text interpretation to the right of the hexadecimal dump), along with various binary values. Items in the constant pool are variable length, with the first byte of each item identifying the type of item and how it should be decoded. I won't go into the details of all that here -- there are many references available if you're interested, starting with the actual JVM specification. The key point is just that the constant pool contains all the references to other classes and methods used by this class, along with the actual definitions for this class and its methods. The constant pool can easily make up half or more of the binary class size, though the average proportion is probably less. Following the constant pool are several items that reference constant pool entries for the class itself, its super class, and interfaces. These items are followed by information about the fields and methods, which are themselves represented as complex structures. The executable code for methods is present in the form of code attributes contained within the method definitions. This code is in the form of instructions for the JVM, generally called bytecode, which is one of the topics for the next section. Attributes are used for several defined purposes in the Java class format, including the already-mentioned bytecode, constant values for fields, exception handling, and debugging information. These purposes aren't the only possible uses for attributes, though. From the beginning, the JVM specification has required JVMs to ignore attributes of unknown types. This requirement gives flexibility for extending the use of attributes to serve other purposes in the future, such as providing meta-information needed by frameworks that work with user classes -- an approach that the Java-derived C# language has used extensively. Unfortunately, no hooks have yet been provided for making use of this flexibility at the user level.
Don't miss the rest of this series Part 2, "Introducing reflection" (June 2003) Part 3, "Applied reflection" (July 2003) Part 4, "Class transformation with Javassist" (September 2003) Part 5, "Transforming classes on-the-fly" (February 2004) Part 6, "Aspect-oriented changes with Javassist" (March 2004) Part 7, "Bytecode engineering with BCEL" (April 2004) Part 8, "Replacing reflection with code generation" (June 2004)
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
Back to top Bytecode and stacks The bytecode that makes up the executable portion of the class file is actually machine code for a special kind of computer -- the JVM. This is called a virtual machine because it's been designed for implementation in software, rather than hardware. Every JVM used to run Java platform applications is built around an implementation of this machine. This virtual machine is actually fairly simple. It uses a stack architecture, meaning instruction operands are loaded to an internal stack before they're used. The instruction set includes all the normal arithmetic and logical operations, along with conditional and unconditional branches, load/store, call/return, stack manipulation, and several special types of instructions. Some of the instructions include immediate operand values that are directly encoded into the instruction. Others directly reference values from the constant pool. Even though the virtual machine is simple, the implementations aren't necessarily so. Early (first generation) JVMs were basically interpreters for the virtual machine bytecode. These actually were relatively simple, but suffered from severe performance problems -- interpreting code is always going to take longer than executing native code. To reduce these performance problems, second generation JVMs added just-in-time (JIT) translation. The JIT technique compiles Java bytecode to native code before executing it for the first time, giving much better performance for repeated executions. Current generation JVMs go even further, using adaptive techniques to monitor program execution and selectively optimize heavily used code. Back to top Loading the classes Languages such as C and C++ that compile to native code generally require a linking step after source code is compiled. This linking process merges code from separately compiled source files, along with shared library code, to form an executable program. The Java language is different. With the Java language, the classes generated by the compiler generally remain just as they are until they're loading into a JVM. Even building a JAR file from the class files doesn't change this -- the JAR is just a container for the class files. Rather than a separate step, linking classes is part of the job performed by the JVM when it loads them into memory. This adds some overhead as classes are initially loaded, but also provides a high level of flexibility for Java applications. For example, applications can be written to use interfaces with the actual implementations left unspecified until run time. This late binding approach to assembling an application is used extensively in the Java platform, with servlets being one common example. The rules for loading classes are spelled out in detail in the JVM specification. The basic principle is that classes are only loaded when needed (or at least appear to be loaded this way -- the JVM has some flexibility in the actual loading, but must maintain a fixed sequence of class initialization). Each class that gets loaded may have other classes that it depends on, so the loading process is recursive. The classes in Listing 2 show how this recursive loading works. The Demo class includes a simple main method that creates an instance of Greeter and calls the greet method. The Greeter constructor creates an instance of Message, which it then uses in the greet method call. Listing 2. Source code for class loading demonstration public class Demo { public static void main(String[] args) { System.out.println("**beginning execution**"); Greeter greeter = new Greeter(); System.out.println("**created Greeter**"); greeter.greet(); } } public class Greeter { private static Message s_message = new Message("Hello, World!");
}
public void greet() { s_message.print(System.out); }
public class Message {
private String m_text; public Message(String text) { m_text = text; } public void print(java.io.PrintStream ps) { ps.println(m_text); }
}
Setting the parameter -verbose:class on the java command line prints a trace of the class loading process. Listing 3 shows partial output from running the Listing 2 program with this parameter: Listing 3. Partial -verbose:class output [Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar] [Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar] [Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar] [Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar] [Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] ... [Loaded java.security.Principal from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded java.security.cert.Certificate from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded Demo] **beginning execution** [Loaded Greeter] [Loaded Message] **created Greeter** Hello, World! [Loaded java.util.HashMap$KeySet from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] [Loaded java.util.HashMap$KeyIterator from /usr/java/j2sdk1.4.1/jre/lib/rt.jar] This is only a partial listing of the most important parts -- the full trace consists of 294 lines, most of which I deleted for this listing. The initial set of class loads (279, in this case) are all triggered by the attempt to load the Demo class. These are the core classes that are used by every Java program, no matter how small. Even eliminating all the code from the Demo main method doesn't affect this initial sequence of loads. The number and names of classes involved will differ from one version of the class libraries to another, though. The portion of the listing after the Demo class is loaded is more interesting. The sequence here shows that the Greeter class is only loaded when an instance of the class is about to be created. However, the Greeter class uses a static instance of the Message class, so before an instance of the former can be created, the latter class also needs to be loaded. A lot happens inside the JVM when a class is loaded and initialized, including decoding the binary class format, checking compatibility with other classes, verifying the sequence of bytecode operations, and finally constructing a java.lang.Class instance to represent the new class. This Class object becomes the basis for all instances of the new class created by the JVM. It's also the identifier for the loaded class itself -- you can have multiple copies of the same binary class loaded in a JVM, each with its own Class instance. Even though these copies all share the same class name, they will be separate classes to the JVM.
Off the beaten (class) path Class loading in a JVM is controlled by class loaders. There's a bootstrap class loader built into the JVM that's responsible for loading the basic Java class library classes. This particular class loader has some special features. For one thing, it only loads classes found on the boot class path. Because these are trusted system classes, the bootstrap loader skips much of the validation that gets done for normal (untrusted) classes. Bootstrap isn't the only class loader. For starters, a JVM defines an extension class loader for loading classes from standard Java extension APIs, and a system class loader for loading classes from the general class path (including your application classes). Applications can also define their own class loaders for special purposes (such as run-time reloading of classes). Such added class loaders are derived from the java.lang.ClassLoader class (possibly indirectly), which provides the core support for building an internal class representation (a java.lang.Class instance) from an array of bytes. Each constructed class is in some sense "owned" by the class loader that loaded it. Class loaders normally keep a map of the classes they've loaded, to be able to find one by name if it's requested again. Each class loader also keeps a reference to a parent class loader, defining a tree of class loaders with the bootstrap loader at the root. When an instance of a particular class (identified by name) is needed, whichever class loader initially handles the request normally checks with its parent class loader first before trying to load the class directly. This applies recursively if there are multiple layers of class loaders, so it means that a class will normally be visible not only within the class loader that loaded it, but also to all descendant class loaders. It also means that if a class can be loaded by more than one class loader in a chain, the one furthest up the tree will be the one that actually loads it. There are many circumstances where multiple application classloaders are used by Java programs. One example is within the J2EE framework. Each J2EE application loaded by the framework needs to have a separate class loader to prevent classes in one application from interfering with other applications. The framework code itself will also use one or more other class loaders, again to prevent interference to or from applications. The complete set of class loaders make up a tree-structured hierarchy with different types of classes loaded at each level.
Trees of loaders As an example of a class loader hierarchy in action, Figure 1 shows the class loader hierarchy defined by the Tomcat servlet engine. Here the Common class loader loads from JAR files in a particular directory of the Tomcat installation that's intended for code shared between the server and all Web applications. The Catalina loader is for Tomcat's own classes, and the Shared loader for classes shared between Web applications. Finally, each Web application gets its own loader for its private classes. Figure 1. Tomcat class loaders
In this type of environment, keeping track of the proper loader to use for requesting a new class can be messy. Because of this, the setContextClassLoader and getContextClassLoader methods were added to the java.lang.Thread class in the Java 2 platform. These methods let the framework set the class loader to be used for each application while running code from that application. The flexibility of being able to load independent sets of classes is an important feature of the Java platform. Useful as this feature is, though, it can create confusion in some cases. One confusing aspect is the continuing issue of dealing with JVM classpaths. In the Tomcat hierarchy of class loaders shown in Figure 1, for instance, classes loaded by the Common class loader will never be able to directly access (by name) classes loaded by a Web application. The only way to tie these together is through the use of interfaces visible to both sets of classes. In this case, that includes the javax.servlet.Servlet implemented by Java servlets. Problems can arise when code is moved between class loaders for any reason. For instance, when J2SE 1.4 moved the JAXP API for XML processing into the standard distribution, it created problems for many environments where applications had previously relied on loading their own chosen implementations of the XML APIs. With J2SE 1.3, this can be done just by including the appropriate JAR file in the user class path. In J2SE 1.4, the standard versions of these APIs are now in the extensions class path, so these will normally override any implementations present in the user class path. Other types of confusion are also possible when using multiple class loaders. Figure 2 shows an example of a class identity crisis that results when an interface and associated implementation are each loaded by two separate class loaders. Even though the names and binary implementations of the interfaces and classes are the same, an instance of the class from one loader cannot be recognized as implementing the interface from the other loader. This confusion could be resolved in Figure 2 by moving the interface class I into the System class loader's space. There would still be two separate instances of class A, but both would implement the same interface I. Figure 2. Class identity crisis
Back to top Conclusions The Java class definition and JVM specification together define an extremely powerful framework for run-time assembly of code. Through the use of class loaders, Java applications are able to work with multiple versions of classes that would otherwise cause conflicts. The flexibility of class loaders even allows dynamic reloading of modified code while an application continues to execute. The cost of the Java platform's flexibility in this area is somewhat higher overhead when starting an application. Hundreds of separate classes need to be loaded by the JVM before it can start executing even the simplest application code. This startup cost generally makes the Java platform better suited to long-running, server-type applications than for frequently used small programs. Server applications also benefit the most from the flexibility of run-time assembly of code, so it's no surprise that the Java platform has become increasingly favored for this type of development. In Part 2 of this series, I'll cover an introduction to using another aspect of the Java platform's dynamic underpinnings: the Reflection API. Reflection gives your executing code access to internal class information. This can be a great tool for building flexible code that can be hooked together at run time without the need for any source code links between classes. But as with most tools, you need to know when and how to use it to best advantage. Check back to find out the tricks and trade-offs of effective reflection in Part 2 of Java programming dynamics.
Resources ď Ź
Go straight to the source in The Java Virtual Machine Specification for details of the binary class format, loading of classes, and actual Java bytecode.
ď Ź
Learn all the details of building your own special class loaders with the tutorial, "Understanding the Java ClassLoader" by Greg Travis (developerWorks, April 2001).
Martyn Honeyford's popular article " Weighing in on Java native compilation" (developerWorks, January 2002) offers more details regarding the issues and trade-offs of native code compilation for the Java language.
The binary class format contains a remarkable amount of information, often even enough to reconstruct your source code (except for comments). Greg Travis shows you how you can use this information in his article "How to lock down your Java code (or open up someone else's) " (developerWorks, May 2001).
Get details on the Jikes Research Virtual Machine (RVM), which is implemented in the Java language and is self-hosted (that is, its Java code runs on itself without requiring a second virtual machine).
Keep up with progress on making attributes available to Java developers with Java Specification Request (JSR) 175 for A Metadata Facility for the Java Programming Language.
Find out the details of the Apache Tomcat Java language Web server project of the Apache Software Foundation, including the details of Tomcat class loader usage.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 1: Classes and class http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html loading 10804 04292003
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks developerWorks In this article: Beginners'
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 2: Introducing reflection Use run-time class information to limber up your programming
class
Reflections on a class Security and reflection Reflection
performance
Reflection
summary
Document options
Level: Intermediate
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 03 Jun 2003
Download
Sample code
Reflection gives your code access to internal information for classes loaded into the JVM and allows you to write code that works with classes selected during execution, not in the source code. This makes reflection a great tool for building flexible applications. But watch out -- if used inappropriately, reflection can be costly. In Part 2 of his series on Java platform internals, software consultant Dennis Sosnoski provides an introduction to using reflection, as well as a look at some of the costs involved. You'll also find out how the Java Reflection API lets you hook into objects at run time.
Resources About the author Rate this page
Related links Java technology technical library
In "Java programming dynamics, Part 1," I gave you an introduction to Java programming classes and class loading. That article described some of the extensive information present in the Java binary class format. This month I cover the basics of using the Java Reflection API to access and use some of that same information at run time. To help keep things interesting even for developers who already know the basics of reflection, I'm including a look at how reflection performance compares with direct access. Using reflection is different from normal Java programming in that it works with metadata -- data that describes other data. The particular type of metadata accessed by Java language reflection is the description of classes and objects within the JVM. Reflection gives you run-time access to a variety of class information. It even lets you read and write fields and call methods of a class selected at run time. Reflection is a powerful tool. It lets you build flexible code that can be assembled at run time without requiring source code links between components. But some aspects of reflection can be problematic. In this article, I'll go into the reasons why you might not want to use reflection in your programs, as well as the reasons why you would. After you know the trade-offs, you can decide for yourself when the benefits outweigh the drawbacks.
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
Beginners' class The starting point for using reflection is always a java.lang.Class instance. If you want to work with a predetermined class, the Java language provides an easy shortcut to get the Class instance directly: Class clas = MyClass.class;
When you use this technique, all the work involved in loading the class takes place behind the scenes. If you need to read the class name at run time from some external source, however, this approach isn't going to work. Instead, you need to use a class loader to find the class information. Here's one way to do that: // "name" is the class name to load Class clas = null; try { clas = Class.forName(name); } catch (ClassNotFoundException ex) { // handle exception case } // use the loaded class
Don't miss the rest of this series Part 1, "Classes and class loading" (April 2003) Part 3, "Applied reflection" (July 2003) Part 4, "Class transformation with Javassist" (September 2003) Part 5, "Transforming classes on-the-fly" (February 2004) Part 6, "Aspect-oriented changes with Javassist" (March 2004) Part 7, "Bytecode engineering with BCEL" (April 2004) Part 8, "Replacing reflection with code generation" (June 2004)
If the class has already been loaded, you'll get back the existing Class information. If the class hasn't been loaded yet, the class loader will load it now and return the newly constructed class instance. Back to top Reflections on a class The Class object gives you all the basic hooks for reflection access to the class metadata. This metadata includes information about the class itself, such as the package and superclass of the class, as well as the interfaces implemented by the class. It also includes details of the constructors, fields, and methods defined by the class. These last items are the ones most often used in programming, so I'll give some examples of working with them later in this section. For each of these three types of class components -- constructors, fields, and methods -- the java.lang.Class provides four separate reflection calls to access information in different ways. The calls all follow a standard form. Here's the set used to find constructors:
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
Constructor getConstructor(Class[] params) -- Gets the public constructor using the specified parameter types
Constructor[] getConstructors() -- Gets all the public constructors for the class
Constructor getDeclaredConstructor(Class[] params) -- Gets the constructor (regardless of access level) using the specified parameter types
Constructor[] getDeclaredConstructors() -- Gets all the constructors (regardless of access level) for the class
Each of these calls returns one or more java.lang.reflect.Constructor instances. This Constructor class defines a newInstance method that takes an array of objects as its only argument, then returns a newly constructed instance of the original class. The array of objects are the parameter values used for the constructor call. As an example of how this works, suppose you have a TwoString class with a constructor that takes a pair of Strings, as shown in Listing 1: Listing 1. Class constructed from pair of strings public class TwoString { private String m_s1, m_s2; public TwoString(String s1, String s2) { m_s1 = s1; m_s2 = s2; } }
The code shown in Listing 2 gets the constructor and uses it to create an instance of the TwoString class using Strings "a" and "b": Listing 2. Reflection call to constructor Class[] types = new Class[] { String.class, String.class }; Constructor cons = TwoString.class.getConstructor(types); Object[] args = new Object[] { "a", "b" }; TwoString ts = cons.newInstance(args);
The code in Listing 2 ignores several possible types of checked exceptions thrown by the various reflection methods. The exceptions are detailed in the Javadoc API descriptions, so in the interest of conciseness, I'm leaving them out of all the code examples. While I'm on the topic of constructors, the Java programming language also defines a special shortcut method you can use to create an instance of a class with a no-argument (or default) constructor. The shortcut is embedded into the Class definition itself like this: Object newInstance() -- Constructs new instance using default constructor Even though this approach only lets you use one particular constructor, it makes a very convenient shortcut if that's the one you want. This technique is especially useful when working with JavaBeans, which are required to define a public, no-argument constructor.
Fields by reflection The Class reflection calls to access field information are similar to those used to access constructors, with a field name used in place of an array of parameter types:
Field getField(String name) -- Gets the named public field
Field[] getFields() -- Gets all public fields of the class
Field getDeclaredField(String name) -- Gets the named field declared by the class
Field[] getDeclaredFields() -- Gets all the fields declared by the class
Despite the similarity to the constructor calls, there's one important difference when it comes to fields: the first two variants return information for public fields that can be accessed through the class -- even those inherited from an ancestor class. The last two return information for fields declared directly by the class -- regardless of the fields' access types. The java.lang.reflect.Field instances returned by the calls define getXXX and setXXX methods for all the primitive types, as well as generic get and set methods that work with object references. It's up to you to use an appropriate method based on the actual field type, though the getXXX methods will handle widening conversions automatically (such as using the getInt method to retrieve a byte value). Listing 3 shows an example of using the field reflection methods, in the form of a method to increment an int field of an object by name: Listing 3. Incrementing a field by reflection public int incrementField(String name, Object obj) throws... { Field field = obj.getClass().getDeclaredField(name);
int value = field.getInt(obj) + 1; field.setInt(obj, value); return value;
}
This method starts to show some of the flexibility possible with reflection. Rather than working with a specific class, incrementField uses the getClass method of the passed-in object to find the class information, then finds the named field directly in that class.
Methods by reflection The Class reflection calls to access method information are very similar to those used for constructors and fields:
Method getMethod(String name, Class[] params) -- Gets the named public method using the specified parameter types
Method[] getMethods() -- Gets all public methods of the class
Method getDeclaredMethod(String name, Class[] params) -- Gets the named method declared by the class using the specified parameter types
Method[] getDeclaredMethods() -- Gets all the methods declared by the class
As with the field calls, the first two variants return information for public methods that can be accessed through the class -- even those inherited from an ancestor class. The last two return information for methods declared directly by the class, without regard to the access type of the method. The java.lang.reflect.Method instances returned by the calls define an invoke method you can use to call the method on an instance of the defining class. This invoke method takes two arguments, which supply the class instance and an array of parameter values for the call. Listing 4 takes the field example a step further, showing an example of method reflection in action. This method increments an int JavaBean property defined with get and set methods. For example, if the object defined getCount and setCount methods for an integer count value, you could pass "count" as the name parameter in a call to this method in order to increment that value. Listing 4. Incrementing a JavaBean property by reflection public int incrementProperty(String name, Object obj) { String prop = Character.toUpperCase(name.charAt(0)) + name.substring(1); String mname = "get" + prop; Class[] types = new Class[] {}; Method method = obj.getClass().getMethod(mname, types); Object result = method.invoke(obj, new Object[0]); int value = ((Integer)result).intValue() + 1; mname = "set" + prop; types = new Class[] { int.class }; method = obj.getClass().getMethod(mname, types); method.invoke(obj, new Object[] { new Integer(value) }); return value; }
To follow the JavaBeans convention, I convert the first letter of the property name to uppercase, then prepend get to construct the read method name and set to construct the write method name. JavaBeans read methods just return the value and write methods take the value as the only parameter, so I specify the parameter types for the methods to match. Finally, the convention requires the methods to be public, so I use the form of lookup that finds public methods callable on the class. This example is the first one where I've passed primitive values using reflection, so let's look at how this works. The basic principle is simple: whenever you need to pass a primitive value, just substitute an instance of the corresponding wrapper class (defined in the java.lang package) for that type of primitive. This applies to both calls and returns. So when I call the get method in my example, I expect the result to be a java.lang.Integer wrapper for the actual int property value.
Reflecting arrays Arrays are objects in the Java programming language. Like all objects, they have classes. If you have an array, you can get the class of that array using the standard getClass method, just as with any other object. However, getting the class without an existing instance works differently than for other types of objects. Even after you have an array class there's not much you can do with it directly -- the constructor access provided by reflection for normal classes doesn't work for arrays, and arrays don't have any accessible fields. Only the base java.lang.Object methods are defined for array objects. The special handling of arrays uses a collection of static methods provided by the java.lang.reflect.Array class. The methods in this class let you create new arrays, get the length of an array object, and read and write indexed values of an array object. Listing 5 shows a useful method for effectively resizing an existing array. It uses reflection to create a new array of the same type, then copies all the data across from the old array before returning the new array. Listing 5. Growing an array by reflection public Object growArray(Object array, int size) { Class type = array.getClass().getComponentType(); Object grown = Array.newInstance(type, size); System.arraycopy(array, 0, grown, 0, Math.min(Array.getLength(array), size)); return grown; }
Back to top Security and reflection Security can be a complex issue when dealing with reflection. Reflection is often used by framework-type code, and for this you may want the framework to have full access to your code without concern for normal access restrictions. Yet uncontrolled access can create major security risks in other cases, such as when code is executed in an environment shared by untrusted code. Because of these conflicting needs, the Java programming language defines a multi-level approach to handling reflection security. The basic mode is to enforce the same restrictions on reflection as would apply for source code access:
Access from anywhere to public components of the class No access outside the class itself to private components Limited access to protected and package (default access) components
There's a simple way around these restrictions, though -- at least sometimes. The Constructor, Field, and Method classes I've used in the earlier examples all extend a common base class -- the java.lang.reflect.AccessibleObject class. This class defines a setAccessible method that lets you turn the access checks on or off for an instance of one of these classes. The only catch is that if a security manager is present, it will check that the code turning off access checks has permission to do so. If there's no permission, the security manager throws an exception. Listing 6 demonstrates a program that uses reflection on an instance of the Listing 1 TwoString class to show this in action: Listing 6. Reflection security in action public class ReflectSecurity { public static void main(String[] args) { try { TwoString ts = new TwoString("a", "b"); Field field = clas.getDeclaredField("m_s1"); // field.setAccessible(true); System.out.println("Retrieved value is " + field.get(inst)); } catch (Exception ex) { ex.printStackTrace(System.out); } } }
If you compile this code and run it directly from the command line without any special parameters, it'll throw an IllegalAccessException on the field.get(inst) call. If you uncomment the field.setAccessible(true) line, then recompile and rerun the code, it will succeed. Finally, if you add the JVM parameter -Djava.security.manager on the command line to enable a security manager, it will again fail, unless you define permissions for the ReflectSecurity class. Back to top Reflection performance Reflection is a powerful tool, but suffers from a few drawbacks. One of the main drawbacks is the effect on performance. Using reflection is basically an interpreted operation, where you tell the JVM what you want to do and it does it for you. This type of operation is always going to be slower than just doing the same operation directly. To demonstrate the performance cost of using reflection, I prepared a set of benchmark programs for this article (see Resources for a link to the full code). Listing 7 shows an excerpt from the field access performance test, including the basic test methods. Each method tests one form of access to fields -- accessSame works with member fields of the same object, accessOther uses fields of another object accessed directly, and accessReflection uses fields of another object accessed by reflection. In each case, the methods perform the same computations -- a simple add/multiply sequence in a loop. Listing 7. Field access performance test code public int accessSame(int loops) { m_value = 0; for (int index = 0; index < loops; index++) { m_value = (m_value + ADDITIVE_VALUE) * MULTIPLIER_VALUE; } return m_value; } public int accessReference(int loops) { TimingClass timing = new TimingClass(); for (int index = 0; index < loops; index++) { timing.m_value = (timing.m_value + ADDITIVE_VALUE) * MULTIPLIER_VALUE; } return timing.m_value; } public int accessReflection(int loops) throws Exception { TimingClass timing = new TimingClass(); try {
}
Field field = TimingClass.class. getDeclaredField("m_value"); for (int index = 0; index < loops; index++) { int value = (field.getInt(timing) + ADDITIVE_VALUE) * MULTIPLIER_VALUE; field.setInt(timing, value); } return timing.m_value; } catch (Exception ex) { System.out.println("Error using reflection"); throw ex; }
The test program calls each method repeatedly with a large loop count, averaging the time measurements over several calls. The time for the first call to each method is not included in the average, so initialization time isn't a factor in the results. In the test runs for this article, I used a loop count of 10 million for each call, running on a 1GHz PIIIm system. My timing results with three different Linux JVMs are shown in Figure 1. All tests used the default settings for each JVM. Figure 1. Field access times
The logarithmic scale of the chart allows the full range of times to be displayed, but lessens the visual impact of the differences. In the case of the first two sets of figures (the Sun JVMs), the execution time using reflection is over 1000 times greater than that using direct access. The IBM JVM does somewhat better by comparison, but the reflection method still takes more than 700 times as long as the other methods. There were no significant differences in times between the other two methods on any JVM, though the IBM JVM did run these almost twice as fast as the Sun JVMs. Most likely, this difference reflects the specialized optimizations used by the Sun Hot Spot JVMs, which tend to do poorly in simple benchmarks. Besides the field access time tests, I did the same sort of timing test for method calls. For method calls, I tried the same three access variations as for field access, with the added variable of using no-argument methods versus passing and returning a value on the method calls. Listing 8 shows the code for the three methods used to test the passed-and-returned value form of the calls. Listing 8. Method access performance test code public int callDirectArgs(int loops) { int value = 0; for (int index = 0; index < loops; index++) { value = step(value); } return value; } public int callReferenceArgs(int loops) { TimingClass timing = new TimingClass(); int value = 0; for (int index = 0; index < loops; index++) { value = timing.step(value); } return value; } public int callReflectArgs(int loops) throws Exception { TimingClass timing = new TimingClass(); try { Method method = TimingClass.class.getMethod ("step", new Class [] { int.class }); Object[] args = new Object[1]; Object value = new Integer(0); for (int index = 0; index < loops; index++) { args[0] = value; value = method.invoke(timing, args); } return ((Integer)value).intValue(); } catch (Exception ex) { System.out.println("Error using reflection"); throw ex; } }
Figure 2 shows my timing results for method calls. Here again, reflection is much slower than the direct alternative. The differences aren't quite as large as for the field access case, though, ranging from several hundred times slower on the Sun 1.3.1 JVM to less than 30 times slower on the IBM JVM for the no-argument case. The test performance for reflection method calls with arguments are substantially slower than the calls with no arguments on all JVMs. This is probably partially because of the java.lang.Integer wrapper needed for the int value passed and returned. Because Integers are immutable, a new one needs to be generated for each method return, adding considerable overhead. Figure 2. Method call times
Reflection performance was one area of focus for Sun when developing the 1.4 JVM, which shows in the reflection method call results. The Sun 1.4.1 JVM shows greatly improved performance over the 1.3.1 version for this type of operation, running about seven times faster in my tests. The IBM 1.4.0 JVM again delivered even better performance for this simple test, though, running two to three times faster than the Sun 1.4.1 JVM. I also wrote a similar timing test program for creating objects using reflection. The differences for this case aren't nearly as significant as for the field and method call cases, though. Constructing a simple java.lang.Object instance with a newInstance() call takes about 12 times longer than using new Object() on the Sun 1.3.1 JVM, about four times longer on the IBM 1.4.0 JVM, and only about two times longer on the Sun 1.4.1 JVM. Constructing an array using Array.newInstance(type, size) takes a maximum of about two times longer than using new type[size] for any tested JVM, with the difference decreasing as the array size grows. Back to top Reflection summary Java language reflection provides a very versatile way of dynamically linking program components. It allows your program to create and manipulate objects of any classes (subject to security restrictions) without the need to hardcode the target classes ahead of time. These features make reflection especially useful for creating libraries that work with objects in very general ways. For example, reflection is often used in frameworks that persist objects to databases, XML, or other external formats. Reflection also has a couple of drawbacks. One is the performance issue. Reflection is much slower than direct code when used for field and method access. To what extent that matters depends on how reflection is used in a program. If it's used as a relatively infrequent part of the program's operation, the slow performance won't be a concern. Even the worst -case timing figures in my tests showed reflection operations taking only a few microseconds. The performance issues only become a serious concern if reflection is used in the core logic of performance-critical applications. A more serious drawback for many applications is that using reflection can obscure what's actually going on inside your code. Programmers expect to see the logic of a program in the source code, and techniques such as reflection that bypass the source code can create maintenance problems. Reflection code is also more complex than the corresponding direct code, as can be seen in the code samples from the performance comparisons. The best ways to deal with these issues are to use reflection sparingly -- only in the places where it really adds useful flexibility -- and document its use within the target classes. In the next installment, I'll give a more detailed example of how to use reflection. This example provides an API for processing command line arguments to a Java application, a tool you may find useful for your own applications. It also builds on the strengths of reflection while avoiding the weaknesses. Can reflection simplify your command line processing? Find out in Part 3 of Java programming dynamics.
Back to top Download Name j-dyn0603.zip
Size
Download method
HTTP
Information about download methods
Resources
Download the performance benchmarks programs used in this article.
Get a historical perspective on the growing capabilities of Java reflection in "Reflection: A new way to discover information about Java classes" (developerWorks, May 1998).
Reflection plays an especially important role in working with JavaBeans components. Learn all about this in "Reflecting, introspecting, and customizing JavaBeans" (developerWorks, February 2000).
Extensive use of reflection can have harmful effects on framework performance. For a graphic example and some related discussions, see the author's XML data binding articles " Data binding, Part 2: Performance" (developerWorks, January 2003) and "Data binding, Part 3: JiBX architecture" (developerWorks, April 2003).
For an in-depth tutorial on using reflection, try The Reflection API trail in the Java Tutorial from Sun.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 2: Introducing reflection http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html 10819 06032003
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 3: Applied reflection
developerWorks In this article:
Building a framework for command line arguments
Defining the problem Choosing
the
Implementing library
interface the
Closing the library Resources
Command line argument processing is one of those nasty chores that seems to keep coming around no matter how many times you've dealt with it in the past. Rather than writing variations of the same code over and over, why not use reflection to simplify the job of argument processing? Java consultant Dennis Sosnoski shows you how. In this article, Dennis outlines an open source library that makes command line arguments practically handle themselves.
Rate this page
Java technology technical library
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 15 Jul 2003
About the author
Related links
Document options
Level: Intermediate
In last month's article , I introduced the Java Reflection API and ran through some of its basic capabilities. I also looked into reflection performance, and ended with some guidelines for when reflection should or should not be used in an application. This month I'm going even further by looking at an application that seems to be a good match for reflection's strengths and weaknesses: a library for command line argument processing. I'll start by first defining the problem to be solved, then designing an interface for the library before actually getting into the implementation code. My actual experience developing the library was not so structured -- I started out trying to simplify existing code in a family of applications that use a common code base, then generalized from there. The linear "definedesign-build" sequence in this article is much more concise than a full description of the development process, though, and in the process of organizing it this way I've revised some of my initial assumptions and cleaned up several aspects of the library code. Hopefully, you'll find it useful as a model for developing your own reflection -based applications.
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
Defining the problem I've written many Java applications using arguments from the command line. Most started out really small, but several ended up growing way beyond my initial plans. There's a standard pattern I've observed in how this process works for me: 1. Start with one or two required arguments in a particular order.
Don't miss the rest of this series Part 1, "Classes and class loading" (April 2003)
2. Think of more things the application should do, then add more arguments.
Part 2, "Introducing reflection" (June 2003)
3. Get tired of typing in all the arguments every time, so make some of the arguments optional, with default values.
Part 4, "Class transformation with Javassist" (September 2003) Part 5, "Transforming classes on-the-fly" (February 2004)
4. Forget the order of the arguments, so change the code to allow them in any order. 5. Give the application to other people who are interested. They don't know what the arguments are supposed to be, so add better error checking and "help" descriptions for the arguments.
Part 6, "Aspect-oriented changes with Javassist" (March 2004) Part 7, "Bytecode engineering with BCEL" (April 2004) Part 8, "Replacing reflection with code generation" (June 2004)
By the time I get to step 5 I usually regret that I began the whole process in the first place. Fortunately, I tend to forget the latter stages pretty quickly, and within a week or two I'll think of yet another simple little command line application I'd like to have. After that it's just a matter of time before the whole ugly cycle repeats. There are several libraries available to help with command line argument processing. I'm going to ignore them, however, and go my own way in this article. This isn't (just) because I have a "not invented here" attitude, but rather to use argument processing as an example. As it happens, the strengths and weakness of reflection are a good match with the requirements for an argument processing library. In particular, an argument processing library:
Needs a flexible interface to support a variety of applications Must be easy to configure for each application Doesn't require top performance, because the arguments are only processed once Has no access security issues, because command line applications generally run without a security manager
The actual reflection code within this library represents only a small portion of the full implementation, so I'll focus primarily on the aspects that are most relevant to reflection. If you want to find out more about the library (and perhaps use it for your own simple command line applications), you'll find the link to the Web site in the Resources section.
Sketching out a design
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, Probably the most convenient way for an application to access argument data is through fields of the application's main object. or general JVM issues, visit the JVM and Bytecode discussion forum, moderated For example, suppose you're writing an application that generates business plans. You might want to use a boolean flag to by Dennis Sosnoski. control whether the business plan is concise or wordy, an int for first year revenue, a float for the expected compound String revenue growth rate, and a for the product description. I'll call these variables that influence the operation of the application parameters to distinguish them from the actual arguments (the values for the parameter variables) supplied on the command line. Using fields for these parameters will make them easily available at any point in the application code where they're needed. It's also easy to set defaults for any of the parameters right at the point of definition when using fields, as shown in Listing 1: Listing 1. Business plan generator (partial listing) public class PlanGen { private boolean m_isConcise; // rarely used, default false private int m_initialRevenue = 1000; // thousands, default is 1M private float m_growthRate = 1.5; // default is 50% growth rate private String m_productDescription = // McD look out, here I come "eFood - (Really) Fast Food Online"; ... private int revenueForYear(int year) { return (int)(m_initialRevenue * Math.pow(m_growthRate, year-1)); } ...
Reflection will give direct access to these private fields, allowing the argument processing library to set values without any special hooks in the application code. I do need some way for the library to relate these fields to particular command line arguments, though. Before I can define how this linkage between an argument and a field is communicated to the library, I first need to decide how I want to format the command line arguments. For this article, I'll define a command line format that's a simplified version of UNIX conventions. Argument values for parameters can be supplied in any order, with a leading hyphen to indicate that an argument gives one or more single-character parameter flags (as opposed to actual parameter values). For the business plan generator, I'll pick these parameter flag characters:
c -- concise plan f -- first year revenue ($ thousands) g -- growth rate (yearly multiplier) n -- product name
The boolean parameters only need the flag character itself to set a value, but the other types of parameters require some sort of additional argument information. I'll just append the value of a numeric argument immediately following the parameter flag character (which means digits can't be used as flag characters), while for String-valued parameters I'll use the argument following the flag character on the command line as the actual value. Finally, if there are required parameters (such as an output file name for the business plan generator), I'll assume the argument values for these follow the optional parameter values on the command line. Given these conventions, a command line for the business plan generator might look like this: java PlanGen -c -f2500 -g2.5 -n "iSue4U - Litigation at Internet Speed" plan.txt When it's all put together, the meaning of each argument is:
-c -- generates concise plan -f2500 -- first year revenue of $2,500,000 -g2.5 -- growth rate of 250 percent per year -n "iSue4U . . ." -- product name is "iSue4U . . ." plan.txt -- required name of the output file
At this point I've got a basic functional specification for the argument processing library. The next step is to define a specific interface for the application code to use the library. Back to top Choosing the interface You can handle the actual processing of command line arguments with a single call, but the application first needs a way to define its particular parameters to the library. These parameters can be of several different types (in the case of the business plan generator example, they can be boolean, int, float, and java.lang.String). Each type may also have some special requirements. For instance, it would be nice to let the boolean parameters be defined as false if the flag character is present, rather than always true if present. It would also be useful to define a valid range for an int value. I'll handle these differing requirements by using a base class for all parameter definitions, subclassing it for each specific type of parameter. This approach lets the application supply the parameter definitions to the library as an array of instances of the base parameter definition class, while the actual definitions can use the specific subclass matching each parameter type. For the business plan generator example, this could take the form shown in Listing 2: Listing 2. Parameter definitions for business plan generator private static final ParameterDef[] PARM_DEFS = { new BoolDef('c', "m_isConcise"), new IntDef('f', "m_initialRevenue", 10, 10000), new FloatDef('g', "m_growthRate", 1.0, 100.0), new StringDef('n', "m_productDescription") }
With the allowed parameters defined in an array, the call from the application program to the argument processing code can be kept as simple as a single call to a static method. To allow for added arguments beyond those defined in the parameter array (either required values or variable length sets of values), I'll have the call return the actual number of arguments processed. This lets the application check for additional arguments and use them appropriately. The end result looks like Listing 3: Listing 3. Using the library public class PlanGen
{
private static final ParameterDef[] PARM_DEFS = { ... }; public static void main(String[] args) { // if no arguments are supplied, assume help is needed if (args.length > 0) { // process arguments directly to instance PlanGen inst = new PlanGen(); int next = ArgumentProcessor.processArgs (args, PARM_DEFS, inst);
}
}
// next unused argument is output file name if (next >= args.length) { System.err.println("Missing required output file name"); System.exit(1); } File outf = new File(args[next++]); ... } else { System.out.println("\nUsage: java PlanGen " + "[-options] file\nOptions are:\n c concise plan\n" + "f first year revenue (K$)\n g growth rate\n" + "n product description"); }
The only part remaining is the handling of error reporting (such as an unknown parameter flag character or a numeric value out of range). For this purpose I'll define ArgumentErrorException as an unchecked exception to be thrown if one of these errors occurs. If this exception isn't caught, it will immediately kill the application with an error message and stack trace dumped to the console. As an alternative, you can catch this exception directly in your code and handle it some other way (perhaps printing out the actual error message along with usage information, for instance). Back to top Implementing the library For the library to use reflection as planned, it needs to look up the fields specified by the array of parameter definitions and then store the appropriate values to these fields from corresponding command line arguments. This task could be handled by just looking up the field information as needed for the actual command line arguments, but I've instead made a choice to separate the lookup from the usage. I'll find all the fields in advance, then just use the information that's already been found during processing of the arguments. Finding all the fields in advance is a defensive programming step to eliminate one of the potential problems with using reflection. If I only looked up the fields as needed, it would be easy to break a parameter definition (for instance, by mistyping the corresponding field name) without realizing that anything was wrong. There would be no compile-time errors because the field names are passed as Strings, and the program would even execute fine as long as no argument matching the broken parameter definition was specified on the command line. This type of masked error can easily result in shipping broken code. Given that I want to look up the field information before actually processing the arguments, Listing 4 shows a base class implementation for the parameter definitions with a bindToClass() method that handles the field lookup. Listing 4. Base class for parameter definitions public abstract class ParameterDef { protected char m_char; // argument flag character protected String m_name; // parameter field name protected Field m_field; // actual parameter field protected ParameterDef(char chr, String name) { m_char = chr; m_name = name; } public char getFlag() { return m_char; } protected void bindToClass(Class clas) { try { // handle the field look up and accessibility m_field = clas.getDeclaredField(m_name); m_field.setAccessible(true);
} }
} catch (NoSuchFieldException ex) { throw new IllegalArgumentException("Field '" + m_name + "' not found in " + clas.getName()); }
public abstract void handle(ArgumentProcessor proc);
The actual library implementation involves a few classes beyond what I've mentioned in this article. I'm not going to go through the whole list here, since most are irrelevant to the reflection aspect of the library. What I will mention is that I chose to store the target object as a field of the ArgumentProcessor class and implement the actual setting of a parameter field within this class. This approach gives a simple pattern for argument processing: the ArgumentProcessor class scans the arguments to find parameter flags, looks up the corresponding parameter definition for each flag (which will always be a subclass of ParameterDef), and calls the handle() method of the definition. The handle() method in turn calls a setValue() method of the ArgumentProcessor after interpreting the argument value. Listing 5 shows a partial version of the ArgumentProcessor class, including the parameter binding calls in the constructor and the setValue() method: Listing 5. Partial listing of main library class public class ArgumentProcessor { private Object m_targetObject; // parameter value object private int m_currentIndex; // current argument position ... public ArgumentProcessor(ParameterDef[] parms, Object target) { // bind all parameters to target class for (int i = 0; i < parms.length; i++) { parms[i].bindToClass(target.getClass()); }
}
// save target object for later use m_targetObject = target;
public void setValue(Object value, Field field) { try { // set parameter field value using reflection field.set(m_targetObject, value); } catch (IllegalAccessException ex) { throw new IllegalArgumentException("Field " + field.getName() + " is not accessible in object of class " + m_targetObject.getClass().getName()); } } public void reportArgumentError(char flag, String text) { throw new ArgumentErrorException(text + " for argument '" + flag + "' in argument " + m_currentIndex); }
}
public static int processArgs(String[] args, ParameterDef[] parms, Object target) { ArgumentProcessor inst = new ArgumentProcessor(parms, target); ... }
Finally, Listing 6 shows a partial implementation of the parameter definition subclass for int parameter values. This includes an override of the base class bindToClass() method (from Listing 4) that first calls the base class implementation and then checks that the field found matches the expected type. The subclasses for other specific parameter types (boolean, float, String, and so on) are very similar. Listing 6. int parameter definition class public class IntDef extends ParameterDef { private int m_min; // minimum allowed value private int m_max; // maximum allowed value public IntDef(char chr, String name, int min, int max) { super(chr, name); m_min = min; m_max = max; } protected void bindToClass(Class clas) { super.bindToClass(clas); Class type = m_field.getType(); if (type != Integer.class && type != Integer.TYPE) { throw new IllegalArgumentException("Field '" + m_name + "'in " + clas.getName() + " is not of type int"); } } public void handle(ArgumentProcessor proc) { // set up for validating boolean minus = false; boolean digits = false; int value = 0; // convert number supplied in argument list to 'value' ... // make sure we have a valid value
}
}
value = minus ? -value : value; if (!digits) { proc.reportArgumentError(m_char, "Missing value"); } else if (value < m_min || value > m_max) { proc.reportArgumentError(m_char, "Value out of range"); } else { proc.setValue(new Integer(value), m_field); }
Back to top Closing the library In this article, I've run through the design of a library for processing command line arguments as an example of reflection in action. This library makes a good illustration of how to use reflection effectively -- it simplifies application code without sacrificing significant performance. How much performance is sacrificed? In some quick tests on my development system, a simple test program averaged about 40 milliseconds longer to run with argument processing using the full library, as compared to no argument processing at all. Most of that time represents the loading of the library classes and other classes used by the library code, so even for applications with many command line parameters defined and many argument values, it's unlikely to be much worse than this. For my command line applications, the additional 40 milliseconds isn't something I'm going to notice. The full library code is available from the link in Resources. It includes several features I've left out of this article, including such niceties as hooks to easily generate a formatted list of parameter flags and descriptions to help supply usage instructions for an application. You're welcome to use the library in your own programs and extend it in any way you find useful. Now that I've covered the basics of Java classes in Part 1, and the principles of the Java Reflection API in Part 2 and Part 3, the rest of this series is going to head off along the less-traveled path of bytecode manipulation. I'll start off easy in Part 4 with a look at the user-friendly Javassist library for working with binary classes. Do you want to try transforming methods, but are reluctant to start programming in bytecode? Javassist may be just the tool to suit your needs. Find out how next month.
Resources
Download the full argument processing library (known as JArgp) from the Sourceforge project site.
Get a historical perspective on the growing capabilities of Java reflection in "Reflection: A new way to discover information about Java classes" (developerWorks, May 1998) by Dan Becker.
Reflection plays an especially important role in working with JavaBeans technology. Learn all about this in "Reflecting, introspecting, and customizing JavaBeans" (developerWorks, February 2000) by Laura Bennett.
Extensive use of reflection can have harmful effects on framework performance. For a graphic example and some related discussions, see Dennis Sosnoski's XML data binding articles: "Data binding, Part 2: Performance" (developerWorks, January 2003) and "Data binding, Part 3: JiBX architecture" (developerWorks, April 2003).
For an in-depth tutorial on using reflection, try Dale Green's The Reflection API trail in the Java Tutorial from Sun.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 3: Applied reflectionhttp://www.ibm.com/developerworks/thankyou/feedback-thankyou.html 10838 07152003
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks developerWorks In this article: Javassist
My IBM
technology >
Java programming dynamics, Part 4: Class transformation with Javassist Using Javassist to transform methods in bytecode
basics
Classworking Javassist
with
Trust in the source, Luke? Looking
> Java
Support & downloads
ahead
Document options
Level: Intermediate
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 16 Sep 2003
Resources
Bored with Java classes that execute just the way the source code was written? Then cheer up, because you're about to find out about twisting classes into shapes never intended by the compiler! In this article, Java consultant Dennis Sosnoski kicks his Java programming dynamics series into high gear with a look at Javassist, the bytecode manipulation library that's the basis for the aspect -oriented programming features being added to the widely used JBoss application server. You'll find out the basics of transforming existing classes with Javassist and see both the power and the limitations of this framework's source code approach to classworking.
About the author Rate this page
Related links Java technology technical library
After covering the basics of the Java class format and runtime access through reflection, it's time to move this series on to more advanced topics. This month I'll start in on the second part of the series, where the Java class information becomes just another form of data structure to be manipulated by applications. I'll call this whole topic area classworking. I'll start classworking coverage with the Javassist bytecode manipulation library. Javassist isn't the only library for working with bytecode, but it does have one feature in particular that makes it a great starting point for experimenting with classworking: you can use Javassist to alter the bytecode of a Java class without actually needing to learn anything about bytecode or the Java virtual machine (JVM) architecture. This is a mixed blessing in some respects -- I don't generally advocate messing with technology you don't understand -- but it certainly makes bytecode manipulation much more accessible than with frameworks where you work at the level of individual instructions.
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
Javassist basics Javassist lets you inspect, edit, and create Java binary classes. The inspection aspect mainly duplicates what's available directly in Java through the Reflection API, but having an alternative way to access this information is useful when you're actually modifying classes rather than just executing them. This is because the JVM design doesn't provide you any access to the raw class data after it's been loaded into the JVM. If you're going to work with classes as data, you need to do so outside of the JVM. Javassist uses the javassist.ClassPool class to track and control the classes you're manipulating. This class works a lot like a JVM classloader, but with the important difference that rather than linking loaded classes for execution as part of your application, the class pool makes loaded classes usable as data through the Javassist API. You can use a default class pool that loads from the JVM search path, or define one that searches your own list of paths. You can even load binary classes directly from byte arrays or streams, and create new classes from scratch. Classes loaded in a class pool are represented by javassist.CtClass instances. As with the standard Java java.lang.Class class, CtClass provides methods for inspecting class data such as fields and methods. That's just the start for CtClass, though, which also defines methods for adding new fields, methods, and constructors to the class, and for altering the class name, superclass, and interfaces. Oddly, Javassist does not provide any way of deleting fields, methods, or constructors from a class. Fields, methods, and constructors are represented by javassist.CtField, javassist.CtMethod, and javassist.CtConstructor instances, respectively. These classes define methods for modifying all aspects of the item represented by the class, including the actual bytecode body of a method or constructor.
Don't miss the rest of this series Part 1, "Classes and class loading" (April 2003) Part 2, "Introducing reflection" (June 2003) Part 3, "Applied reflection" (July 2003) Part 5, "Transforming classes on-the-fly" (February 2004) Part 6, "Aspect-oriented changes with Javassist" (March 2004) Part 7, "Bytecode engineering with BCEL" (April 2004) Part 8, "Replacing reflection with code generation" (June 2004)
The source of all bytecode Javassist lets you completely replace the bytecode body of a method or constructor, or selectively add bytecode at the beginning or end of the existing body (along with a couple of other variations for constructors). Either way, the new bytecode is passed as a Java-like source code statement or block in a String. The Javassist methods effectively compile the source code you provide into Java bytecode, which they then insert into the body of the target method or constructor. The source code accepted by Javassist doesn't exactly match the Java language, but the main difference is just the addition of some special identifiers used to represent the method or constructor parameters, method return value, and other items you may want to use in your inserted code. These special identifiers all start with the $ symbol, so they're not going to interfere with anything you'd otherwise do in your code. There are also some restrictions on what you can do in the source code you pass to Javassist. The first restriction is the actual format, which must be a single statement or block. This isn't much of a restriction for most purposes, because you can put any sequence of statements you want in a block. Here's an example using the special Javassist identifiers for the first two method parameter values to show how this works: { }
System.out.println("Argument 1: " + $1); System.out.println("Argument 2: " + $2);
A more substantial limitation on the source code is that there's no way to refer to local variables declared outside the statement or block being added. This means that if you're adding code at both the start and end of a method, for instance, you generally won't be able to pass information from the code added at the start to the code added at the end. There are ways around this limitation, but the workarounds are messy -- you generally need to find a way to merge the separate code inserts into a single block. Back to top Classworking with Javassist For an example of applying Javassist, I'll use a task I've often handled directly in source code: measuring the time taken to execute a method. This is easy enough to do in the source; you just record the current time at the start of the method, then check the current time again at the end of the method and find the difference between the two values. If you don't have source code, it's normally much more difficult to get this type of timing information. That's where classworking comes in handy -- it lets you make changes like this for any method, without needing source code. Listing 1 shows a (bad) example method that I'll use as a guinea pig for my timing experiments: the buildString method of the StringBuilder class. This method constructs a String of any requested length by doing exactly what any Java performance guru will tell you not to do -- it repeatedly appends a single character to the end of a string in order to create a longer string. Because strings are immutable, this approach means a new string will be constructed each time through the loop, with the data copied from the old string and a single character added at the end. The net effect is that this method will run into more and more overhead as it's used to create longer strings.
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
Listing 1. Method to be timed public class StringBuilder { private String buildString(int length) { String result = ""; for (int i = 0; i < length; i++) { result += (char)(i%26 + 'a'); } return result; }
}
public static void main(String[] argv) { StringBuilder inst = new StringBuilder(); for (int i = 0; i < argv.length; i++) { String result = inst.buildString(Integer.parseInt(argv[i])); System.out.println("Constructed string of length " + result.length()); } }
Adding method timing Because I have the source code available for this method, I'll show you how I would add the timing information directly. This will also serve as the model for what I want to do using Javassist. Listing 2 shows just the buildString() method, with timing added. This doesn't amount to much of a change. The added code just saves the start time to a local variable, then computes the elapsed time at the end of the method and prints it to the console. Listing 2. Method with timing private String buildString(int length) { long start = System.currentTimeMillis(); String result = ""; for (int i = 0; i < length; i++) { result += (char)(i%26 + 'a'); } System.out.println("Call to buildString took " + (System.currentTimeMillis()-start) + " ms."); return result; }
Doing it with Javassist Getting the same effect by using Javassist to manipulate the class bytecode seems like it should be easy. Javassist provides ways to add code at the beginning and end of methods, after all, which is exactly what I did in the source code to add timing information for the method. There's a hitch, though. When I described how Javassist lets you add code, I mentioned that the added code could not reference local variables defined elsewhere in the method. This limitation blocks me from implementing the timing code in Javassist the same way I did in the source code; in that case, I defined a new local variable in the code added at the start and referenced that variable in the code added at the end. So what other approach can I use to get the same effect? Well, I could add a new member field to the class and use that instead of a local variable. That's a smelly kind of solution, though, and suffers from some limitations for general use. Consider what would happen with a recursive method, for instance. Each time the method called itself, the saved start time value from the last call would be overwritten and lost. Fortunately there's a cleaner solution. I can keep the original method code unchanged and just change the method name, then add a new method using the original name. This interceptor method can use the same signature as the original method, including returning the same value. Listing 3 shows what a source code version of this approach would look like: Listing 3. Adding an interceptor method in the source
private String buildString$impl(int length) { String result = ""; for (int i = 0; i < length; i++) { result += (char)(i%26 + 'a'); } return result; } private String buildString(int length) { long start = System.currentTimeMillis(); String result = buildString$impl(length); System.out.println("Call to buildString took " + (System.currentTimeMillis()-start) + " ms."); return result; }
This approach of using an interceptor method works well with Javassist. Because the entire body of the method is a single block, I can define and use local variables within the body without any problems. Generating the source code for the interception method is also easy -- it only needs a few substitutions to work for any possible method.
Running the interception Implementing the code to add method timing uses some of the Javassist APIs described in Javassist basics. Listing 4 shows this code, in the form of an application that takes a pair of command-line arguments giving the class name and method name to be timed. The main() method body just finds the class information and then passes it to the addTiming() method to handle the actual modifications. The addTiming() method first renames the existing method by appending "$impl" to the end of the name, then creates a copy of the method using the original name. It then replaces the body of the copied method with timing code wrapping a call to the renamed original method. Listing 4. Adding the interceptor method with Javassist public class JassistTiming { public static void main(String[] argv) { if (argv.length == 2) { try { // start by getting the class file and method CtClass clas = ClassPool.getDefault().get(argv[0]); if (clas == null) { System.err.println("Class " + argv[0] + " not found"); } else { // add timing interceptor to the class addTiming(clas, argv[1]); clas.writeFile(); System.out.println("Added timing to method " + argv[0] + "." + argv[1]); } } catch (CannotCompileException ex) { ex.printStackTrace(); } catch (NotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); }
}
} else { System.out.println("Usage: JassistTiming class method-name"); }
private static void addTiming(CtClass clas, String mname) throws NotFoundException, CannotCompileException { // get the method information (throws exception if method with // given name is not declared directly by this class, returns // arbitrary choice if more than one with the given name) CtMethod mold = clas.getDeclaredMethod(mname); // rename old method to synthetic name, then duplicate the // method with original name for use as interceptor String nname = mname+"$impl"; mold.setName(nname); CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null); // start the body text generation by saving the start time // to a local variable, then call the timed method; the // actual code generated needs to depend on whether the // timed method returns a value String type = mold.getReturnType().getName(); StringBuffer body = new StringBuffer(); body.append("{\nlong start = System.currentTimeMillis();\n"); if (!"void".equals(type)) { body.append(type + " result = "); } body.append(nname + "($$);\n"); // finish body text generation with call to print the timing // information, and return saved value (if not void) body.append("System.out.println(\"Call to method " + mname + " took \" +\n (System.currentTimeMillis()-start) + " + "\" ms.\");\n"); if (!"void".equals(type)) { body.append("return result;\n"); } body.append("}"); // replace the body of the interceptor method with generated // code block and add it to class mnew.setBody(body.toString()); clas.addMethod(mnew);
}
}
// print the generated code block just to show what was done System.out.println("Interceptor method body:"); System.out.println(body.toString());
The construction of the interceptor method body uses a java.lang.StringBuffer to accumulate the body text (showing the proper way to handle String construction, as opposed to the approach used in StringBuilder). This varies depending on whether the original method returns a value or not. If it does return a value, the constructed code saves that value in a local variable so it can be returned at the end of the interceptor method. If the original method is of type void, there's nothing to be saved and nothing to be returned from the interceptor method. The actual body text looks just like standard Java code except for the call to the (renamed) original method. This is the body.append(nname + "($$);\n"); line in the code, where nname is the modified name for the original method. The $$ identifier used in the call is the way Javassist represents the list of parameters to the method under construction. By using this identifier in the call to the original method, all the arguments supplied in the call to the interceptor method are passed on to the original method. Listing 5 shows the results of first running the StringBuilder program in unmodified form, then running the JassistTiming program to add timing information, and finally running the StringBuilder program after it's been modified. You can see how the StringBuilder run after modification reports execution times, and how the times increase much faster than the length of the constructed string because of the inefficient string construction code. Listing 5. Running the programs [dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Constructed string of length 1000 Constructed string of length 2000 Constructed string of length 4000 Constructed string of length 8000 Constructed string of length 16000 [dennis]$ java -cp javassist.jar:. JassistTiming StringBuilder buildString Interceptor method body: { long start = System.currentTimeMillis(); java.lang.String result = buildString$impl($$); System.out.println("Call to method buildString took " + (System.currentTimeMillis()-start) + " ms."); return result; } Added timing to method StringBuilder.buildString [dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Call to method buildString took 37 ms. Constructed string of length 1000 Call to method buildString took 59 ms. Constructed string of length 2000 Call to method buildString took 181 ms. Constructed string of length 4000 Call to method buildString took 863 ms. Constructed string of length 8000 Call to method buildString took 4154 ms. Constructed string of length 16000
Back to top Trust in the source, Luke? Javassist does a great job of making classworking easy by letting you work with source code rather than actual bytecode instruction lists. But this ease of use comes with some drawbacks. As I mentioned back in The source of all bytecode, the source code used by Javassist is not exactly the Java language. Besides recognizing special identifiers in the code, Javassist implements much looser compile-time checks on the code than required by the Java language specification. Because of this, it will generate bytecode from the source in ways that may have surprising results if you're not careful. As an example, Listing 6 shows what happens when I change the type of the local variable used for the method start time in the interceptor code from long to int. Javassist accepts the source code and converts it into valid bytecode, but the resulting times are garbage. If you tried compiling this assignment directly in a Java program, you'd get a compile error because it violates one of the rules of the Java language: a narrowing assignment requires a cast. Listing 6. Storing a long in an int [dennis]$ java -cp javassist.jar:. JassistTiming StringBuilder buildString
Interceptor method body: { int start = System.currentTimeMillis(); java.lang.String result = buildString$impl($$); System.out.println("Call to method buildString took " + (System.currentTimeMillis()-start) + " ms."); return result; } Added timing to method StringBuilder.buildString [dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Call to method buildString took 1060856922184 ms. Constructed string of length 1000 Call to method buildString took 1060856922172 ms. Constructed string of length 2000 Call to method buildString took 1060856922382 ms. Constructed string of length 4000 Call to method buildString took 1060856922809 ms. Constructed string of length 8000 Call to method buildString took 1060856926253 ms. Constructed string of length 16000
Depending on what you do in the source code, you can even get Javassist to generate invalid bytecode. Listing 7 shows an example of this, where I've patched the JassistTiming code to always treat the timed method as returning an int value. Javassist again accepts the source code without complaint, but the resulting bytecode fails verification when I try to execute it. Listing 7. Storing a String in an int [dennis]$ java -cp javassist.jar:. JassistTiming StringBuilder buildString Interceptor method body: { long start = System.currentTimeMillis(); int result = buildString$impl($$); System.out.println("Call to method buildString took " + (System.currentTimeMillis()-start) + " ms."); return result; } Added timing to method StringBuilder.buildString [dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Exception in thread "main" java.lang.VerifyError: (class: StringBuilder, method: buildString signature: (I)Ljava/lang/String;) Expecting to find integer on stack
This type of issue isn't a problem as long as you're careful with the source code you supply to Javassist. It's important to realize that Javassist won't necessarily catch any errors in the code, though, and that the results of an error may be difficult to predict. Back to top Looking ahead There's a lot more to Javassist than what we've covered in this article. Next month, we'll delve a little deeper with a look at some of the special hooks Javassist provides for bulk modification of classes and for on -thefly modification as classes are loaded at runtime. These are the features that make Javassist a great tool for implementing aspects in your applications, so make sure you catch the follow-up for the full story on this powerful tool.
Resources
Javassist was originated by Shigeru Chiba of the Department of Mathematics and Computing Sciences, Tokyo Institute of Technology. It's recently joined the open source JBoss application server project where it's the basis for the addition of new aspect -oriented programming features. Javassist is distributed under the Mozilla Public License (MPL) and the GNU Lesser General Public License (LGPL) open source licenses.
Learn more about the Java bytecode design in "Java bytecode: Understanding bytecode makes you a better programmer" (developerWorks, July 2001).
Want to find out more about aspect-oriented programming? Try "Improve modularity with aspect-oriented programming" (developerWorks, January 2002) for an overview of working with the AspectJ language.
The open source Jikes Project provides a very fast and highly compliant compiler for the Java programming language. Use it to generate your bytecode the old fashioned way -- from Java source code.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 4: Class transformation http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html with Javassist 10868 09162003
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks developerWorks In this article: Loading Runtime
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 5: Transforming classes on-the-fly Learn how to modify classes as they're being loaded with Javassist
zone timing
Up next Download Resources
Document options
Level: Intermediate
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 03 Feb 2004
About the author
Sample code
After a short hiatus, Dennis Sosnoski is back with Part 5 of his Java programming dynamics series. You've seen how to write a program that transforms Java class files to change code behavior. In this installment, Dennis shows you how to combine transformation with the actual loading of classes using the Javassist framework, for flexible "just-in-time" aspect-oriented feature handling. This approach lets you decide what you want to change at runtime, and potentially make different modifications each time you run a program. Along the way you'll also get a deeper look at the general issues of classloading into the JVM.
Rate this page
Related links Java technology technical library
In Part 4, "Class transformations with Javassist," you learned how to use the Javassist framework to transform Java class files generated by the compiler, writing the modified class files back out. This type of class file transform step is great for making persistent changes, but not necessarily convenient when you want to make different changes each time you execute your application. For such transient changes, an approach that works when you actually start up your application is much better. The JVM architecture gives us a convenient way of doing this -- by working with the classloader implementation. Using classloader hooks, you can intercept the process of loading classes into the JVM and transform the class representations before they're actually loaded. To illustrate how this works, I'm first going to demonstrate intercepting the classloading directly, then show how Javassist provides a convenient shortcut that you can use in your applications. Along the way I'll make use of pieces from the prior articles in this series. Loading zone Normally you run a Java application by specifying the main class as a parameter to the JVM. This works fine for standard operations, but doesn't give you any way of hooking into the classloading process in time to be useful for most applications. As I discussed in Part 1 "Classes and classloading," many classes are loaded before your main class even begins to execute. Intercepting the loading of these classes requires a level of indirection in the execution of the program. Fortunately, it's pretty easy to emulate the work done by the JVM in running the main class of your application. All you need to do is use reflection (as covered in Part 2) to first find the static main() method in the specified class, then call it with the desired command line arguments. Listing 1 gives sample code to do this (I've left out the imports and exceptions to keep it short):
Don't miss the rest of this series Part 1, "Classes and class loading" (April 2003)
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
Part 2, "Introducing reflection" (June 2003) Part 3, "Applied reflection" (July 2003) Part 4, "Class transformation with Javassist " (September 2003) Part 6, "Aspect-oriented changes with Javassist " (March 2004) Part 7, "Bytecode engineering with BCEL" (April 2004) Part 8, "Replacing reflection with code generation" (June 2004)
Listing 1. Java application runner public class Run { public static void main(String[] args) { if (args.length >= 1) { try { // load the target class to be run Class clas = Run.class.getClassLoader(). loadClass(args[0]); // invoke "main" method of target class Class[] ptypes = new Class[] { args.getClass() }; Method main = clas.getDeclaredMethod("main", ptypes); String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); main.invoke(null, new Object[] { pargs }); } catch ... }
}
}
} else { System.out.println ("Usage: Run main-class args..."); }
To run your Java application using this class, you just need to name it as the target for the java command, following it with the main class for your application and any arguments you want passed to your application. In other words, if the command you use for launching your Java application normally is: java test.Test arg1 arg2 arg3
You'd instead launch it using the Run class with the command: java Run test.Test arg1 arg2 arg3
Intercepting classloading Just on its own, the little Run class from Listing 1 isn't very useful. To accomplish my goal of intercepting the classloading process we need to go a step further, by defining and using our own classloader for the application classes. As we discussed in Part 1, classloaders use a tree-structured hierarchy. Each classloader (except the root classloader used by the JVM for core Java classes) has a parent classloader. Classloaders are supposed to check with their parent classloader before loading a class on their own, in order to prevent conflicts that can arise when the same class is loaded by more than one classloader in a hierarchy. This process of checking with the parent first is called delegation -- the classloaders delegate responsibility for loading a class to the classloader closest to the root that has access to that class information.
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
When the Run program from Listing 1 begins execution, it's already been loaded by the default System classloader for the JVM (the one that works off the classpath you define). To comply with the delegation rule for classloading, we'll need to make our classloader a true replacement for the System classloader, using all the same classpath information and delegating to the same parent. Fortunately, the java.net.URLClassLoader class used by current JVMs for the System classloader implementation provides an easy way to retrieve the classpath information, using the getURLs() method. To write our classloader, we can just subclass java.net.URLClassLoader, and initialize the base class to use the same classpath and parent classloader as the System classloader that loads the main class. Listing 2 gives the actual implementation of this approach: Listing 2. A verbose classloader public class VerboseLoader extends URLClassLoader { protected VerboseLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } public Class loadClass(String name) throws ClassNotFoundException { System.out.println("loadClass: " + name); return super.loadClass(name); } protected Class findClass(String name) throws ClassNotFoundException { Class clas = super.findClass(name); System.out.println("findclass: loaded " + name + " from this loader"); return clas; } public static void main(String[] args) { if (args.length >= 1) { try { // get paths to be used for loading ClassLoader base = ClassLoader.getSystemClassLoader(); URL[] urls; if (base instanceof URLClassLoader) { urls = ((URLClassLoader)base).getURLs(); } else { urls = new URL[] { new File(".").toURI().toURL() }; } // list the paths actually being used System.out.println("Loading from paths:"); for (int i = 0; i < urls.length; i++) { System.out.println(" " + urls[i]); } // load target class using custom class loader VerboseLoader loader = new VerboseLoader(urls, base.getParent()); Class clas = loader.loadClass(args[0]);
// invoke "main" method of target class Class[] ptypes = new Class[] { args.getClass() }; Method main = clas.getDeclaredMethod("main", ptypes); String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); Thread.currentThread(). setContextClassLoader(loader); main.invoke(null, new Object[] { pargs }); } catch ... }
}
}
} else { System.out.println ("Usage: VerboseLoader main-class args..."); }
We've subclassed java.net.URLClassLoader with our own VerboseLoader class that lists out all the classes being loaded, noting which ones are loaded by this loader instance (rather than a delegation parent classloader). Here again I've left out the imports and exceptions to keep the code concise. The first two methods in the VerboseLoader class, loadClass() and findClass(), are overrides of standard classloader methods. The loadClass() method is called for each class requested from the classloader. In this case, we have it just print a message to the console and then call the base class version for actual handling. The base class method implements the standard classloader delegation behavior, first checking if the parent classloader can load the requested class, and only trying to load the class directly using the protected findClass() method if the parent classloader fails. For the VerboseLoader implementation of findClass(), we first call the overridden base class implementation, then print out a message if the call succeeds (returns without throwing an exception). The main() method of VerboseLoader either gets the list of classpath URLs from the loader used for the containing class or, if used with a loader that's not an instance of URLClassLoader, just uses the current directory as the only classpath entry. Either way, it lists out the paths actually being used, then creates an instance of the VerboseLoader class and uses it to load the target class named on the command line. The rest of the logic, to find and call the main() method of the target class, is the same as the Listing 1 Run code. Listing 3 shows an example of the VerboseLoader command line and output, which is used to call the Run application from Listing 1: Listing 3. Example output from Listing 2 program [dennis]$ java VerboseLoader Run Loading from paths: file:/home/dennis/writing/articles/devworks/dynamic/code5/ loadClass: Run loadClass: java.lang.Object findclass: loaded Run from this loader loadClass: java.lang.Throwable loadClass: java.lang.reflect.InvocationTargetException loadClass: java.lang.IllegalAccessException loadClass: java.lang.IllegalArgumentException loadClass: java.lang.NoSuchMethodException loadClass: java.lang.ClassNotFoundException loadClass: java.lang.NoClassDefFoundError loadClass: java.lang.Class loadClass: java.lang.String loadClass: java.lang.System loadClass: java.io.PrintStream Usage: Run main-class args...
In this case, the only class loaded directly by the VerboseLoader is the Run class. All the other classes used by the Run class are core Java classes, which are loaded by delegation through the parent classloader. Most -- if not all -- of these core Java classes will actually have been loaded during the start up of the VerboseLoader application itself, so the parent classloader will just return a reference to the previously created java.lang.Class instance.
Javassist intercepts VerboseClassloader from Listing 2 shows the basics of intercepting classloading. To modify the classes as they're being loaded we could take this further, adding code to the findClass() method to access the binary class file as a resource and then working with the binary data. Javassist actually includes the code to do this type of interception directly, so rather than taking this example further, we'll see instead how to use the Javassist implementation. Intercepting classloading with Javassist builds on the same javassist.ClassPool class we worked with in Part 4. In that article, we requested a class by name directly from the ClassPool, getting back the Javassist representation of the class in the form of a javassist.CtClass instance. That's not the only way to use a ClassPool, though -- Javassist also provides a classloader that uses the ClassPool as its source of class data, in the form of the javassist.Loader class. To let you work with the classes as they're being loaded, the ClassPool uses an Observer pattern. You can pass an instance of the expected observer interface, javassist.Translator, to the constructor of the ClassPool. Each time a new class is requested from the ClassPool it calls the onWrite() method of the observer, which can modify the class representation before it's delivered by the ClassPool. The javassist.Loader class includes a convenient run() method that loads a target class and calls the main() method of that class with a supplied array of arguments (as in the Listing 1 code). Listing 4 demonstrates using the Javassist classes and this method to load and run a target application class. The simple javassist.Translator observer implementation in this case just prints out a message about the class being requested. Listing 4. Javassist application runner public class JavassistRun { public static void main(String[] args) { if (args.length >= 1) { try { // set up class loader with translator Translator xlat = new VerboseTranslator(); ClassPool pool = ClassPool.getDefault(xlat); Loader loader = new Loader(pool); // invoke "main" method of target class String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); loader.run(args[0], pargs); } catch ... }
}
} else { System.out.println ("Usage: JavassistRun main-class args..."); }
public static class VerboseTranslator implements Translator { public void start(ClassPool pool) {}
}
}
public void onWrite(ClassPool pool, String cname) { System.out.println("onWrite called for " + cname); }
Here's an example of the JavassistRun command line and output, using it to call the Run application from Listing 1: [dennis]$java -cp .:javassist.jar JavassistRun Run onWrite called for Run Usage: Run main-class args...
Back to top Runtime timing The method timing modification we examined in Part 4 can be a useful tool for isolating performance issues, but it really needs a more flexible interface. In that article, we just passed the class and method name as command-line parameters to my program, which loaded the binary class file, added the timing code, then wrote the class back out. For this article, we'll convert the code to use a load -time modification approach, and to support pattern-matching for specifying the classes and methods to be timed. Changing the code to handle modifications as the classes are loaded is easy. Building off the javassist.Translator code from Listing 4, we can just call the method that adds the timing information from onWrite() when the class name being written matches the target class name. Listing 5 shows this (without all the details of addTiming() -- see Part 4 for this). Listing 5. Adding timing code at load-time public class TranslateTiming { private static void addTiming(CtClass clas, String mname) throws NotFoundException, CannotCompileException { ... } public static void main(String[] args) { if (args.length >= 3) { try { // set up class loader with translator Translator xlat = new SimpleTranslator(args[0], args[1]); ClassPool pool = ClassPool.getDefault(xlat); Loader loader = new Loader(pool); // invoke "main" method of target class String[] pargs = new String[args.length-3]; System.arraycopy(args, 3, pargs, 0, pargs.length); loader.run(args[2], pargs); } catch (Throwable ex) { ex.printStackTrace();
}
}
} else { System.out.println("Usage: TranslateTiming" + " class-name method-mname main-class args..."); }
public static class SimpleTranslator implements Translator { private String m_className; private String m_methodName; public SimpleTranslator(String cname, String mname) { m_className = cname; m_methodName = mname; } public void start(ClassPool pool) {}
}
}
public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { if (cname.equals(m_className)) { CtClass clas = pool.get(cname); addTiming(clas, m_methodName); } }
Pattern methods Besides making the method timing code work at load-time, as shown in Listing 5, it would be nice to add flexibility in specifying the method(s) to be timed. I started out implementing this using the regular expression matching support in the Java 1.4 java.util.regex package, then realized it wasn't really giving me the kind of flexibility I wanted. The problem was that the kind of patterns that are meaningful to me for selecting classes and methods to be modified don't fit well into the regular expression model. So what kind of patterns are meaningful for selecting classes and methods? What I wanted was the ability to use any of several characteristics of the class and method in the patterns, including the actual class and method name, the return type, and the call parameter type(s). On the other hand, I didn't need really flexible comparisons on the names and types -- a simple equals comparison handled most of the cases I was interested in, and adding basic wildcards to the comparisons took care of the rest. The easiest approach to handling this was just to make the patterns look like standard Java method declarations, with a few extensions. For some examples of this approach, here are several patterns that will match the String buildString(int) method of the test.StringBuilder class: java.lang.String test.StringBuilder.buildString(int) test.StringBuilder.buildString(int) *buildString(int) *buildString
The general pattern of these patterns is first an optional return type (with exact text), then the combined class and method name pattern (with "*" wildcard characters), and finally the list of parameter type(s) (with exact text). If the return type is present, it must be separated from the method name match by a space, while the list of parameters follows the method name match. To make the parameter match flexible, I set it up to work in two ways. If the parameters are given as a list surrounded by parentheses, they must exactly match the method parameters. If they're instead surrounded by square braces ("[]"), the types listed must all be present as parameters of a matching method, but the method may use them in any order and may also use additional parameters. So *buildString(int, java.lang.String) matches any method with a name ending in "buildString" and taking exactly two parameters, an int and a String, in that order. *buildString[int,java.lang.String] matches methods with the same names, but taking two or more parameters, one of which is an int and another a java.lang.String. Listing 6 gives an abbreviated version of the javassist.Translator subclass I wrote to handle these patterns. The actual matching code isn't really relevant to this article, but it's included in the download file (see Resources) if you'd like to look it over or use it yourself. The main program class that uses this TimingTranslator is BatchTiming, also included in the download file. Listing 6. Pattern-matching translator public class TimingTranslator implements Translator { public TimingTranslator(String pattern) { // build matching structures for supplied pattern ... } private boolean matchType(CtMethod meth) { ... } private boolean matchParameters(CtMethod meth) { ... } private boolean matchName(CtMethod meth) { ... } private void addTiming(CtMethod meth) { ... } public void start(ClassPool pool) {} public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { // loop through all methods declared in class CtClass clas = pool.get(cname); CtMethod[] meths = clas.getDeclaredMethods(); for (int i = 0; i < meths.length; i++) { // check if method matches full pattern CtMethod meth = meths[i]; if (matchType(meth) && matchParameters(meth) && matchName(meth)) {
}
}
}
}
// handle the actual timing modification addTiming(meth);
Back to top Up next In the last two articles, you've now seen how to use Javassist for handling basic transformations. For the next article, we'll look into the advanced features of this framework that provide search -and-replace techniques for editing bytecode. These features make systematic changes to program behavior easy, including changes such as intercepting all calls to a method or all accesses of a field. They're the key to understanding why Javassist is a great framework for aspect-oriented support in Java programs. Check back next month to see how you can use Javassist to unlock aspects in your applications.
Back to top Download Name
Size
j-dyn0203 -source.zip
Download method 318 KB
HTTP
Information about download methods
Resources
Javassist was originated by Shigeru Chiba of the Department of Mathematics and Computing Sciences, Tokyo Institute of Technology. It has recently joined the open source JBoss application server project where it's the basis for the addition of new aspect -oriented programming features. Download the Javassist current release from the JBoss project Files page on Sourceforge.
Learn all the details of building your own special class loaders with the tutorial, "Understanding the Java ClassLoader" (developerWorks, April 2001)
Find out more about the Java bytecode design in "Java bytecode: Understanding bytecode makes you a better programmer" (developerWorks, July 2001) by Peter Haggar.
Want to find out more about aspect-oriented programming? Try "Improve modularity with aspect-oriented programming" (developerWorks, January 2002) by Nicholas Lesiecki for an overview of working with the AspectJ language.
The open source Jikes Project provides a very fast and highly compliant compiler for the Java programming language. Use it to generate your bytecode the old fashioned way -- from Java source code.
Browse for books on these and other technical topics.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional
software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 5: Transforming classes http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html on-the-fly 10908 02032004
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 6: Aspect -oriented changes with Javassist
developerWorks In this article:
Using Javassist for bytecode search-and-replace transformations
Handling bytecode modifications Code
conversion
Class easy
mutilation
Wrapping
up
Document options
Level: Intermediate made
Javassist
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 02 Mar 2004
Download
Sample code
Java consultant Dennis Sosnoski saves the best for last in his three-part coverage of the Javassist framework. This time he shows how the Javassist search-and-replace support makes editing Java bytecode practically as easy as a text editor's Replace All command. Want to report all writes to a particular field or patch in a change to a parameter passed in a method call? Javassist makes it easy, and Dennis shows you how.
Resources About the author Rate this page
Related links Java technology technical library
Part 4 and Part 5 of this series covered how you can use Javassist for localized changes to binary classes. This time you'll learn about an even more powerful way of using the framework, taking advantage of Javassist's support for finding all uses of a particular method or field in the bytecode. This feature is at least as important to Javassist's power as its support for a source code-like way of specifying bytecode. Support for selective replacement of operations is also the feature that makes Javassist an excellent tool for adding aspect-oriented programming features to standard Java code. In Part 5 you saw how Javassist lets you intercept the classloading process -- and even make changes to the binary class representations as they're being loaded. The systematic bytecode transformations I'm covering in this article can be used for either static class file transformations or runtime interception, but they're especially useful when used at runtime.
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
Handling bytecode modifications Javassist provides two separate ways to handle systematic bytecode modifications. The first technique, using the javassist.CodeConverter class, is a little simpler to use but a lot more limited in terms of what you can accomplish. The second technique uses custom subclasses of the javassist.ExprEditor class. This takes a little more work, but the added flexibility more than makes up for the effort. I'll look at examples of both approaches in this article.
Back to top Code conversion The first Javassist technique for systematic bytecode modification uses the javassist.CodeConverter class. To take advantage of this technique, you just create an instance of the CodeConverter class and configure it with one or more transformation actions. Each transformation is configured using a method call that identifies the type of transformation. These fall into three categories: method call transformations, field access transformations, and a new object transformation. Listing 1 gives an example of using a method call transformation. In this case, the transformation just adds a notification that a method is being called. In the code, I first get the javassist.ClassPool instance I'm going to use throughout, configuring it to work with a translator (as previously seen in Part 5) Then, I access a pair of method definitions through the ClassPool. The first method definition is for the "set"-style method to be monitored (with the class and method name from command line arguments), the second is for the reportSet() method within the TranslateConvert class that will report a call to the first method.
Don't miss the rest of this series Part 1, "Classes and class loading" (April 2003)
Once I have the method information, I can use the CodeConverter insertBeforeMethod() to configure a transformation that adds a call to the reporting method before each call to the set method. Then all that needs to be done is to apply this converter to one or more classes. In the Listing 1 code, I do this within the onWrite() method of the ConverterTranslator inner class with the call to the instrument() method of the class object. This will automatically apply the transformation to every class loaded from the ClassPool instance.
Part 4, "Class transformation with Javassist" (September 2003)
Listing 1. Using CodeConverter
Part 2, "Introducing reflection" (June 2003) Part 3, "Applied reflection" (July 2003)
Part 5, "Transforming classes on-the-fly" (February 2004) Part 7, "Bytecode engineering with BCEL" (April 2004) Part 8, "Replacing reflection with code generation" (June 2004)
public class TranslateConvert { public static void main(String[] args) { if (args.length >= 3) { try { // set up class loader with translator ConverterTranslator xlat = new ConverterTranslator(); ClassPool pool = ClassPool.getDefault(xlat); CodeConverter convert = new CodeConverter(); CtMethod smeth = pool.get(args[0]). getDeclaredMethod(args[1]); CtMethod pmeth = pool.get("TranslateConvert"). getDeclaredMethod("reportSet"); convert.insertBeforeMethod(smeth, pmeth); xlat.setConverter(convert); Loader loader = new Loader(pool); // invoke "main" method of application class String[] pargs = new String[args.length-3]; System.arraycopy(args, 3, pargs, 0, pargs.length); loader.run(args[2], pargs); } catch ... }
}
} else { System.out.println("Usage: TranslateConvert " + "clas-name set-name main-class args..."); }
public static void reportSet(Bean target, String value) { System.out.println("Call to set value " + value); } public static class ConverterTranslator implements Translator { private CodeConverter m_converter; private void setConverter(CodeConverter convert) { m_converter = convert; } public void start(ClassPool pool) {}
}
}
public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { CtClass clas = pool.get(cname); clas.instrument(m_converter); }
That's a fairly complex operation to configure, but once it's set up it works painlessly. Listing 2 gives a code sample to use as a test case. Here the Bean provides a test object with bean-like get and set methods, which are used by the BeanTest program to access the values. Listing 2. A bean tester public class Bean { private String m_a; private String m_b; public Bean() {} public Bean(String a, String b) { m_a = a; m_b = b; } public String getA() { return m_a; } public String getB() { return m_b; } public void setA(String string) { m_a = string; }
}
public void setB(String string) { m_b = string; }
public class BeanTest { private Bean m_bean; private BeanTest() { m_bean = new Bean("originalA", "originalB"); } private void print() {
}
System.out.println("Bean values are " + m_bean.getA() + " and " + m_bean.getB());
private void changeValues(String lead) { m_bean.setA(lead + "A"); m_bean.setB(lead + "B"); }
}
public static void main(String[] args) { BeanTest inst = new BeanTest(); inst.print(); inst.changeValues("new"); inst.print(); }
Here's the output if I just run the Listing 2 BeanTest program directly: [dennis]$ java -cp . BeanTest Bean values are originalA and originalB Bean values are newA and newB
If I run it using the Listing 1 TranslateConvert program and specify one of the set methods to monitor, the output will look like this: [dennis]$ java -cp .:javassist.jar TranslateConvert Bean setA BeanTest Bean values are originalA and originalB Call to set value newA Bean values are newA and newB
Everything works the same as before, but now there's a notification that the selected method is being called during the execution of the program. In this case, the same effect could easily have been achieved in other ways, for instance by adding code to the actual set method body using the techniques from Part 4. The difference here is that by adding the code at the point of use, I gain flexibility. For instance, I could easily modify the TranslateConvert.ConverterTranslator onWrite() method to check the class name being loaded, and only transform classes that are included in a list I'm interested in observing. Adding code directly to the set method body wouldn't allow such selective monitoring. The flexibility provided by systematic bytecode transformations is what makes them a powerful tool for implementing aspect-oriented extensions for standard Java code. You'll see more of this in the remainder of this article.
Conversion limits The transformations handled by CodeConverter are useful, but limited. For instance, if you want to call a monitoring method before or after a target method is called, that monitoring method must be defined as static void and must take a single parameter of the class of the target method, followed by the same number and types of parameters as the target method. When the monitoring method is called, it's passed the actual target object as its first argument, followed by all the arguments for the target method.
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
This rigid structure means that the monitoring methods need to be precisely matched to the target class and method. By way of example, suppose I change the definition of the reportSet() method in Listing 1 to take a generic java.lang.Object parameter in hopes of making it usable with different target classes: public static void reportSet(Object target, String value) { System.out.println("Call to set value " + value); }
This compiles fine, but when I try it out it breaks: [dennis]$ java -cp .:javassist.jar TranslateConvert Bean setA BeanTest Bean values are A and B java.lang.NoSuchMethodError: TranslateConvert.reportSet(LBean;Ljava/lang/String;)V at BeanTest.changeValues(BeanTest.java:17) at BeanTest.main(BeanTest.java:23) at ...
There are ways to get around this limitation. One solution would be to actually generate a custom monitoring method at runtime that matches the target method. That's a lot of effort to go through, though, and I'm not even going to try it for this article. Fortunately, Javassist also provides another way of handling systematic bytecode transformations. This other way, using javassist.ExprEditor, is both more flexible and more powerful than CodeConverter. Back to top Class mutilation made easy Bytecode transformations with javassist.ExprEditor build on the same principles as those done using CodeConverter. The ExprEditor approach is perhaps a little harder to understand, though, so I'll start off by demonstrating the basic principles and then add in the actual transformations. Listing 3 shows how you can use ExprEditor to report the basic items that are potential targets for aspect-oriented transformations. Here I subclass ExprEditor in my own VerboseEditor, overriding three of the base class methods -- all named edit(), but with different parameter types. As in the Listing 1 code, I actually use this subclass from within the onWrite() method of the DissectionTranslator inner class, passing an instance in the call to the instrument() method of the class object for every class loaded from our ClassPool instance. Listing 3. A class dissector public class Dissect { public static void main(String[] args) { if (args.length >= 1) { try { // set up class loader with translator Translator xlat = new DissectionTranslator(); ClassPool pool = ClassPool.getDefault(xlat); Loader loader = new Loader(pool); // invoke the "main" method of the application class String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); loader.run(args[0], pargs); } catch (Throwable ex) { ex.printStackTrace(); }
}
} else { System.out.println ("Usage: Dissect main-class args..."); }
public static class DissectionTranslator implements Translator { public void start(ClassPool pool) {}
}
public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { System.out.println("Dissecting class " + cname); CtClass clas = pool.get(cname); clas.instrument(new VerboseEditor()); }
public static class VerboseEditor extends ExprEditor { private String from(Expr expr) { CtBehavior source = expr.where(); return " in " + source.getName() + "(" + expr.getFileName() + ":" + expr.getLineNumber() + ")"; } public void edit(FieldAccess arg) { String dir = arg.isReader() ? "read" : "write"; System.out.println(" " + dir + " of " + arg.getClassName() + "." + arg.getFieldName() + from(arg)); } public void edit(MethodCall arg) { System.out.println(" call to " + arg.getClassName() + "." + arg.getMethodName() + from(arg)); }
}
}
public void edit(NewExpr arg) { System.out.println(" new " + arg.getClassName() + from(arg)); }
Listing 4 shows the output generated by running the Listing 4 Dissect program on the Listing 2 BeanTest program. This gives the detailed breakdown of what's being done within each method of each class loaded, listing all method calls, field accesses, and new object creations. Listing 4. BeanTest dissected [dennis]$ java -cp .:javassist.jar Dissect BeanTest Dissecting class BeanTest new Bean in BeanTest(BeanTest.java:7) write of BeanTest.m_bean in BeanTest(BeanTest.java:7) read of java.lang.System.out in print(BeanTest.java:11) new java.lang.StringBuffer in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11)
read of BeanTest.m_bean in print(BeanTest.java:11) call to Bean.getA in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) read of BeanTest.m_bean in print(BeanTest.java:11) call to Bean.getB in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) call to java.lang.StringBuffer.toString in print(BeanTest.java:11) call to java.io.PrintStream.println in print(BeanTest.java:11) read of BeanTest.m_bean in changeValues(BeanTest.java:16) new java.lang.StringBuffer in changeValues(BeanTest.java:16) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:16) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:16) call to java.lang.StringBuffer.toString in changeValues(BeanTest.java:16) call to Bean.setA in changeValues(BeanTest.java:16) read of BeanTest.m_bean in changeValues(BeanTest.java:17) new java.lang.StringBuffer in changeValues(BeanTest.java:17) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:17) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:17) call to java.lang.StringBuffer.toString in changeValues(BeanTest.java:17) call to Bean.setB in changeValues(BeanTest.java:17) new BeanTest in main(BeanTest.java:21) call to BeanTest.print in main(BeanTest.java:22) call to BeanTest.changeValues in main(BeanTest.java:23) call to BeanTest.print in main(BeanTest.java:24) Dissecting class Bean write of Bean.m_a in Bean(Bean.java:10) write of Bean.m_b in Bean(Bean.java:11) read of Bean.m_a in getA(Bean.java:15) read of Bean.m_b in getB(Bean.java:19) write of Bean.m_a in setA(Bean.java:23) write of Bean.m_b in setB(Bean.java:27) Bean values are originalA and originalB Bean values are newA and newB
I could easily add support for reporting casts, instanceof tests, and catch blocks by implementing the appropriate methods in VerboseEditor. But just listing out information about these component items gets boring, so let's instead look into actually modifying the items.
Vivisection in progress The Listing 4 dissection of classes lists basic component operations. It's easy to see how these would be useful to work with when implementing aspect -oriented features. For example, a logger that reports all write accesses to selected fields would be a useful aspect to apply in many applications. That is the sort of thing I've been promising to show you how to do, after all. Fortunately for the theme of this article, ExprEditor not only lets me know what operations are present in the code, it also lets me modify the operations being reported. The parameter types passed in the various ExprEditor.edit() method calls each defines a replace() method. If I pass this method a statement in the usual Javassist source code form (covered in Part 4), that statement will be compiled to bytecode and used to replace the original operation. This makes slicing and dicing your bytecode easy. Listing 5 shows an application of code replacement. Rather than just logging operations, I've chosen here to actually modify the String value being stored to a selected field. In FieldSetEditor, I implement the method signature that matches field accesses. Within this method, I just check two things: that the field name is the one I'm looking for, and that the operation is a store. When I find a match, I replace the original store with one that uses the result of a call to the reverse() method within the actual TranslateEditor application class. The reverse() method just reverses the order of the characters in the original string and prints out a message to show that it has been used. Listing 5. Reversing string sets public class TranslateEditor { public static void main(String[] args) { if (args.length >= 3) { try { // set up class loader with translator EditorTranslator xlat = new EditorTranslator(args[0], new FieldSetEditor(args[1])); ClassPool pool = ClassPool.getDefault(xlat); Loader loader = new Loader(pool); // invoke the "main" method of the application class String[] pargs = new String[args.length-3]; System.arraycopy(args, 3, pargs, 0, pargs.length); loader.run(args[2], pargs); } catch (Throwable ex) { ex.printStackTrace(); }
}
} else { System.out.println("Usage: TranslateEditor clas-name " + "field-name main-class args..."); }
public static String reverse(String value) { int length = value.length(); StringBuffer buff = new StringBuffer(length); for (int i = length-1; i >= 0; i--) { buff.append(value.charAt(i)); } System.out.println("TranslateEditor.reverse returning " + buff); return buff.toString(); } public static class EditorTranslator implements Translator { private String m_className; private ExprEditor m_editor; private EditorTranslator(String cname, ExprEditor editor) { m_className = cname; m_editor = editor; } public void start(ClassPool pool) {}
}
public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { if (cname.equals(m_className)) { CtClass clas = pool.get(cname); clas.instrument(m_editor); } }
public static class FieldSetEditor extends ExprEditor { private String m_fieldName; private FieldSetEditor(String fname) { m_fieldName = fname; }
}
}
public void edit(FieldAccess arg) throws CannotCompileException { if (arg.getFieldName().equals(m_fieldName) && arg.isWriter()) { StringBuffer code = new StringBuffer(); code.append("$0."); code.append(arg.getFieldName()); code.append("=TranslateEditor.reverse($1);"); arg.replace(code.toString()); } }
Here's what happens if I run this on the Listing 2 BeanTest program: [dennis]$ java -cp .:javassist.jar TranslateEditor Bean m_a BeanTest TranslateEditor.reverse returning Alanigiro Bean values are Alanigiro and originalB TranslateEditor.reverse returning Awen Bean values are Awen and newB
I've successfully grafted in a call to the added code at each store into the Bean.m_a field (one in the constructor and one in the set method). I could counteract this effect by implementing a similar modification on the loads from the field, but personally I find the reversed values a lot more interesting than what we started with, so I'll choose to stay with these. Back to top Wrapping up Javassist In this article, you've seen how systematic bytecode transformations can easily be done using Javassist. Combining it with the last two articles, you should have a solid basis for implementing your own aspect oriented transformations of Java applications, either as a separate build step or at runtime. To get a better idea of the power of this approach, you may also want to look at the JBoss Aspect Oriented Programming project (JBossAOP) that's been built around Javassist. JBossAOP uses an XML configuration file to define any of a variety of different operations to be done to your application classes. These include using interceptors on field accesses or method calls, adding mix-in interface implementations to existing classes, and more. JBossAOP is built into the version of the JBoss application server now under development, but is also available as a standalone tool for use with your applications outside of JBoss. Next up for this series is a look at the Byte Code Engineering Library (BCEL), a part of the Apache Software Foundation's Jakarta Project. BCEL is one of the most widely used frameworks for Java classworking. It gives a very different way of working with bytecode from the Javassist approach we've seen in the last three articles, focusing on the individual bytecode instructions rather than the source -level work that's Javassist's strength. Check back for the full details on working at the bytecode assembler level next month.
Back to top Download Name
Size
Download method
HTTP
j-dyn0302 -source.zip
Information about download methods
Resources
Check out the rest of Dennis Sosnoski's Java programming dynamics series.
Download the example code for this article.
Javassist was originated by Shigeru Chiba of the Department of Mathematics and Computing Sciences, Tokyo Institute of Technology. It has recently joined the open source JBoss application server project where it's the basis for the addition of new aspect -oriented programming features. Download the current release of Javassist from the JBoss project Files page on Sourceforge.
Learn more about the Java bytecode design in "Java bytecode: Understanding bytecode makes you a better programmer" (developerWorks, July 2001) by Peter Haggar.
Want to find out more about aspect-oriented programming? Try "Improve modularity with aspect-oriented programming" (developerWorks. January 2002) by Nicholas Lesiecki for an overview of working with the AspectJ language. A more recent article, "AOP banishes the tight-coupling blues" (developerWorks, February 2004) by Andrew Glover shows how one of AOP's functional design concepts -- static crosscutting -- can turn what might be a tangled mass of tightly coupled code into a powerful, extensible enterprise application.
The open source Jikes Project provides a very fast and highly compliant compiler for the Java programming language. Use it to generate your bytecode the old fashioned way -- from Java source code.
Browse for books on these and other technical topics.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 6: Aspect-oriented http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html changes with Javassist 10920 03022004
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks developerWorks In this article:
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 7: Bytecode engineering with BCEL Apache BCEL lets you get to the details of JVM assembler language for classworking
BCEL class access Classworking with BCEL Wrapping up BCEL Up next Download
Document options
Level: Intermediate
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 14 Apr 2004
Resources
Sample code
The Apache Byte Code Engineering Library (BCEL) lets you dig into the bytecode of Java classes. You can use it to transform existing class representations or construct new ones, and because BCEL works at the level of individual JVM instructions, it gives you the utmost power over your code. That power comes with a cost in complexity, though. In this article, Java consultant Dennis Sosnoski gives you the BCEL basics and guides you through an example BCEL application so you can decide for yourself if the power justifies the complexity.
About the author Rate this page
Related links Java technology technical library
In the last three articles of this series, I've shown you how to use the Javassist framework for classworking. This time I'm going to cover a very different approach to bytecode manipulation, using the Apache Byte Code Engineering Library (BCEL). BCEL operates at the level of actual JVM instructions, unlike the source code interface supported by Javassist. The low-level approach makes BCEL very good for when you really want to control every step of the program execution, but it also makes working with BCEL a lot more complex than using Javassist for cases where both will work. I'm going to start out by covering the basic BCEL architecture, then devote most of this article to rebuilding my first Javassist classworking example with BCEL. I'll finish up with a quick look at some of the tools included in the BCEL package and some of the applications developers have built on top of BCEL. BCEL class access
Watch these demos Integrate new tools and architectures into your environment -- fast!
Rate this page Help us improve this content
BCEL gives you all the same basic capabilities as Javassist to inspect, edit, and create Java binary classes. The obvious difference with BCEL is that everything is designed to work at the level of JVM assembler language, rather than the source code interface provided by Javassist. There are some deeper differences under the covers, including the use of two separate hierarchies of components within BCEL -- one for inspecting existing code and the other for creating new code. I'm going to assume you're familiar with Javassist from the previous articles in this series (see the sidebar Don't miss the rest of this series ). I'll therefore concentrate on the differences that are likely to confuse you when you start working with BCEL. As with Javassist, the class inspection aspect of BCEL basically duplicates what's available directly in the Java platform through the Reflection API. This duplication is necessary in a classworking toolkit because you generally don't want to load the classes you're working with until after they've been modified. BCEL provides some basic constant definitions in the org.apache.bcel package, but aside from these definitions all the inspection-related code is in the org.apache.bcel.classfile package. The starting point within this package is the JavaClass class. This class plays about the same role in accessing class information using BCEL as java.lang.Class does when using regular Java reflection. JavaClass defines methods to get the field and method information for the class, as well as structural information about superclass and interfaces. Unlike java.lang.Class, JavaClass also provides access to the internal information for the class, including the constant pool and attributes, and the complete binary class representation as a byte stream. JavaClass instances are usually created by parsing the actual binary class. BCEL provides the org.apache.bcel.Repository class to handle the parsing for you. By default, BCEL parses and caches the representations of classes found in the JVM classpath, getting the actual binary class representations from an org.apache.bcel.util.Repository instance (note the difference in the package name). org.apache.bcel.util.Repository is actually an interface for a source of binary class representations. You can substitute other paths for looking up class files, or other ways of accessing class information, in place of the default source that uses the classpath.
Don't miss the rest of this series Part 1, "Classes and class loading"(April 2003) Part 2, "Introducing reflection" (June 2003) Part 3, "Applied reflection" (July 2003) Part 4, "Class transformation with Javassist" (September 2003) Part 5, "Transforming classes on-the-fly" (February 2004) Part 6, "Aspect-oriented changes with Javassist" (March 2004) Part 8, "Replacing reflection with code generation" (June 2004)
Changing classes Besides reflection-style access to class components, org.apache.bcel.classfile.JavaClass also provides methods for altering the class. You can use these methods to set any of the class components to new values. They're not generally of much direct use, though, because the other classes in the package don't provide support for constructing new versions of the components in any reasonable manner. Instead, there's an entire separate set of classes in the org.apache.bcel.generic package that provides editable versions of the same components represented by org.apache.bcel.classfile classes. Just as org.apache.bcel.classfile.JavaClass is the starting point for using BCEL to inspect existing classes, org.apache.bcel.generic.ClassGen is your starting point for creating new classes. It also works for modifying existing classes -- to handle that case, there's a constructor that takes a JavaClass instance and uses it to initialize the ClassGen class information. Once you're done with your class modifications, you can get a usable class representation from the ClassGen instance by calling a method that returns a JavaClass, which can in turn be converted to a binary class representation. Sound confusing? I think it is. In fact, going back and forth between the two packages is one of the most awkward aspects of working with BCEL. The duplicate class structures tend to get in the way, so if you're doing much with BCEL, it may be worthwhile to write wrapper classes that can hide some of these differences. For this article, I'll work mainly with the org.apache.bcel.generic package classes and avoid the use of wrappers, but it's something for you to keep in mind for your own work.
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
Besides ClassGen, the org.apache.bcel.generic package defines classes to manage the construction of various class components. These construction classes include ConstantPoolGen for handling the constant pool, FieldGen and MethodGen for fields and methods, and InstructionList for working with sequences of JVM instructions. Finally, the org.apache.bcel.generic package also defines classes to represent every type of JVM instruction. You can create instances of these classes directly, or in some cases by using the org.apache.bcel.generic.InstructionFactory helper class. The advantage of using InstructionFactory is that it handles many of the bookkeeping details of instruction building for you (including adding items to the constant pool as needed for the instructions). You'll see how to make all these classes play together in the next section. Back to top Classworking with BCEL For an example of applying BCEL, I'll use the same task I used as a Javassist example back in Part 4 -- measuring the time taken to execute a method. I'll even use the same approach I used with Javassist: I'll create a copy of the original method to be timed using a modified name, then replace the body of the original method with code that wraps timing calculations around a call to the renamed method.
Selecting a guinea pig Listing 1 gives an example method I'll use for demonstration purposes: the buildString method of the StringBuilder class. As I said in Part 4, this method constructs a String of any requested length by doing exactly what any Java performance guru will tell you not to do -- it repeatedly appends a single character to the end of a string to create a longer string. Because strings are immutable, this approach means a new string will be constructed each time through the loop, with the data copied from the old string and a single character added at the end. The net effect is that this method will run into more and more overhead as it's used to create longer strings. Listing 1. Method to be timed public class StringBuilder { private String buildString(int length) { String result = ""; for (int i = 0; i < length; i++) { result += (char)(i%26 + 'a'); } return result; }
}
public static void main(String[] argv) { StringBuilder inst = new StringBuilder(); for (int i = 0; i < argv.length; i++) { String result = inst.buildString(Integer.parseInt(argv[i])); System.out.println("Constructed string of length " + result.length()); } }
Listing 2 shows the source code equivalent to the classworking change I'll make with BCEL. Here the wrapper method just saves the current time, then calls the renamed original method and prints a time report before returning the result of the call to the original method. Listing 2. Timing added to original method public class StringBuilder { private String buildString$impl(int length) { String result = ""; for (int i = 0; i < length; i++) { result += (char)(i%26 + 'a'); } return result; } private String buildString(int length) { long start = System.currentTimeMillis(); String result = buildString$impl(length); System.out.println("Call to buildString$impl took " + (System.currentTimeMillis()-start) + " ms."); return result; }
}
public static void main(String[] argv) { StringBuilder inst = new StringBuilder(); for (int i = 0; i < argv.length; i++) { String result = inst.buildString(Integer.parseInt(argv[i])); System.out.println("Constructed string of length " + result.length()); } }
Coding the transform Implementing the code to add method timing uses the BCEL APIs I outlined in the BCEL class access section. Working at the level of JVM instructions makes the code a lot longer than the Javassist example back in Part 4, so here I'm going to walk through it a piece at a time before giving you the complete implementation. In the final code, all these pieces will make up a single method, one that takes a pair of parameters: cgen, an instance of the org.apache.bcel.generic.ClassGen class initialized with the existing information for the class being modified; and method, an org.apache.bcel.classfile.Method instance for the method I'm going to time. Listing 3 has the first piece of code for the transform method. As you can see from the comments, the first part just initializes the basic BCEL components I'm going to use, which includes initializing a new org.apache.bcel.generic.MethodGen instance using the information for the method to be timed. I set an empty instruction list for this MethodGen, which I'll later fill in with the actual timing code. In the second part, I create a second org.apache.bcel.generic.MethodGen instance from the original method, then remove the original method from the class. On this second MethodGen instance, I just change the name to use a "$impl" suffix, then call getMethod() to convert the modifiable method information to a fixed form as an org.apache.bcel.classfile.Method instance. I then use the addMethod() call to add the renamed method to the class. Listing 3. Adding the interception method // set up the construction tools InstructionFactory ifact = new InstructionFactory(cgen); InstructionList ilist = new InstructionList(); ConstantPoolGen pgen = cgen.getConstantPool(); String cname = cgen.getClassName(); MethodGen wrapgen = new MethodGen(method, cname, pgen); wrapgen.setInstructionList(ilist); // rename a copy of the original method MethodGen methgen = new MethodGen(method, cname, pgen); cgen.removeMethod(method); String iname = methgen.getName() + "$impl"; methgen.setName(iname); cgen.addMethod(methgen.getMethod());
Listing 4 gives the next piece of code for the transform method. The first part here computes the space occupied by the method call parameters on the stack. This piece is needed because to store the start time on the stack frame before calling the wrapped method I need to know what offset can be used for a local variable (note that I could use BCEL's local variable handling to get the same effect, but for this article I prefer an explicit approach). The second part of this code generates the call to java.lang.System.currentTimeMillis() to get the start time, saving it to the computed local variable offset in the stack frame. You might wonder why I check whether the method is static at the start of my parameter size calculation, then initialize the stack frame slot to zero if it is (as opposed to one if it is not). This approach relates to how the Java language handles method calls. For non-static methods, the first (hidden) parameter on every call is the this reference for the target object, which I need to take into account when computing the complete parameter set size on the stack frame. Listing 4. Setting up for the wrapped call // compute the size of the calling parameters Type[] types = methgen.getArgumentTypes(); int slot = methgen.isStatic() ? 0 : 1; for (int i = 0; i < types.length; i++) { slot += types[i].getSize(); } // save time prior to invocation ilist.append(ifact.createInvoke("java.lang.System", "currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); ilist.append(InstructionFactory.createStore(Type.LONG, slot));
Listing 5 shows the code to generate the call to the wrapped method and save the result (if any). The first part of this piece again checks whether the method is static. If the method is not static, I generate code to load the this object reference to the stack, and also set the method call type to virtual (rather than static). The for loop then generates code to copy all call parameter values to the stack, the createInvoke() method generates the actual call to the wrapped method, and the final if statement saves the result value to another local variable position in the stack frame (if the result type is not void). Listing 5. Calling the wrapped method // call the wrapped method int offset = 0; short invoke = Constants.INVOKESTATIC; if (!methgen.isStatic()) { ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0)); offset = 1; invoke = Constants.INVOKEVIRTUAL; } for (int i = 0; i < types.length; i++) { Type type = types[i]; ilist.append(InstructionFactory.createLoad(type, offset)); offset += type.getSize(); } Type result = methgen.getReturnType(); ilist.append(ifact.createInvoke(cname, iname, result, types, invoke)); // store result for return later if (result != Type.VOID) { ilist.append(InstructionFactory.createStore(result, slot+2)); }
Now into the wrap up. Listing 6 generates the code to actually compute the number of milliseconds elapsed since the start time, and to print it out as a nicely formatted message. This part looks very complex, but most of the operations are actually just writing individual pieces of the output message. It does illustrate several types of operations I didn't use in the earlier code, including a field access (to java.lang.System.out) and a few different instruction types. Most of these should be easy to understand if you think in terms of the JVM as a stack-based processor, so I won't go into details here. Listing 6. Computing and printing time used // print time required for method call ilist.append(ifact.createFieldAccess("java.lang.System", "out", new ObjectType("java.io.PrintStream"), Constants.GETSTATIC)); ilist.append(InstructionConstants.DUP); ilist.append(InstructionConstants.DUP); String text = "Call to method " + methgen.getName() + " took "; ilist.append(new PUSH(pgen, text)); ilist.append(ifact.createInvoke("java.io.PrintStream", "print", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL)); ilist.append(ifact.createInvoke("java.lang.System", "currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); ilist.append(InstructionFactory.createLoad(Type.LONG, slot)); ilist.append(InstructionConstants.LSUB); ilist.append(ifact.createInvoke("java.io.PrintStream", "print", Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL)); ilist.append(new PUSH(pgen, " ms.")); ilist.append(ifact.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
After the timing message code is generated, all that's left for Listing 7 is the completion of the wrapper method code with a return of the saved result value (if any) from the wrapped method call, followed by the finalizing of the constructed wrapper method. This last part involves several steps. The call to stripAttributes(true) just tells BCEL not to generate debug information for the constructed method, while the setMaxStack() and setMaxLocals() calls calculate and set the stack usage information for the method. After that's been done, I can actually generate the finalized version of the method and add it to the class. Listing 7. Completing the wrapper // return result from wrapped method call if (result != Type.VOID) { ilist.append(InstructionFactory.createLoad(result, slot+2)); } ilist.append(InstructionFactory.createReturn(result)); // finalize the constructed method wrapgen.stripAttributes(true); wrapgen.setMaxStack(); wrapgen.setMaxLocals(); cgen.addMethod(wrapgen.getMethod()); ilist.dispose();
The full code Listing 8 shows the complete code (slightly reformatted to fit the width), including a main() method that takes the name of the class file and method to be transformed: Listing 8. The complete transform code public class BCELTiming { private static void addWrapper(ClassGen cgen, Method method) { // set up the construction tools InstructionFactory ifact = new InstructionFactory(cgen); InstructionList ilist = new InstructionList(); ConstantPoolGen pgen = cgen.getConstantPool(); String cname = cgen.getClassName(); MethodGen wrapgen = new MethodGen(method, cname, pgen); wrapgen.setInstructionList(ilist); // rename a copy of the original method MethodGen methgen = new MethodGen(method, cname, pgen); cgen.removeMethod(method); String iname = methgen.getName() + "$impl"; methgen.setName(iname); cgen.addMethod(methgen.getMethod()); Type result = methgen.getReturnType(); // compute the size of the calling parameters Type[] types = methgen.getArgumentTypes();
int slot = methgen.isStatic() ? 0 : 1; for (int i = 0; i < types.length; i++) { slot += types[i].getSize(); } // save time prior to invocation ilist.append(ifact.createInvoke("java.lang.System", "currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); ilist.append(InstructionFactory. createStore(Type.LONG, slot)); // call the wrapped method int offset = 0; short invoke = Constants.INVOKESTATIC; if (!methgen.isStatic()) { ilist.append(InstructionFactory. createLoad(Type.OBJECT, 0)); offset = 1; invoke = Constants.INVOKEVIRTUAL; } for (int i = 0; i < types.length; i++) { Type type = types[i]; ilist.append(InstructionFactory. createLoad(type, offset)); offset += type.getSize(); } ilist.append(ifact.createInvoke(cname, iname, result, types, invoke)); // store result for return later if (result != Type.VOID) { ilist.append(InstructionFactory. createStore(result, slot+2)); } // print time required for method call ilist.append(ifact.createFieldAccess("java.lang.System", "out", new ObjectType("java.io.PrintStream"), Constants.GETSTATIC)); ilist.append(InstructionConstants.DUP); ilist.append(InstructionConstants.DUP); String text = "Call to method " + methgen.getName() + " took "; ilist.append(new PUSH(pgen, text)); ilist.append(ifact.createInvoke("java.io.PrintStream", "print", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL)); ilist.append(ifact.createInvoke("java.lang.System", "currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); ilist.append(InstructionFactory. createLoad(Type.LONG, slot)); ilist.append(InstructionConstants.LSUB); ilist.append(ifact.createInvoke("java.io.PrintStream", "print", Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL)); ilist.append(new PUSH(pgen, " ms.")); ilist.append(ifact.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL)); // return result from wrapped method call if (result != Type.VOID) { ilist.append(InstructionFactory. createLoad(result, slot+2)); } ilist.append(InstructionFactory.createReturn(result));
}
// finalize the constructed method wrapgen.stripAttributes(true); wrapgen.setMaxStack(); wrapgen.setMaxLocals(); cgen.addMethod(wrapgen.getMethod()); ilist.dispose();
public static void main(String[] argv) { if (argv.length == 2 && argv[0].endsWith(".class")) { try { JavaClass jclas = new ClassParser(argv[0]).parse(); ClassGen cgen = new ClassGen(jclas); Method[] methods = jclas.getMethods(); int index; for (index = 0; index < methods.length; index++) { if (methods[index].getName().equals(argv[1])) { break; } } if (index < methods.length) { addWrapper(cgen, methods[index]); FileOutputStream fos = new FileOutputStream(argv[0]); cgen.getJavaClass().dump(fos); fos.close(); } else { System.err.println("Method " + argv[1] + " not found in " + argv[0]); } } catch (IOException ex) { ex.printStackTrace(System.err); }
}
}
} else { System.out.println ("Usage: BCELTiming class-file method-name"); }
Taking it out for a spin Listing 9 shows the results of first running the StringBuilder program in unmodified form, then running the BCELTiming program to add timing information, and finally running the StringBuilder program after it's been modified. You can see how StringBuilder starts reporting execution times after it's been modified, and how the times increase much faster than the length of the constructed string because of the inefficient string construction code. Listing 9. Running the programs [dennis]$ java StringBuilder Constructed string of length Constructed string of length Constructed string of length Constructed string of length Constructed string of length
1000 2000 4000 8000 16000 1000 2000 4000 8000 16000
[dennis]$ java -cp bcel.jar:. BCELTiming StringBuilder.class buildString [dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Call to method buildString$impl took 20 ms. Constructed string of length 1000 Call to method buildString$impl took 79 ms. Constructed string of length 2000 Call to method buildString$impl took 250 ms. Constructed string of length 4000 Call to method buildString$impl took 879 ms. Constructed string of length 8000 Call to method buildString$impl took 3875 ms. Constructed string of length 16000
Back to top Wrapping up BCEL There's more to BCEL than just the basic classworking support I've shown in this article. It also includes a full verifier implementation to make sure that a binary class is valid according to the JVM specification (see org.apache.bcel.verifier.VerifierFactory), a disassembler that generates a nicely framed and linked JVM-level view of a binary class, and even a BCEL program generator that outputs source code for a BCEL program to build a class you provide. (The org.apache.bcel.util.BCELifier class is not included in the Javadocs, so look to the source code for usage. This feature is intriguing, but the output is probably too cryptic to be of use to most developers). In my own use of BCEL, I've found the HTML disassembler especially useful. To try it out, just execute the org.apache.bcel.util.Class2HTML class from the BCEL JAR, with the path to the class file you want to disassemble as a command line argument. It'll generate the HTML files in the current directory. For example, here I'll disassemble the StringBuilder class I used for my timing example: [dennis]$ java -cp bcel.jar org.apache.bcel.util.Class2HTML StringBuilder.class Processing StringBuilder.class...Done. Figure 1 is a screen capture of the framed output generated by the disassembler. In this shot the large frame in the upper right shows the disassembly of the timing wrapper method added to the StringBuilder class. The full HTML output is included in the download files -- just open the StringBuilder.html file in a browser window if you'd like to view this live." Figure 1. Disassembling StringBuilder
Currently, BCEL is probably the most widely used framework for Java classworking. It lists a number of other projects that use BCEL on the Web site, including the Xalan XSLT compiler, the AspectJ extension to the Java programming language, and several JDO implementations. Many other unlisted projects are also using BCEL, including my own JiBX XML data binding project. However, several of the projects listed by BCEL have since switched to other libraries, so don't take the length of the list as an absolute guide to BCEL's popularity. The big advantages of BCEL are its commercial-friendly Apache licensing and its extensive JVM instruction-level support. These features, combined with its stability and longevity, have made it a very popular choice for classworking applications. BCEL does not seem all that well designed for either speed or ease of use, though. Javassist offers a much friendlier API for most purposes, with equivalent (or perhaps even better) speed, at least in my simple tests. If your projects can make use of software using the Mozilla Public License (MPL) or GNU Lesser General Public License (LGPL), Javassist may be a better choice right now (it's available under either of these licenses). Back to top Up next Now that I've introduced you to both Javassist and BCEL, my next article in this series will dig into a more useful application of classworking than what you've seen so far. Back in Part 2, I demonstrated how reflection calls to methods are much slower than direct calls. In Part 8, I'll show how you can use both Javassist and BCEL to replace reflection calls with dynamically generated code at runtime -- with a dramatic improvement in performance. Check back next month for another dose of Java programming dynamics to find out the details.
Back to top Download Name
Size
ftp://www6.software.ibm.com/software/developer/library/j
Download method
-dyn7.zip
Information about download methods
Resources
Download the sample code, including the disassembler output for the modified class.
Get all the details on the open source Byte Code Engineering Library at the Apache project page.
Learn more about the Java bytecode design in "Java bytecode: Understanding bytecode makes you a better programmer" (developerWorks, July 2001) by Peter Haggar.
For an excellent reference to the JVM architecture and instruction set, see Inside the Java Virtual Machine, by Bill Venners (Artima Software, Inc., 2004). You can view some sample chapters online to get a look at it before you purchase.
You can purchase or view the official Java Virtual Machine Specification online for the definitive word on all aspects of JVM operation.
AspectJ extends the Java language with aspect-oriented features, using BCEL to weave code into classes generated by the compiler. Learn all about it at the Eclipse project page.
For some other projects making use of BCEL, check out the Apache Xalan XSLTC compiler for XSL stylesheets, the Hansel JUnit extension that monitors code coverage in tests, and the author's own JiBX framework for fast XML data binding.
Want to find out more about aspect-oriented programming? Try "Improve modularity with aspect-oriented programming" (developerWorks January 2002) by Nicholas Lesiecki for an overview of working with the AspectJ language.
The open source Jikes Project provides a very fast and highly compliant compiler for the Java programming language. Use it to generate your bytecode the old fashioned way -- from Java source code.
Find hundreds more Java technology resources on the developerWorks Java technology zone.
Browse for books on these and other technical topics.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 7: Bytecode engineering http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html with BCEL 10935 04142004
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact
Country/region [ select ]
1
Terms of use
dW All of dW
Home
Products
Services & industry solutions
developerWorks developerWorks In this article:
> Java
Support & downloads
My IBM
technology >
Java programming dynamics, Part 8: Replacing reflection with code generation Runtime code generation offers a way of replacing reflection with direct access for top performance
Reflections on performance Building a glue class Don't miss the rest of this series Performance
check
Sample code
Earlier in this article series, you learned how reflection performance is many times slower than direct access, and then learned about classworking with Javassist and the Apache Byte Code Engineering Library (BCEL). Java consultant Dennis Sosnoski wraps up his Java programming dynamics series by demonstrating how you can use runtime classworking to replace reflection code with generated code that runs at full speed ahead.
Download Resources
Rate this page
Document options requiring JavaScript are not displayed
Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc. 10 Jun 2004
Speeding you on your way
About the author
Document options
Level: Intermediate
Now that you've seen how to use the Javassist and BCEL frameworks for classworking (see the listing of previous articles in this series), I'm going to show you a practical classworking application. This application is replacing the use of reflection with classes generated at runtime and immediately loaded into the JVM. In the process of putting it together, I'm going to refer back to the first two articles of the series as well as the Javassist and BCEL coverage, so it makes a nice wrap-up for what's turned out to be a long series of articles.
Watch these demos Integrate new tools and architectures into your environment -- fast!
Reflections on performance Related links Java technology technical library
Back in Part 2, I showed how reflection is many times slower than direct code for both field access and method calls. This sluggishness is not a problem for many applications, but there are always going to be cases where performance is critical. In these cases, reflection can represent a real bottleneck. Replacing reflection with statically compiled code can be very messy, though, and in some cases (as in frameworks where the classes or items accessed by reflection are supplied at runtime, rather than as part of the same build process) may even be impossible without restructuring the whole application.
Rate this page Help us improve this content
Classworking gives you an alternative that combines the performance of statically compiled code with the flexibility of reflection. The basic approach here is to construct a custom class at runtime that will wrap access to the target classes (previously reached by reflection) in a way that can be used by your general-purpose code. After loading the custom class into the JVM, you're then set to run at full speed.
Setting the stage Listing 1 gives a starting point for the application. Here I've defined a simple bean class, HolderBean, and an access class, ReflectAccess. The access class takes a single command line argument that must be the name of one of the int-valued bean class properties (value1 or value2). It increments the value of the named property, then prints out both property values before exiting. Listing 1. Reflecting a bean public class HolderBean { private int m_value1; private int m_value2; public int getValue1() { return m_value1; } public void setValue1(int value) { m_value1 = value; } public int getValue2() { return m_value2; } public void setValue2(int value) { m_value2 = value; }
} public class ReflectAccess { public void run(String[] args) throws Exception { if (args.length == 1 && args[0].length() > 0) { // create property name char lead = args[0].charAt(0); String pname = Character.toUpperCase(lead) + args[0].substring(1); // look up the get and set methods Method gmeth = HolderBean.class.getDeclaredMethod ("get" + pname, new Class[0]); Method smeth = HolderBean.class.getDeclaredMethod ("set" + pname, new Class[] { int.class }); // increment value using reflection HolderBean bean = new HolderBean(); Object start = gmeth.invoke(bean, null); int incr = ((Integer)start).intValue() + 1; smeth.invoke(bean, new Object[] {new Integer(incr)}); // print the ending values System.out.println("Result values " + bean.getValue1() + ", " + bean.getValue2());
}
}
} else { System.out.println("Usage: ReflectAccess value1|value2"); }
Here's a pair of sample runs of ReflectAccess to illustrate the results: [dennis]$ java -cp . ReflectAccess value1 Result values 1, 0 [dennis]$ java -cp . ReflectAccess value2 Result values 0, 1
Back to top Building a glue class Now that I've demonstrated the reflection version of the code, I'll show you how to substitute a generated class for the use of reflection. There's a subtle problem involved in making this substitution work properly that goes back to the discussion of classloading in Part 1 of this series. The problem is that I'm going to generate a class at runtime that I want to access from the statically compiled code of the access class, but because the generated class doesn't exist for the compiler, there's no way to reference it directly. So how can I link the statically compiled code to the generated class? The basic solution is to define a base class or interface that can be accessed by the statically compiled code, then extend that base class or implement that interface in the generated class. The statically compiled code can then make direct calls to methods, even though the methods won't actually be implemented until runtime. In Listing 2, I've defined an interface, IAccess, intended to provide this link for the generated code. The interface includes three methods. The first method just sets a target object to be accessed. The other two methods are proxies for the get and set methods used to access an int property value.
Don't miss the rest of this series Part 1, "Classes and class loading"(April 2003) Part 2, "Introducing reflection" (June 2003) Part 3, "Applied reflection" (July 2003) Part 4, "Class transformation with Javassist" (September 2003)
Listing 2. Interface to the glue class public interface IAccess { public void setTarget(Object target); public int getValue(); public void setValue(int value); }
Part 5, "Transforming classes on-the-fly" (February 2004) Part 6, "Aspect-oriented changes with Javassist" (March 2004) Part 7, "Bytecode engineering with BCEL" (April 2004)
The intent here is that the generated implementation of the IAccess interface will provide the code to call the appropriate get and set methods of a target class. Listing 3 shows a sample of how this interface could be implemented, assuming that I want to access the value1 property of the Listing 1 HolderBean class: Listing 3. Glue class sample implementation public class AccessValue1 implements IAccess { private HolderBean m_target;
}
public void setTarget(Object target) { m_target = (HolderBean)target; } public int getValue() { return m_target.getValue1(); } public void setValue(int value) { m_target.setValue1(value); }
The Listing 2 interface is designed to be used with a particular property of a particular type of object. This interface keeps the implementation code simple -- always an advantage when working with bytecode -- but means that the implementation class is very specific. There will need to be a separate implementation class for each type of object and property I want to access through this interface, which limits the use of this approach as a general replacement ffor reflection. This limitation isn't a problem as long as you only apply the technique selectively in cases where reflection performance is really a bottleneck.
Generating with Javassist Generating the implementation class for the Listing 2 IAccess interface with Javassist is easy -- I just need to create a new class that implements the interface, add a member variable for the target object reference, and finish by adding a no-argument constructor and the simple implementation methods. Listing 4 shows the Javassist code to complete these steps, structured as a method call that takes the target class and get/set method information and returns the binary representation of the constructed class:
Ask the expert: Dennis Sosnoski on JVM and bytecode issues For comments or questions about the material covered in this article series, as well as anything else that pertains to Java bytecode, the Java binary class format, or general JVM issues, visit the JVM and Bytecode discussion forum, moderated by Dennis Sosnoski.
Listing 4. Javassist glue class construction /** Parameter types for call with no parameters. */ private static final CtClass[] NO_ARGS = {}; /** Parameter types for call with single int value. */ private static final CtClass[] INT_ARGS = { CtClass.intType }; protected byte[] createAccess(Class tclas, Method gmeth, Method smeth, String cname) throws Exception { // build generator for the new class String tname = tclas.getName(); ClassPool pool = ClassPool.getDefault(); CtClass clas = pool.makeClass(cname); clas.addInterface(pool.get("IAccess")); CtClass target = pool.get(tname); // add target object field to class CtField field = new CtField(target, "m_target", clas); clas.addField(field); // add public default constructor method to class CtConstructor cons = new CtConstructor(NO_ARGS, clas); cons.setBody(";"); clas.addConstructor(cons); // add public setTarget method CtMethod meth = new CtMethod(CtClass.voidType, "setTarget", new CtClass[] { pool.get("java.lang.Object") }, clas); meth.setBody("m_target = (" + tclas.getName() + ")$1;"); clas.addMethod(meth); // add public getValue method meth = new CtMethod(CtClass.intType, "getValue", NO_ARGS, clas); meth.setBody("return m_target." + gmeth.getName() + "();"); clas.addMethod(meth); // add public setValue method meth = new CtMethod(CtClass.voidType, "setValue", INT_ARGS, clas); meth.setBody("m_target." + smeth.getName() + "($1);"); clas.addMethod(meth);
}
// return binary representation of completed class return clas.toBytecode();
I'm not going to run through this code in any detail because, if you've been following this series, most of the operations will already look familiar (and if you haven't been following the series, check out Part 5 now for an overview of working with Javassist).
Generating with BCEL Generating the implementation class for the Listing 2 IAccess with BCEL is not quite as easy as with Javassist, but it's still not terribly complicated. Listing 5 gives the code for this purpose. This code uses the same sequence of operations as the Listing 4 Javassist code, but runs somewhat longer because of the need to spell out each bytecode instruction for BCEL. As with the Javassist version, I'm going to skip over the details of the implementation (refer back to Part 7 for an overview of BCEL if anything looks unfamiliar). Listing 5. BCEL glue class construction /** Parameter types for call with single int value. */ private static final Type[] INT_ARGS = { Type.INT }; /** Utility method for adding constructed method to class. */ private static void addMethod(MethodGen mgen, ClassGen cgen) { mgen.setMaxStack(); mgen.setMaxLocals(); InstructionList ilist = mgen.getInstructionList(); Method method = mgen.getMethod(); ilist.dispose(); cgen.addMethod(method); } protected byte[] createAccess(Class tclas, java.lang.reflect.Method gmeth, java.lang.reflect.Method smeth, String cname) { // build generators for the new class String tname = tclas.getName(); ClassGen cgen = new ClassGen(cname, "java.lang.Object", cname + ".java", Constants.ACC_PUBLIC, new String[] { "IAccess" }); InstructionFactory ifact = new InstructionFactory(cgen); ConstantPoolGen pgen = cgen.getConstantPool(); //. add target object field to class FieldGen fgen = new FieldGen(Constants.ACC_PRIVATE, new ObjectType(tname), "m_target", pgen); cgen.addField(fgen.getField()); int findex = pgen.addFieldref(cname, "m_target", Utility.getSignature(tname)); // create instruction list for default constructor InstructionList ilist = new InstructionList(); ilist.append(InstructionConstants.ALOAD_0); ilist.append(ifact.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL)); ilist.append(InstructionFactory.createReturn(Type.VOID)); // add public default constructor method to class MethodGen mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, null, "<init>", cname, ilist, pgen); addMethod(mgen, cgen); // create instruction list for setTarget method ilist = new InstructionList(); ilist.append(InstructionConstants.ALOAD_0); ilist.append(InstructionConstants.ALOAD_1); ilist.append(new CHECKCAST(pgen.addClass(tname))); ilist.append(new PUTFIELD(findex)); ilist.append(InstructionConstants.RETURN); // add public setTarget method mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, new Type[] { Type.OBJECT }, null, "setTarget", cname, ilist, pgen); addMethod(mgen, cgen); // create instruction list for getValue method ilist = new InstructionList(); ilist.append(InstructionConstants.ALOAD_0); ilist.append(new GETFIELD(findex)); ilist.append(ifact.createInvoke(tname, gmeth.getName(), Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); ilist.append(InstructionConstants.IRETURN); // add public getValue method mgen = new MethodGen(Constants.ACC_PUBLIC, Type.INT, Type.NO_ARGS, null, "getValue", cname, ilist, pgen); addMethod(mgen, cgen); // create instruction list for setValue method ilist = new InstructionList(); ilist.append(InstructionConstants.ALOAD_0); ilist.append(new GETFIELD(findex)); ilist.append(InstructionConstants.ILOAD_1); ilist.append(ifact.createInvoke(tname, smeth.getName(), Type.VOID, INT_ARGS, Constants.INVOKEVIRTUAL)); ilist.append(InstructionConstants.RETURN); // add public setValue method mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, INT_ARGS, null, "setValue", cname, ilist, pgen); addMethod(mgen, cgen);
}
// return bytecode of completed class return cgen.getJavaClass().getBytes();
Back to top Performance check Now that I've got code for both Javassist and BCEL versions of the method construction, I can try them out to see how well they work. My original reason for generating code at runtime was to replace reflection with something faster, so it would be good to include a performance comparison to see how well I've succeeded. Just to make it interesting, I'll also look at the time it takes to construct the glue class with each of the frameworks. Listing 6 shows the main parts of the test code I'll use to check out the performance. The runReflection() method runs the reflection part of the test, runAccess() runs the direct access part, and run() controls
Listing 6 shows the main parts of the test code I'll use to check out the performance. The the whole process (including printing the timing results). Both runReflection() and runAccess() take the number of loops to be executed as a parameter, which is in turn passed in from the command line (using code not shown in this listing, but included in the download). The DirectLoader class (at the end of Listing 6) just provides an easy way of loading the generated classes. Listing 6. Performance test code /** Run timed loop using reflection for access to value. */ private int runReflection(int num, Method gmeth, Method smeth, Object obj) { int value = 0; try { Object[] gargs = new Object[0]; Object[] sargs = new Object[1]; for (int i = 0; i < num; i++) { // messy usage of Integer values required in loop Object result = gmeth.invoke(obj, gargs); value = ((Integer)result).intValue() + 1; sargs[0] = new Integer(value); smeth.invoke(obj, sargs);
}
} } catch (Exception ex) { ex.printStackTrace(System.err); System.exit(1); } return value;
/** Run timed loop using generated class for access to value. */ private int runAccess(int num, IAccess access, Object obj) { access.setTarget(obj); int value = 0; for (int i = 0; i < num; i++) { value = access.getValue() + 1; access.setValue(value); } return value; } public void run(String name, int count) throws Exception { // get instance and access methods HolderBean bean = new HolderBean(); String pname = name; char lead = pname.charAt(0); pname = Character.toUpperCase(lead) + pname.substring(1); Method gmeth = null; Method smeth = null; try { gmeth = HolderBean.class.getDeclaredMethod("get" + pname, new Class[0]); smeth = HolderBean.class.getDeclaredMethod("set" + pname, new Class[] { int.class }); } catch (Exception ex) { System.err.println("No methods found for property " + pname); ex.printStackTrace(System.err); return; } // create the access class as a byte array long base = System.currentTimeMillis(); String cname = "IAccess$impl_HolderBean_" + gmeth.getName() + "_" + smeth.getName(); byte[] bytes = createAccess(HolderBean.class, gmeth, smeth, cname); // load and construct an instance of the class Class clas = s_classLoader.load(cname, bytes); IAccess access = null; try { access = (IAccess)clas.newInstance(); } catch (IllegalAccessException ex) { ex.printStackTrace(System.err); System.exit(1); } catch (InstantiationException ex) { ex.printStackTrace(System.err); System.exit(1); } System.out.println("Generate and load time of " + (System.currentTimeMillis()-base) + " ms.");
}
// run the timing comparison long start = System.currentTimeMillis(); int result = runReflection(count, gmeth, smeth, bean); long time = System.currentTimeMillis() - start; System.out.println("Reflection took " + time + " ms. with result " + result + " (" + bean.getValue1() + ", " + bean.getValue2() + ")"); bean.setValue1(0); bean.setValue2(0); start = System.currentTimeMillis(); result = runAccess(count, access, bean); time = System.currentTimeMillis() - start; System.out.println("Generated took " + time + " ms. with result " + result + " (" + bean.getValue1() + ", " + bean.getValue2() + ")");
/** Simple-minded loader for constructed classes. */ protected static class DirectLoader extends SecureClassLoader { protected DirectLoader() { super(TimeCalls.class.getClassLoader()); }
}
protected Class load(String name, byte[] data) { return super.defineClass(name, data, 0, data.length); }
For a simple timing test, I call the run() method twice, once for each of the properties in the Listing 1 HolderBean class. Running the two test passes is important for a reasonably fair test -- the first pass through the code is going to load all the necessary classes, which adds a lot of overhead to both the Javassist and BCEL class generation process. This overhead isn't required on the second pass, though, giving you a better estimate of how long the class generation would require when used within a real system. Here's a sample of the generated output when the test is executed: [dennis]$$ java -cp .:bcel.jar BCELCalls 2000 Generate and load time of 409 ms. Reflection took 61 ms. with result 2000 (2000, 0) Generated took 2 ms. with result 2000 (2000, 0) Generate and load time of 1 ms. Reflection took 13 ms. with result 2000 (0, 2000) Generated took 2 ms. with result 2000 (0, 2000)
Figure 1 shows the results of this timing test when called with loop counts ranging from 2K to 512K (tests run on an Athlon 2200+ XP system running Mandrake Linux 9.1, using Sun's 1.4.2 JVM). Here I've included both the reflection and generated code times for the second property within each test run (so the pair of times when using the Javassist code generation are first, followed by the same pair of times when using the BCEL code generation). The execution times are about the same regardless of whether Javassist or BCEL is used to generate the glue classes, which is what I'd expect to see -- but it's always good to have confirmation! Figure 1. Reflection vs. generated code speed (time in milliseconds)
As you can see from Figure 1, the generated code executes much faster than reflection in every case. The speed advantage for generated code increases as the number of loops goes up, starting out at roughly 5:1 with 2K loops and going up to about 24:1 with 512K loops. Constructing and loading the first glue class took about 320 milliseconds (ms) for Javassist, and 370 ms for BCEL, while constructing the second glue class took only about 4 ms for Javassist and 2 ms for BCEL (though the clock resolution is only 1 ms, so these times are very rough). If you combine these times, you'll see that even for the 2K loop case generating a class is going to give better performance overall than using reflection (with a total execution time of about 4 ms to 6 ms, versus about 14 ms for reflection). In actuality, the situation is even more biased in favor of the generated code than this chart seems to indicate. When I tried going as low as 25 loops, the reflection code still took 6 ms to 7 ms to execute, while the generated code was too fast to register. The time taken by reflection for relatively small loop counts appears to reflect some optimizations going on within the JVM when a threshold is reached; if I lowered the loop count below about 20, the reflection code also became too fast to register. Back to top Speeding you on your way Now you've seen the kind of performance that runtime classworking can deliver for your applications. Keep it in mind the next time you're facing an intractable performance optimization problem -- it may just be the magic bullet that can save you a major redesign. Classworking is good for more than just performance, though. It's also a uniquely flexible approach to tailoring your application to runtime requirements. Even if you never have cause to use it in your code, I think it's one of the features of Java that keeps programming fun and interesting.
This venture into a real-world application of classworking wraps up the series on Java programming dynamics. But don't despair -- you'll soon get the chance to sample some other classworking applications at the developerWorks buffet when I present some of the tools that have been built around Java bytecode manipulation. First up will be an article on a pair of test tools straight out of Mother Goose.
Back to top Download Name
Size
Download method
HTTP
j-dyn0610.zip
Information about download methods
Resources
Learn more about the Java bytecode design in "Java bytecode: Understanding bytecode makes you a better programmer" (developerWorks, July 2001) by Peter Haggar.
Read "Improve modularity with aspect-oriented programming" (developerWorks, January 2002) by Nicholas Lesiecki to learn more about aspect-oriented programming.
For an excellent reference to the JVM architecture and instruction set, see Inside the Java Virtual Machine, by Bill Venners (Artima Software, Inc., 2004). You can view some sample chapters online to get a look at it before you purchase.
You can purchase or view the official Java Virtual Machine Specification online for the definitive word on all aspects of JVM operation.
Download the example code for this article.
The open source Jikes Project provides a very fast and highly compliant compiler for the Java programming language. Use it to generate your bytecode the old fashioned way -- from Java source code.
Browse for books on these and other technical topics.
You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.
About the author Dennis Sosnoski is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. Dennis is a frequent speaker on XML and Java technologies at conferences nationwide, and chairs the Seattle Java-XML SIG and Eclipse SIG. Contact Dennis at dms@sosnoski.com.
Rate this page 1
Java programming dynamics, Java Part technology 8: Replacing reflection http://www.ibm.com/developerworks/thankyou/feedback-thankyou.html with code generation 10951 06102004
dms@sosnoski.com
Please take a moment to complete this form to help us better serve you. Did the information help you to achieve your goal?
Yes
No
Don't know
Please provide us with comments to help improve this page:
How useful is the information?
1 Not useful
2
3
4
5 Extremely useful
Back to top
About IBM
Privacy
Contact