07 - ASM之ClassWriter
ClassWriter类
class info
ClassWriter的父类是ClassVisitor,因此ClassWriter类继承了visit()、visitField()、visitMethod()和visitEnd()等方法。
public class ClassWriter extends ClassVisitor {
}
fields
ClassWriter定义的字段有哪些。
public class ClassWriter extends ClassVisitor {
private int version;
private final SymbolTable symbolTable;
private int accessFlags;
private int thisClass;
private int superClass;
private int interfaceCount;
private int[] interfaces;
private FieldWriter firstField;
private FieldWriter lastField;
private MethodWriter firstMethod;
private MethodWriter lastMethod;
private Attribute firstAttribute;
//......
}
这些字段与ClassFile结构密切相关:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
constructors
ClassWriter定义的构造方法有两个,这里只关注其中一个,也就是只接收一个int类型参数的构造方法。在使用new关键字创建ClassWriter对象时,推荐使用COMPUTE_FRAMES参数。
public class ClassWriter extends ClassVisitor {
/* A flag to automatically compute the maximum stack size and the maximum number of local variables of methods. */
public static final int COMPUTE_MAXS = 1;
/* A flag to automatically compute the stack map frames of methods from scratch. */
public static final int COMPUTE_FRAMES = 2;
// flags option can be used to modify the default behavior of this class.
// Must be zero or more of COMPUTE_MAXS and COMPUTE_FRAMES.
public ClassWriter(final int flags) {
this(null, flags);
}
}
methods
- visitXxx()方法
在ClassWriter这个类当中,我们仍然是只关注其中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法。这些visitXxx()方法的调用,就是在为构建ClassFile提供“原材料”的过程。
public class ClassWriter extends ClassVisitor {
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces);
public FieldVisitor visitField( // 访问字段
final int access,
final String name,
final String descriptor,
final String signature,
final Object value);
public MethodVisitor visitMethod( // 访问方法
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions);
public void visitEnd();
// ......
}
- toByteArray()方法
在ClassWriter类当中,提供了一个toByteArray()方法。这个方法的作用是将“所有的努力”(对visitXxx()的调用)转换成byte[],而这些byte[]的内容就遵循ClassFile结构。
在toByteArray()方法的代码当中,通过三个步骤来得到byte[]:
- 第一步,计算size大小。这个size就是表示byte[]的最终的长度是多少。
- 第二步,将数据填充到byte[]当中。
- 第三步,将byte[]数据返回。
public class ClassWriter extends ClassVisitor {
public byte[] toByteArray() {
// First step: compute the size in bytes of the ClassFile structure.
// The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version,
// constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count,
// methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too.
int size = 24 + 2 * interfaceCount;
int fieldsCount = 0;
FieldWriter fieldWriter = firstField;
while (fieldWriter != null) {
++fieldsCount;
size += fieldWriter.computeFieldInfoSize();
fieldWriter = (FieldWriter) fieldWriter.fv;
}
int methodsCount = 0;
MethodWriter methodWriter = firstMethod;
while (methodWriter != null) {
++methodsCount;
size += methodWriter.computeMethodInfoSize();
methodWriter = (MethodWriter) methodWriter.mv;
}
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
int attributesCount = 0;
// ......
if (firstAttribute != null) {
attributesCount += firstAttribute.getAttributeCount();
size += firstAttribute.computeAttributesSize(symbolTable);
}
// IMPORTANT: this must be the last part of the ClassFile size computation, because the previous
// statements can add attribute names to the constant pool, thereby changing its size!
size += symbolTable.getConstantPoolLength();
// Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
// dynamic resizes) and fill it with the ClassFile content.
ByteVector result = new ByteVector(size);
result.putInt(0xCAFEBABE).putInt(version);
symbolTable.putConstantPool(result);
int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
result.putShort(interfaceCount);
for (int i = 0; i < interfaceCount; ++i) {
result.putShort(interfaces[i]);
}
result.putShort(fieldsCount);
fieldWriter = firstField;
while (fieldWriter != null) {
fieldWriter.putFieldInfo(result);
fieldWriter = (FieldWriter) fieldWriter.fv;
}
result.putShort(methodsCount);
boolean hasFrames = false;
boolean hasAsmInstructions = false;
methodWriter = firstMethod;
while (methodWriter != null) {
hasFrames |= methodWriter.hasFrames();
hasAsmInstructions |= methodWriter.hasAsmInstructions();
methodWriter.putMethodInfo(result);
methodWriter = (MethodWriter) methodWriter.mv;
}
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
result.putShort(attributesCount);
// ......
if (firstAttribute != null) {
firstAttribute.putAttributes(symbolTable, result);
}
// Third step: replace the ASM specific instructions, if any.
if (hasAsmInstructions) {
return replaceAsmInstructions(result.data, hasFrames);
} else {
return result.data;
}
}
}
创建ClassWriter对象
推荐使用COMPUTE_FRAMES
在创建ClassWriter对象的时候,要指定一个flags参数,它可以选择的值有三个:
- 第一个,可以选取的值是0。ASM不会自动计算max stacks和max locals,也不会自动计算stack map frames。
- 第二个,可以选取的值是ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。
- 第三个,可以选取的值是ClassWriter.COMPUTE_FRAMES(推荐使用)。ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
COMPUTE_FRAMES
在创建ClassWriter对象的时候,使用ClassWriter.COMPUTE_FRAMES,ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
首先,来看一下max stacks和max locals。在ClassFile结构中,每一个方法都用method_info来表示,而方法里定义的代码则使用Code属性来表示,其结构如下:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack; // 这里是max stacks
u2 max_locals; // 这里是max locals
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_MAXS或ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算Code结构中max_stack和max_locals的值。
接着,来看一下stack map frames。在Code结构里,可能有多个attributes,其中一个可能就是StackMapTable_attribute。StackMapTable_attribute结构,就是stack map frame具体存储格式,它的主要作用是对ByteCode进行类型检查。
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算StackMapTable_attribute的内容。

我们推荐使用ClassWriter.COMPUTE_FRAMES。因为ClassWriter.COMPUTE_FRAMES这个选项,能够让ASM帮助我们自动计算max stacks、max locals和stack map frame的具体内容。
- 如果将flags参数的取值为0,那么我们就必须要提供正确的max stacks、max locals和stack map frame的值;
- 如果将flags参数的取值为ClassWriter.COMPUTE_MAXS,那么ASM会自动帮助我们计算max stacks和max locals,而我们则需要提供正确的stack map frame的值。
那么,ASM为什么会提供0和ClassWriter.COMPUTE_MAXS这两个选项呢?因为ASM在计算这些值的时候,要考虑各种各样不同的情况,所以它的算法相对来说就比较复杂,因而执行速度也会相对较慢。同时,ASM也鼓励开发者去研究更好的算法;如果开发者有更好的算法,就可以不去使用ClassWriter.COMPUTE_FRAMES,这样就能让程序的执行效率更高效。
但是,不得不说,要想计算max stacks、max locals和stack map frames,也不是一件容易的事情。出于方便的目的,就推荐大家使用ClassWriter.COMPUTE_FRAMES。在大多数情况下,ClassWriter.COMPUTE_FRAMES都能帮我们计算出正确的值。在少数情况下,ClassWriter.COMPUTE_FRAMES也可能会出错,比如说,有些代码经过混淆(obfuscate)处理,它里面的stack map frame会变更非常复杂,使用ClassWriter.COMPUTE_FRAMES就会出现错误的情况。针对这种少数的情况,我们可以在不改变原有stack map frame的情况下,使用ClassWriter.COMPUTE_MAXS,让ASM只帮助我们计算max stacks和max locals。
使用ClassWriter类
使用ClassWriter生成一个Class文件,可以大致分成三个步骤:
- 第一步,创建ClassWriter对象。
- 第二步,调用ClassWriter对象的visitXxx()方法。
- 第三步,调用ClassWriter对象的toByteArray()方法。
import org.objectweb.asm.ClassWriter;
import static org.objectweb.asm.Opcodes.*;
public class HelloWorldGenerateCore {
public static byte[] dump () throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit();
cw.visitField();
cw.visitMethod();
cw.visitEnd(); // 注意,最后要调用visitEnd()方法
// (3) 调用toByteArray()方法
byte[] bytes = cw.toByteArray();
return bytes;
}
}
小结
本文对ClassWriter类进行了介绍,说明了该类的作用以及使用方式,希望对你能有所帮助