ASM:(8)MethodVisitor和MethodWriter
原文:
https://lsieun.github.io/java-asm-01/method-visitor-intro.html
https://lsieun.github.io/java-asm-01/method-visitor-examples.html
MethodVisitor介绍
通过调用ClassVisitor
类的visitMethod()
方法,会返回一个MethodVisitor
类型的对象。
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions);
在本文当中,我们将对MethodVisitor
类进行介绍。
从类的结构来说,MethodVisitor
类与ClassVisitor
类和FieldVisitor
类是非常相似性的。
class info
第一个部分,MethodVisitor
类是一个abstract
类。
public abstract class MethodVisitor {
}
fields
第二个部分,MethodVisitor
类定义的字段有哪些。
public abstract class MethodVisitor {
protected final int api;
protected MethodVisitor mv;
}
constructors
第三个部分,MethodVisitor
类定义的构造方法有哪些。
public abstract class MethodVisitor {
public MethodVisitor(final int api) {
this(api, null);
}
public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
this.api = api;
this.mv = methodVisitor;
}
}
methods
第四个部分,MethodVisitor
类定义的方法有哪些。在MethodVisitor
类当中,定义了许多的visitXxx()
方法,我们列出了其中的一些方法,内容如下:
public abstract class MethodVisitor {
public void visitCode();
public void visitInsn(final int opcode);
public void visitIntInsn(final int opcode, final int operand);
public void visitVarInsn(final int opcode, final int var);
public void visitTypeInsn(final int opcode, final String type);
public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor);
public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor,
final boolean isInterface);
public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle,
final Object... bootstrapMethodArguments);
public void visitJumpInsn(final int opcode, final Label label);
public void visitLabel(final Label label);
public void visitLdcInsn(final Object value);
public void visitIincInsn(final int var, final int increment);
public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels);
public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels);
public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions);
public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type);
public void visitMaxs(final int maxStack, final int maxLocals);
public void visitEnd();
// ......
}
对于这些visitXxx()
方法,它们分别有什么作用呢?我们有三方面的资料可能参阅:
- 第一,从ASM API的角度来讲,我们可以查看API文档,来具体了解某一个方法是要实现什么样的作用,该方法所接收的参数代表什么含义。
- 第二,从ClassFile的角度来讲,这些
visitXxxInsn()
方法的本质就是组装instruction的内容。我们可以参考Java Virtual Machine Specification的Chapter 6. The Java Virtual Machine Instruction Set部分。 - 第三,《Java ASM系列二:OPCODE》,主要是对opcode进行介绍。
方法的调用顺序
在MethodVisitor
类当中,定义了许多的visitXxx()
方法,这些方法的调用,也要遵循一定的顺序。
(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
visitCode
(
visitFrame |
visitXxxInsn |
visitLabel |
visitInsnAnnotation |
visitTryCatchBlock |
visitTryCatchAnnotation |
visitLocalVariable |
visitLocalVariableAnnotation |
visitLineNumber
)*
visitMaxs
]
visitEnd
我们可以把这些visitXxx()
方法分成三组:
- 第一组,在
visitCode()
方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容,这些内容并不是方法当中“必不可少”的一部分;我们暂时不去考虑这些内容,可以忽略这一组方法。 - 第二组,在
visitCode()
方法和visitMaxs()
方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()
方法,标志着方法体的开始,而visitMaxs()
方法,标志着方法体的结束。 - 第三组,是
visitEnd()
方法。这个visitEnd()
方法,是最后一个进行调用的方法。
对这些visitXxx()
方法进行精简之后,内容如下:
[
visitCode
(
visitFrame |
visitXxxInsn |
visitLabel |
visitTryCatchBlock
)*
visitMaxs
]
visitEnd
这些方法的调用顺序,可以记忆如下:
- 第一步,调用
visitCode()
方法,调用一次。 - 第二步,调用
visitXxxInsn()
方法,可以调用多次。对这些方法的调用,就是在构建方法的“方法体”。 - 第三步,调用
visitMaxs()
方法,调用一次。 - 第四步,调用
visitEnd()
方法,调用一次。
MethodWriter介绍
MethodWriter
类的父类是MethodVisitor
类。在ClassWriter
类里,visitMethod()
方法的实现就是通过MethodWriter
类来实现的。
class info
第一个部分,MethodWriter
类的父类是MethodVisitor
类。
需要注意的是,MethodWriter
类并不带有public
修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public
类一样被外部所使用。
final class MethodWriter extends MethodVisitor {
}
fields
第二个部分,MethodWriter
类定义的字段有哪些。
在MethodWriter
类当中,定义了很多的字段。下面的几个字段,是与方法的访问标识(access flag)、方法名(method name)和描述符(method descriptor)等直接相关的字段:
final class MethodWriter extends MethodVisitor {
private final int accessFlags;
private final int nameIndex;
private final String name;
private final int descriptorIndex;
private final String descriptor;
private Attribute firstAttribute;
}
这些字段与ClassFile
当中的method_info
也是对应的:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
下面的几个字段,是与“方法体”直接相关的几个字段:
final class MethodWriter extends MethodVisitor {
private int maxStack;
private int maxLocals;
private final ByteVector code = new ByteVector();
private Handler firstHandler;
private Handler lastHandler;
private final int numberOfExceptions;
private final int[] exceptionIndexTable;
private Attribute firstCodeAttribute;
}
这些字段对应于Code
属性结构:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 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];
}
constructors
第三个部分,MethodWriter
类定义的构造方法有哪些。
final class MethodWriter extends MethodVisitor {
MethodWriter(SymbolTable symbolTable, int access, String name, String descriptor, String signature, String[] exceptions, int compute) {
super(Opcodes.ASM9);
this.symbolTable = symbolTable;
this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
this.nameIndex = symbolTable.addConstantUtf8(name);
this.name = name;
this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
this.descriptor = descriptor;
this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
if (exceptions != null && exceptions.length > 0) {
numberOfExceptions = exceptions.length;
this.exceptionIndexTable = new int[numberOfExceptions];
for (int i = 0; i < numberOfExceptions; ++i) {
this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index;
}
} else {
numberOfExceptions = 0;
this.exceptionIndexTable = null;
}
this.compute = compute;
if (compute != COMPUTE_NOTHING) {
// Update maxLocals and currentLocals.
int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
if ((access & Opcodes.ACC_STATIC) != 0) {
--argumentsSize;
}
maxLocals = argumentsSize;
currentLocals = argumentsSize;
// Create and visit the label for the first basic block.
firstBasicBlock = new Label();
visitLabel(firstBasicBlock);
}
}
}
methods
第四个部分,MethodWriter
类定义的方法有哪些。
在MethodWriter
类当中,也有两个重要的方法:computeMethodInfoSize()
和putMethodInfo()
方法。这两个方法也是在ClassWriter
类的toByteArray()
方法内使用到。
final class MethodWriter extends MethodVisitor {
int computeMethodInfoSize() {
// ......
// 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
int size = 8;
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
if (code.length > 0) {
if (code.length > 65535) {
throw new MethodTooLargeException(symbolTable.getClassName(), name, descriptor, code.length);
}
symbolTable.addConstantUtf8(Constants.CODE);
// The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
// max_locals, code_length and attributes_count, plus the ByteCode and the exception table.
size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
if (stackMapTableEntries != null) {
boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
// 6 header bytes and 2 bytes for number_of_entries.
size += 8 + stackMapTableEntries.length;
}
// ......
}
if (numberOfExceptions > 0) {
symbolTable.addConstantUtf8(Constants.EXCEPTIONS);
size += 8 + 2 * numberOfExceptions;
}
//......
return size;
}
void putMethodInfo(final ByteVector output) {
boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
// ......
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
int attributeCount = 0;
if (code.length > 0) {
++attributeCount;
}
if (numberOfExceptions > 0) {
++attributeCount;
}
// ......
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
output.putShort(attributeCount);
if (code.length > 0) {
// 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and
// attributes_count, plus the ByteCode and the exception table.
int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler);
int codeAttributeCount = 0;
if (stackMapTableEntries != null) {
// 6 header bytes and 2 bytes for number_of_entries.
size += 8 + stackMapTableEntries.length;
++codeAttributeCount;
}
// ......
output
.putShort(symbolTable.addConstantUtf8(Constants.CODE))
.putInt(size)
.putShort(maxStack)
.putShort(maxLocals)
.putInt(code.length)
.putByteArray(code.data, 0, code.length);
Handler.putExceptionTable(firstHandler, output);
output.putShort(codeAttributeCount);
// ......
}
if (numberOfExceptions > 0) {
output
.putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
.putInt(2 + 2 * numberOfExceptions)
.putShort(numberOfExceptions);
for (int exceptionIndex : exceptionIndexTable) {
output.putShort(exceptionIndex);
}
}
// ......
}
}
MethodWriter类的使用
关于MethodWriter
类的使用,它主要出现在ClassWriter
类当中的visitMethod()
和toByteArray()
方法内。
visitMethod方法
在ClassWriter
类当中,visitMethod()
方法代码如下:
public class ClassWriter extends ClassVisitor {
public final MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodWriter methodWriter = new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
if (firstMethod == null) {
firstMethod = methodWriter;
} else {
lastMethod.mv = methodWriter;
}
return lastMethod = methodWriter;
}
}
toByteArray方法
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 methodsCount = 0;
MethodWriter methodWriter = firstMethod;
while (methodWriter != null) {
++methodsCount;
size += methodWriter.computeMethodInfoSize(); // 这里是对MethodWriter.computeMethodInfoSize()方法的调用
methodWriter = (MethodWriter) methodWriter.mv;
}
// ......
// 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(methodsCount);
boolean hasFrames = false;
boolean hasAsmInstructions = false;
methodWriter = firstMethod;
while (methodWriter != null) {
hasFrames |= methodWriter.hasFrames();
hasAsmInstructions |= methodWriter.hasAsmInstructions();
methodWriter.putMethodInfo(result); // 这里是对MethodWriter.putMethodInfo()方法的调用
methodWriter = (MethodWriter) methodWriter.mv;
}
// ......
// Third step: replace the ASM specific instructions, if any.
if (hasAsmInstructions) {
return replaceAsmInstructions(result.data, hasFrames);
} else {
return result.data;
}
}
}
总结
本文主要对MethodWriter
类进行介绍,内容总结如下:
- 第一点,对于
MethodWriter
类的各个不同部分进行介绍,以便从整体上来理解MethodWriter
类。 - 第二点,关于
MethodWriter
类的使用,它主要出现在ClassWriter
类当中的visitMethod()
和toByteArray()
方法内。 - 第三点,从应用ASM的角度来说,只需要知道
MethodWriter
类的存在就可以了,不需要深究;从理解ASM源码的角度来说,MethodWriter
类也是值得研究的。
MethodVisitor代码示例
示例一:()方法
在.class
文件中,构造方法的名字是
,它表示instance initialization method的缩写。
预期目标
public class HelloWorld {
}
或者:
public class HelloWorld {
public HelloWorld() {
super();
}
}
编码实现
public class MethodVisitorTest implements Opcodes {
public static void main(String[] args) throws Exception {
// (1) 生成byte[]内容
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv1.visitCode();
mv1.visitVarInsn(ALOAD, 0);
mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv1.visitInsn(RETURN);
mv1.visitMaxs(1, 1);
mv1.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
Frame的变化
对于HelloWorld
类中
方法对应的Instruction内容如下:
$ javap -c sample.HelloWorld
public sample.HelloWorld();
Code:
0: aload_0
1: invokespecial #9 // Method java/lang/Object."":()V
4: return
该方法对应的Frame变化情况如下:
()V
[uninitialized_this] []
[uninitialized_this] [uninitialized_this]
[sample/HelloWorld] []
[] []
在这里,我们看到一个很“不一样”的变量,就是uninitialized_this
,它就是一个“引用”,它指向的内存空间还没有初始化;等经过初始化之后,uninitialized_this
变量就变成this
变量。
小总结
通过上面的示例,我们注意四个知识点:
-
第一点,如何使用ClassWriter
类。
- 第一步,创建
ClassWriter
类的实例。 - 第二步,调用
ClassWriter
类的visitXxx()
方法。 - 第三步,调用
ClassWriter
类的toByteArray()
方法。
- 第一步,创建
-
第二点,在使用MethodVisitor类时,其中visitXxx()方法需要遵循的调用顺序。
- 第一步,调用
visitCode()
方法,调用一次 - 第二步,调用
visitXxxInsn()
方法,可以调用多次 - 第三步,调用
visitMaxs()
方法,调用一次 - 第四步,调用
visitEnd()
方法,调用一次
- 第一步,调用
-
第三点,在
.class
文件中,构造方法的名字是
。从Instruction的角度来讲,调用构造方法会用到invokespecial
指令。 -
第四点,从Frame的角度来讲,在构造方法
中,local variables当中索引为() 0
的位置存储的是什么呢?如果还没有进行初始化操作,就是uninitialized_this
变量;如果已经进行了初始化操作,就是this
变量。
┌─── static method ─────┼─── invokestatic
│
│ ┌─── invokevirtual (class)
│ │
│ │ ┌─── constructor
│ │ │
method invocation ───┼─── instance method ───┼─── invokespecial (class) ─────────┼─── private
│ │ │
│ │ └─── super
│ │
│ └─── invokeinterface (interface)
│
└─── dynamic method ────┼─── invokedynamic
示例二:方法
在.class
文件中,静态初始化方法的名字是
,它表示class initialization method的缩写。
预期目标
public class HelloWorld {
static {
System.out.println("class initialization method");
}
}
编码实现
public class MethodVisitorTest implements Opcodes {
public static void main(String[] args) throws Exception {
// (1) 生成byte[]内容
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv1.visitCode();
mv1.visitVarInsn(ALOAD, 0);
mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv1.visitInsn(RETURN);
mv1.visitMaxs(1, 1);
mv1.visitEnd();
}
{
MethodVisitor mv2 = cw.visitMethod(ACC_STATIC, "", "()V", null, null);
mv2.visitCode();
mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitLdcInsn("class initialization method");
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv2.visitInsn(RETURN);
mv2.visitMaxs(2, 0);
mv2.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
Frame的变化
对于HelloWorld
类中
方法对应的Instruction内容如下:
$ javap -c sample.HelloWorld
static {};
Code:
0: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #20 // String class initialization method
5: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
该方法对应的Frame变化情况如下:
()V
[] []
[] [java/io/PrintStream]
[] [java/io/PrintStream, java/lang/String]
[] []
[] []
小总结
通过上面的示例,我们注意三个知识点:
- 第一点,如何使用
ClassWriter
类。 - 第二点,在使用
MethodVisitor
类时,其中visitXxx()
方法需要遵循的调用顺序。 - 第三点,在
.class
文件中,静态初始化方法的名字是
,它的方法描述符是()V
。
示例三:创建对象
预期目标
假如有一个GoodChild
类,内容如下:
public class GoodChild {
public String name;
public int age;
public GoodChild(String name, int age) {
this.name = name;
this.age = age;
}
}
我们的预期目标是生成一个HelloWorld
类:
public class HelloWorld {
public void test() {
GoodChild child = new GoodChild("Lucy", 8);
}
}
编码实现
public class MethodVisitorTest implements Opcodes {
public static void main(String[] args) throws Exception {
// (1) 生成byte[]内容
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv1.visitCode();
mv1.visitVarInsn(ALOAD, 0);
mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv1.visitInsn(RETURN);
mv1.visitMaxs(1, 1);
mv1.visitEnd();
}
{
MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
mv2.visitCode();
mv2.visitTypeInsn(NEW, "sample/GoodChild");
mv2.visitInsn(DUP);
mv2.visitLdcInsn("Lucy");
mv2.visitIntInsn(BIPUSH, 8);
mv2.visitMethodInsn(INVOKESPECIAL, "sample/GoodChild", "", "(Ljava/lang/String;I)V", false);
mv2.visitVarInsn(ASTORE, 1);
mv2.visitInsn(RETURN);
mv2.visitMaxs(4, 2);
mv2.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
运行效果:
Frame的变化
对于HelloWorld
类中test()
方法对应的Instruction内容如下:
$ javap -c sample.HelloWorld
public void test();
Code:
0: new #11 // class sample/GoodChild
3: dup
4: ldc #13 // String Lucy
6: bipush 8
8: invokespecial #16 // Method sample/GoodChild."":(Ljava/lang/String;I)V
11: astore_1
12: return
该方法对应的Frame变化情况如下:
test()V
[sample/HelloWorld] []
[sample/HelloWorld] [uninitialized_sample/GoodChild]
[sample/HelloWorld] [uninitialized_sample/GoodChild, uninitialized_sample/GoodChild]
[sample/HelloWorld] [uninitialized_sample/GoodChild, uninitialized_sample/GoodChild, java/lang/String]
[sample/HelloWorld] [uninitialized_sample/GoodChild, uninitialized_sample/GoodChild, java/lang/String, int]
[sample/HelloWorld] [sample/GoodChild]
[sample/HelloWorld, sample/GoodChild] []
[] []
小总结
通过上面的示例,我们注意四个知识点:
- 第一点,如何使用
ClassWriter
类。 - 第二点,在使用
MethodVisitor
类时,其中visitXxx()
方法需要遵循的调用顺序。 - 第三点,从Instruction的角度来讲,创建对象的指令集合:
new
dup
invokespecial
- 第四点,从Frame的角度来讲,在创建新对象的时候,执行
new
指令之后,它是uninitialized状态,执行invokespecial
指令之后,它是一个“合格”的对象。
示例四:调用方法
预期目标
public class HelloWorld {
public void test(int a, int b) {
int val = Math.max(a, b); // 对static方法进行调用
System.out.println(val); // 对non-static方法进行调用
}
}
编码实现
public class MethodVisitorTest implements Opcodes {
public static void main(String[] args) throws Exception {
// (1) 生成byte[]内容
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv1.visitCode();
mv1.visitVarInsn(ALOAD, 0);
mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv1.visitInsn(RETURN);
mv1.visitMaxs(1, 1);
mv1.visitEnd();
}
{
MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(II)V", null, null);
mv2.visitCode();
mv2.visitVarInsn(ILOAD, 1);
mv2.visitVarInsn(ILOAD, 2);
mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Math", "max", "(II)I", false);
mv2.visitVarInsn(ISTORE, 3);
mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv2.visitVarInsn(ILOAD, 3);
mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
mv2.visitInsn(RETURN);
mv2.visitMaxs(2, 4);
mv2.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
执行结果:
验证结果
import java.lang.reflect.Method;
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("sample.HelloWorld");
Object obj = clazz.newInstance();
Method m = clazz.getDeclaredMethod("test", int.class, int.class);
m.invoke(obj, 10, 20);
}
}
Frame的变化
对于HelloWorld
类中test()
方法对应的Instruction内容如下:
$ javap -c sample.HelloWorld
public void test(int, int);
Code:
0: iload_1
1: iload_2
2: invokestatic #21 // Method java/lang/Math.max:(II)I
5: istore_3
6: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream;
9: iload_3
10: invokevirtual #33 // Method java/io/PrintStream.println:(I)V
13: return
该方法对应的Frame变化情况如下:
test(II)V
[sample/HelloWorld, int, int] []
[sample/HelloWorld, int, int] [int]
[sample/HelloWorld, int, int] [int, int]
[sample/HelloWorld, int, int] [int]
[sample/HelloWorld, int, int, int] []
[sample/HelloWorld, int, int, int] [java/io/PrintStream]
[sample/HelloWorld, int, int, int] [java/io/PrintStream, int]
[sample/HelloWorld, int, int, int] []
[] []
小总结
通过上面的示例,我们注意四个知识点:
- 第一点,如何使用
ClassWriter
类。 - 第二点,在使用
MethodVisitor
类时,其中visitXxx()
方法需要遵循的调用顺序。 - 第三点,从Instruction的角度来讲,调用static方法是使用
invokestatic
指令,调用non-static方法一般使用invokevirtual
指令。 - 第四点,从Frame的角度来讲,实现方法的调用,需要先将
this
变量和方法接收的参数放到operand stack上。
示例五:不调用visitMaxs()方法
在创建ClassWriter
对象时,使用了ClassWriter.COMPUTE_FRAMES
选项。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
使用ClassWriter.COMPUTE_FRAMES
后,ASM会自动计算max stacks、max locals和stack map frames的具体值。 从代码的角度来说,使用ClassWriter.COMPUTE_FRAMES
,会忽略我们在代码中visitMaxs()
方法和visitFrame()
方法传入的具体参数值。 换句话说,无论我们传入的参数值是否正确,ASM会帮助我们从新计算一个正确的值,代替我们在代码中传入的参数。
- 第1种情况,在创建
ClassWriter
对象时,flags
参数使用ClassWriter.COMPUTE_FRAMES
值,在调用mv.visitMaxs(0, 0)
方法之后,仍然能得到一个正确的.class
文件。 - 第2种情况,在创建
ClassWriter
对象时,flags
参数使用0
值,在调用mv.visitMaxs(0, 0)
方法之后,得到的.class
文件就不能正确运行。
需要注意的是,在创建ClassWriter
对象时,flags
参数使用ClassWriter.COMPUTE_FRAMES
值,我们可以给visitMaxs()
方法传入一个错误的值,但是不能省略对于visitMaxs()
方法的调用。 如果我们省略掉visitCode()
和visitEnd()
方法,生成的.class
文件也不会出错;当然,并不建议这么做。但是,如果我们省略掉对于visitMaxs()
方法的调用,生成的.class
文件就会出错。
如果省略掉对于visitMaxs()
方法的调用,会出现如下错误:
Exception in thread "main" java.lang.VerifyError: Operand stack overflow
示例六:不同的MethodVisitor交叉使用
假如我们有两个MethodVisitor
对象mv1
和mv2
,如下所示:
MethodVisitor mv1 = cw.visitMethod(...);
MethodVisitor mv2 = cw.visitMethod(...);
同时,我们也知道MethodVisitor
类里的visitXxx()
方法需要遵循一定的调用顺序:
- 第一步,调用
visitCode()
方法,调用一次 - 第二步,调用
visitXxxInsn()
方法,可以调用多次 - 第三步,调用
visitMaxs()
方法,调用一次 - 第四步,调用
visitEnd()
方法,调用一次
对于mv1
和mv2
这两个对象来说,它们的visitXxx()
方法的调用顺序是彼此独立的、不会相互干扰。
一般情况下,我们可以如下写代码,这样逻辑比较清晰:
MethodVisitor mv1 = cw.visitMethod(...);
mv1.visitCode(...);
mv1.visitXxxInsn(...)
mv1.visitMaxs(...);
mv1.visitEnd();
MethodVisitor mv2 = cw.visitMethod(...);
mv2.visitCode(...);
mv2.visitXxxInsn(...)
mv2.visitMaxs(...);
mv2.visitEnd();
但是,我们也可以这样来写代码:
MethodVisitor mv1 = cw.visitMethod(...);
MethodVisitor mv2 = cw.visitMethod(...);
mv1.visitCode(...);
mv2.visitCode(...);
mv2.visitXxxInsn(...)
mv1.visitXxxInsn(...)
mv1.visitMaxs(...);
mv1.visitEnd();
mv2.visitMaxs(...);
mv2.visitEnd();
在上面的代码中,mv1
和mv2
这两个对象的visitXxx()
方法交叉调用,这是可以的。 换句话说,只要每一个MethodVisitor
对象在调用visitXxx()
方法时,遵循了调用顺序,那结果就是正确的; 不同的MethodVisitor
对象,是相互独立的、不会彼此影响。
那么,可能有的同学会问:MethodVisitor
对象交叉使用有什么作用呢?有没有什么场景下的应用呢?回答是“有的”。 在ASM当中,有一个org.objectweb.asm.commons.StaticInitMerger
类,其中有一个MethodVisitor mergedClinitVisitor
字段,它就是一个很好的示例,在后续内容中,我们会介绍到这个类。
总结
- 第一点,要注意MethodVisitor类里visitXxx()的调用顺序:
- 第一步,调用
visitCode()
方法,调用一次 - 第二步,调用
visitXxxInsn()
方法,可以调用多次 - 第三步,调用
visitMaxs()
方法,调用一次 - 第四步,调用
visitEnd()
方法,调用一次
- 第一步,调用
- 第二点,在
.class
文件当中,构造方法的名字是
,静态初始化方法的名字是
。 - 第三点,针对方法里包含的Instruction内容,需要放到Frame当中才能更好的理解。对每一条Instruction来说,它都有可能引起local variables和operand stack的变化。
- 第四点,在使用
COMPUTE_FRAMES
的前提下,我们可以给visitMaxs()
方法参数传入错误的值,但不能忽略对于visitMaxs()
方法的调用。 - 第五点,不同的
MethodVisitor
对象,它们的visitXxx()
方法是彼此独立的,只要各自遵循方法的调用顺序,就能够得到正确的结果。