Dynamics of Java programming, Part 7: bytecode design with BCEL — reprint

In the last three articles in this series, I showed how to manipulate classes with the javassist framework. This time I will manipulate bytecode in a very different way -- using the Apache byte code engineering library (BCEL). Unlike the source code interfaces supported by javassist, BCEL operates at the actual JVM instruction level. The underlying method makes BCEL useful when you want to control every step of program execution, but when both are competent, it also makes the use of BCEL much more complex than javassist.

I'll first discuss the basic architecture of BCEL, and then most of this article will discuss an example of rebuilding my first javassist class operation with BCEL. Finally, it briefly introduces some tools provided in BCEL package and some applications built by developers with BCEL.

BCEL enables you to also have all the basic capabilities provided by javassist to analyze, edit, and create Java binary classes. One obvious difference with BCEL is that everything is designed to work at the JVM assembly language level rather than the source code interface provided by javassist. In addition to the superficial differences, there are some deeper differences, including the use of two different hierarchies of components in BCEL - one for checking existing code and the other for creating new code. I assume that readers are familiar with javassist through the previous articles in this series (please refer to the sidebar). Therefore, I will mainly introduce the differences that may confuse you when you start using BCEL. Like javassist, BCEL's functions in class analysis are basically repeated with those directly provided by the Java platform through the correlation API. This repetition is necessary for the class operation toolbox, because you generally don't want to Load the classes you want to operate on before they are modified. BCEL at org apache. Some basic constant definitions are provided in the BCEL package, but in addition to these definitions, all analysis related code is available at org apache. bcel. Classfile package. The starting point in this package is the javaclass class. This class plays the same role when accessing class information with BCEL as when using regular java reflection Lang. class works the same. Javaclass defines the methods to get the field and method information of this class, as well as the structure information about the parent class and interface. And Java Unlike lang. class, javaclass also provides access to the internal information of the class, including constant pool and attributes, as well as the complete binary class representation as a byte stream. Javaclass instances are usually created by parsing the actual binary classes. BCEL provides org apache. bcel. The repository class is used to handle parsing. By default, BCEL parses and buffers the class representation found in the JVM classpath, from org apache. bcel. util. Get the actual binary class representation in the repository instance (pay attention to the different package names). Org.apache.bcel.util.repository is actually the interface of the source code represented by the binary class. Where the class path is used in the default source code, you can replace it with other paths to query the class file or other methods to access the class information.

In addition to access to reflective forms of class components, org apache. bcel. classfile. Javaclass also provides methods to change classes. You can use these methods to set any component to a new value. However, they are generally not used directly because other classes in the package do not support building new versions of components in any reasonable way. Instead, at org apache. bcel. There is a completely separate set of classes in the generic package, which provides org apache. bcel. The editable version of the same component represented by the classfile class. Like org apache. bcel. classfile. Javaclass is the starting point for analyzing existing classes using BCEL, like org apache. bcel. generic. Classgen is the starting point for creating new classes. It is also used to modify existing classes -- to handle this situation, there is a constructor that takes a javaclass instance as a parameter and uses it to initialize classgen class information. After modifying the class, you can get the usable class representation from the classgen instance by calling a method that returns javaclass, and it can be converted into a binary class representation. Sounds a little messy? Yes, I think it is. In fact, rotating between two packages is one of the main disadvantages of using BCEL. Duplicate class structures always get in the way, so if you use BCEL frequently, you may need to write a wrapper class that can hide some of the differences. In this article, I will mainly use org. Org apache. bcel. Generic package classes and avoid using wrappers. But keep this in mind as you develop your own. In addition to classgen, org apache. bcel. The generic package also defines classes that manage the structure of different components. These structure classes include constantpoolgen for processing constant pools, fieldgen and methodgen for fields and methods, and instructionlist for processing a series of JVM instructions. Finally, org apache. bcel. The generic package also defines classes that represent JVM instructions of each type. You can create instances of these classes directly or, in some cases, use org apache. bcel. generic. Instructionfactory helper class. The advantage of using instructionfactory is that it handles many of the bookkeeping details of instruction construction (including adding items to the constant pool according to the needs of instructions). In the following section, you will see how to make all these classes work together. As an example of using BCEL, I will use a javassist example in - measuring the time to execute a method. I even use the same way as when using javassist: create a time to be timed with a changed name A copy of the original method, and then, by calling the renamed method, replace the body of the original method with the code wrapped with the time calculation. Listing 1 shows an example method for demonstration purposes: the buildstring method of the StringBuilder class. As I said in, this method takes the approach that all Java performance experts warn you not to use to build a string -- it repeatedly appends a single character at the end of the string to create a longer string. Because the string is immutable, this means that a new string is constructed each time the loop, the data is copied from the old string and a character is added at the end. The overall effect is that this method will incur more and more overhead when creating longer strings. Listing 1 Method to time < length; i++) +="(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 class operation changes with BCEL. The wrapper method here only saves the current time, then calls the renamed original method, and prints the time report before returning the result of calling the original method. Listing 2 Add timing < length in the original method; i++) +="(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() ); } } } Use the BCEL API I described in the next section to implement the code to add method timing. The operation at the JVM instruction level makes the code much longer than the javassist example in, so I'm going to introduce it paragraph by paragraph before providing a complete implementation. In the final code, all the fragments form a method with two parameters: CGEN -- it's org apache. bcel. generic. An instance of the classgen class, which initializes with the existing information of the modified class, and the method - org. To time the method apache. bcel. classfile. Method instance. Listing 3 is the first code of the transformation method. As you can see from the comments, the first part just initializes the basic BCEL components to be used. It includes initializing a new org. Com with the information of the timing method to be used apache. bcel. generic. Methodgen instance. I set up an empty instruction list for this methodgen, and I'll fill it later with the actual timing code. In part 2, I used the original method to create a second org apache. bcel. generic. Methodgen instance, and then delete the original method from the class. In the second MethodGen instance, I just put the name "$impl" suffix, and then call getmethod () to convert the modifiable method information into a fixed form of org.. apache. bcel. classfile. Method instance. Then call addMethod () to add a renamed method to the class. Listing 3 Add interception method / / rename a copy of the original method methodgen methodgen = new methodgen (method, pgen); cgen. removeMethod(method); String iname = methgen. getName() + "$impl"; methgen. setName(iname); cgen. addMethod(methgen.getmethod()); Listing 4 shows the next piece of code for the transformation method. The first part here calculates the space occupied by the method call parameters on the stack. This code is needed to store the start time on the stack frame before calling the wrapper method, I need to know what offset values can be used for local variables (note that I can use BCEL's local variables to get the same effect, but I choose to use explicit methods in this article). The second part of this code generates a call to Java. Lang. system. Currenttimemillis() to get the start time, And save it to the local variable offset calculated in the stack frame. You may wonder why you should check whether the method is static when starting parameter size calculation. If it is static, Initialize the stack frame slot to zero (not static, just the opposite). This method is related to how Java handles method calls. For non static methods, the first (hidden) parameter of each call is the this reference of the target object, which I want to take into account when calculating the full parameter set size in the stack frame. Listing 4. Setting the wrapped call < types. Length; I + +) {+= "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 that generates the call to the wrapper method and saves the result (if any). The first part of this code checks whether the method is static again. If the method is not static, generate the code that loads the reference of this object into the stack, and set the method call type to virtual (instead of static). Then the for loop generates code that copies all call parameter values to the stack, and the createinvoke () method generates the actual call to the wrapped method, Finally, the if statement saves the result value to another local variable in the stack frame (if the result type is not void). Listing 5. Call the wrapped method < types. Length; I + +) type IList append(instructionfactory.createload(type,offset)); += "type.getSize();" 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 start packing. Listing 6 generates the code that actually calculates the number of milliseconds elapsed after the start time and prints it as a formatted message. This part may seem complicated, but most operations actually just write out parts of the output message. It does show several types of operations that I didn't use in the previous code, Include field access (to Java. Lang. system. Out) and several different instruction types. If you think of the JVM as a stack based processor, most of them are easy to understand, so I won't elaborate here. Listing 6. Calculate and print the time used. After generating the timing information code, what is left in listing 7 is the call result value of the method that saves the wrapper (if any), and then end the wrapper method built. The last part involves several steps. Calling stripattributes (true) just tells BCEL not to generate debugging information for the built method, while setmaxstack() and setmaxlocals() call to calculate and set the stack usage information of the method. After this step, you can actually generate the final version of the method and add it to the class. Listing 7 Complete the wrapper / / finalize the constructed method wrapper stripAttributes(true); wrapgen. setMaxStack(); wrapgen. setMaxLocals(); cgen. addMethod(wrapgen.getmethod()); ilist. dispose(); Listing 8 shows the complete code (slightly changing the format to fit the display width), including the main () method with the name of the class file as the parameter and the method to be converted: Listing 8 Complete conversion code / / set up the construction tools instructionfactory effect = new instructionfactory (CGEN); InstructionList ilist = new InstructionList(); ConstantPoolGen pgen = cgen. getConstantPool(); String cname = cgen. getClassName(); MethodGen wrapgen = new MethodGen(method,pgen); wrapgen. setInstructionList(ilist); // rename a copy of the original method MethodGen methgen = new MethodGen(method,pgen); cgen. removeMethod(method); String iname = methgen. getName() + "$impl"; methgen. setName(iname); cgen. addMethod(methgen.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>