Javassist provides low-level apis to edit class files directly. In order to use these apis, you need to understand the Java bytecode and the format of the class files in detail, so that you can make various modifications to the class files through these apis.
If you want to generate a simple class files, javassist. The bytecode. ClassFileWriter may offer the best API. It offers than javassist. The bytecode. ClassFile faster speed, although this API is smaller.
1. Obtain the ClassFile object
Javassist. The bytecode. ClassFile object represents a class file. To get this object, CtClass’s getClassFile() should be called.
In addition, you can directly from the class file construct a javassit. The bytecode. ClassFile. For example,
BufferedInputStream fin
= new BufferedInputStream(new FileInputStream("Point.class"));
ClassFile cf = new ClassFile(new DataInputStream(fin));
Copy the code
These code snippets create a ClassFile object from Point.class.
A ClassFile object can be written back to the ClassFile. Write () of ClassFile writes the contents of the ClassFile to the DataOutputStream.
2. Add and remove members
ClassFile provides addField() and addMethod() to add fields or methods (note that constructors are treated as normal methods at the bytecode level). It provides addAttribute() to add an attribute to the class file.
Note that FieldInfo, MethodInfo, and AttributeInfo objects include a reference to ConstPool. ConstPool objects must be common to both ClassFile objects and FieldInfo(or MethodInfo, etc.) to which the object is added. In other words, FieldInfo (or MethodInfo, etc.) objects are prohibited from being shared between different ClassFile objects.
To remove a field or method from a ClassFile object, you must first get a java.util.List object that contains all the fields of the class. GetFields () and getMethods() return a list. Fields or methods can be removed by calling the remove() method of the List object. Attributes can also be removed in a similar manner. Call getAttributes() of FieldInfo or MethodInfo to get a list of attributes and remove it from the list.
3. Traverse the method body
A CodeIterator is useful for checking all bytecode instructions in a method body. To get the object, do the following:
ClassFile cf = ... ;
MethodInfo minfo = cf.getMethod("move"); // we assume move is not overloaded.
CodeAttribute ca = minfo.getCodeAttribute();
CodeIterator i = ca.iterator();
Copy the code
The CodeIterator object allows you to access bytecode instructions line by line from beginning to end. The following methods are part of the CodeIterator declaration:
-
void begin()
Move to the first instruction.
-
void move(int index)
Instruction to move to the specified index.
-
boolean hasNext()
Returns true if there are directives.
-
int next()
Returns the index of the next instruction.
Note that it does not return the index of the next opcode.
-
int byteAt(int index)
Returns the positive 8-bit value of the specified index.
-
int u16bitAt(int index)
Returns the positive 16-bit value of the specified index.
-
int write(byte[] code, int index)
Writes the byte array to the specified index.
-
void insert(int index, byte[] code)
Insert the byte array into the index. Branch offsets and so on are automatically adjusted.
The following code snippet shows all the instructions contained in the method body:
CodeIterator ci = ... ;
while (ci.hasNext()) {
int index = ci.next();
int op = ci.byteAt(index);
System.out.println(Mnemonic.OPCODE[op]);
}
Copy the code
4. Generate bytecode sequences
A Bytecode object represents a sequence of Bytecode instructions. It’s a mutable array of bytecode. Example code is as follows:
ConstPool cp = ... ; // constant pool table Bytecode b = new Bytecode(cp, 1, 0); b.addIconst(3); b.addReturn(CtClass.intType); CodeAttribute ca = b.toCodeAttribute();Copy the code
This produces the following sequence:
iconst_3
ireturn
Copy the code
You can also get the byte array containing this sequence by calling Bytecode’s get() method. The array obtained can be inserted into another code property.
Bytecode provides a number of methods to add special instructions to a sequence. It provides addOpcode() to add an 8-bit opcode and addIndex() to add an index. The 8-bit value of each Opcode is defined in the Opcode interface.
AddOpcode () and other methods for adding special instructions automatically maintain the maximum stack depth unless the control flow contains no branches. This value can be obtained by calling getMaxStack() on the Bytecode object. It is also reflected in the CodeAttribute object created by the ByteCode object. To recalculate the maximum stack depth of the method body, CodeAttribute’s computeMaxStack() method is called.
5. Annotations (meta tags)
Annotations are stored in class files as annotation properties that are not visible (or visible) at runtime. These properties can be from ClassFile MethodInfo, FieldInfo object. Call these objects getAttribute (AnnotationsAttribute invisibleTag). More details, you can view the javassist. The bytecode. AnnotationsAttribute classes and javassist. The bytecode. The annotation package javadoc handbook.
Javassist also allows you to access annotations through advanced apis. If you want to access annotations through CtClass, call CtClass or CtBehavior getAnnotations().