Initial BCEL attempt: An Example of modifying class implementation

Source: Internet
Author: User

There is a requirement in the project: without modifying the source code, replace the reference of a class with our own implementation. The following is a simple example:

Java code
Public class CarHolder {
Private Car;
Public CarHolder (){
Init ();
}
 
Private void init (){
Car = new Benz ();
}
 
Public void displayCarName (){
System. out. println (car. getCarName ());
}
}


Normally, when you call the displayCarName method, you will get "Hi, my name is Benz". However, when you call displayCarName, You need to output "Yeah, I am BMW "is actually replacing the pre-defined Benz implementation with the Implementation class of the BMW Car.

The essence of the requirement is to modify the CarHolder bytecode so that its runtime behavior looks different from the source code. There are two opportunities to modify the bytecode: 1. static modification: Replace the compiled class file of the java file with the modified class file, and the classLoader will load our implementation; 2. dynamic Modification: Use a special classLoader to load the source class file and modify it to the desired implementation, or when the classLoader loads the class file, use the ClassFileTransformer mechanism provided by JDK Instrumentation to modify the bytecode (java. lang. instrument. classFileTransformer ). The results of the two policies are the same. JVM can execute the modified bytecode.

Currently, there are more than a dozen components that can modify bytecode, some of which are at the source code level and some are at the JVM execution command level. In the end, I chose to try BCEL because it can familiarize myself with the organizational structure of the class file and the details of the JVM instruction set. At the same time, it is also added to sun's internal JDK, using multiple frameworks is also an opportunity for learning.

Before using BCEL, I reviewed the relevant content in the JVM specification and understood the usage of the constant pool and common JVM commands. The final code used is like this

Java code
Public class StaticChangedCode {
 
Public static void main (String [] args ){
Try {
// Manipulate the class file as an object
JavaClass clazz = Repository. lookupClass (CarHolder. class );
ClassGen classGen = new ClassGen (clazz );
 
// Because the old type is replaced, the types, methods, and attributes that are not in the current constant pool must be added one by one.
// Reference index of the constant pool item, which must be called in method instructions
ConstantPoolGen cPoolGen = classGen. getConstantPool ();
Int value = cPoolGen. addClass ("bcel. changeimpl. BMW ");
Int methodIndex = cPoolGen
. AddMethodref ("bcel. changeimpl. BMW", "<init>", "() V ");
Int fieldIndex = cPoolGen
. AddFieldref ("bcel. changeimpl. CarHolder ",
"Car", "Lbcel/changeimpl/Car ;");
 
// Obtain the method you want to manipulate, because I know the second ranking in the init method, so I am writing it to death.
Method sourceMethod = classGen. getMethods () [1];
MethodGen methodGen = new MethodGen (sourceMethod, clazz. getClassName (), cPoolGen );
InstructionList instructionList = methodGen. getInstructionList ();
 
// Delete the commands that initialize Benz from the original command list
InstructionHandle [] handles = instructionList. getInstructionHandles ();
InstructionHandle from = handles [1];
InstructionHandle to = handles [4];
InstructionList. delete (from, );
 
// Add the object to initialize the BMW
// There are three steps to create an object: 1. new. Create an object structure on heap and allocate memory;
// 2. dup, retain the reference to the newly created object on the operand stack, and then copy the reference;
// 3. invokespecial: identifies an object using the Copied object reference, and then calls its <init> method.
// After the preceding three steps, the object can be used. In this case, assign a value to the car variable.
InstructionHandle newHandle = instructionList
. Append (handles [0], new NEW (value ));
InstructionHandle dumpHandle = instructionList
. Append (newHandle, new DUP ());
InstructionHandle initHandle = instructionList
. Append (dumpHandle, new INVOKESPECIAL (methodIndex ));
InstructionList. append (initHandle, new PUTFIELD (fieldIndex ));
 
// Because the preceding instruction modification process has been performed, the new instruction method is used to replace the old instruction method.
ClassGen. replaceMethod (sourceMethod, methodGen. getMethod ());
 
// After the work is completed, a new class file is generated again.
JavaClass target = classGen. getJavaClass ();
Target. dump ("bin/bcel/changeimpl/CarHolder. class ");
 
} Catch (Exception e ){
// TODO: handle exception
}
 
}
 
}


Then, when you run CarHolder again, its results change. The above is an example of static modification. If you want to modify it dynamically, you can use your ClassFileTransformer to convert the byte array into a operable object. Then, like the above process, finally, return the byte array to the classloader.

Java code
Public byte [] transform (ClassLoader loader, String className,
Class <?> ClassBeingRedefined, ProtectionDomain protectionDomain,
Byte [] classfileBuffer) throws IllegalClassFormatException {
Try {
// Generate a JavaClass object from the byte array
InputStream inStream = new ByteArrayInputStream (classfileBuffer );
JavaClass jc = new ClassParser (inStream, className). parse ();

// The process is the same here

// Convert it to a byte array again and then give the classloader
JavaClass final = ***;
Return final. getBytes ();
} Catch (Exception e ){
// TODO: handle exception
}
Return classfileBuffer;
}


This is a simple example. We can see the "magic" performance from the above. Just like the magic of many frameworks in the past, many unknown things have been done behind the scenes. At the same time, for this example, I learned a lot of things that I seldom followed before, which is the biggest achievement.

Author: "One small step every day"
 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.