在前面的文章中,我们分析了Class 这个字节码文件的格式,知道了字节码的作用,那么我们就可以直接生成字节码文件,加载到当前的 JVM 中运行,这个在AOP 场景中经常用到。
当然直接手动写字节码难度比较大,太过麻烦。这里就介绍一个非常重要也非常高效的字节码生成框架ASM。
Java8中Lambda表达式,也是通过ASM动态生成类的。
一. ASM 介绍
ASM可以高效地生成.class字节码文件,分为两种方式:
- 核心
API: 流式读取字节码文件内容,并通过访问器的模式,将读取到内容数据暴露出去,类比解析XML文件中的SAX方式。 - 树形
API: 底层就是采用核心API,只不过它将读取到的内容节点,保存在内存中,形成一个树形结构,类比解析XML文件中的DOM方式。
因此我们只要了解核心API 原理就行了,主要就是以下几个方面:
- 各种访问器
Visitor,用来接收到字节码中的各部分数据。 - 各种写入器
Writer,它们是Visitor子类,接收到数据,然后写入到字节数组中,生成一个字节码文件。 -
ClassReader,它比较特殊,就是用来读取一个字节码文件,并且可以将字节码中的各部分数据发送给各个访问器Visitor。
因为我们之前介绍
JVM8的字节码文件格式,因此我们选择ASM也是适配JVM8,其实就是openjdk8中源码。
如果对字节码中结构和信息不清楚的,请先阅读这些文章:字节码文件(ClassFile)详解,字节码的属性,指令集。
二. 访问器Visitor
2.1 类访问器ClassVisitor
2.1.1 ClassVisitor
public abstract class ClassVisitor {
protected final int api;
protected ClassVisitor cv;
public ClassVisitor(final int api) {
this(api, null);
}
public ClassVisitor(final int api, final ClassVisitor cv) {
if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
throw new IllegalArgumentException();
}
this.api = api;
this.cv = cv;
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
if (cv != null) {
cv.visit(version, access, name, signature, superName, interfaces);
}
}
public void visitSource(String source, String debug) {
if (cv != null) {
cv.visitSource(source, debug);
}
}
public void visitOuterClass(String owner, String name, String desc) {
if (cv != null) {
cv.visitOuterClass(owner, name, desc);
}
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (cv != null) {
return cv.visitAnnotation(desc, visible);
}
return null;
}
public AnnotationVisitor visitTypeAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (cv != null) {
return cv.visitTypeAnnotation(typeRef, typePath, desc, visible);
}
return null;
}
public void visitAttribute(Attribute attr) {
if (cv != null) {
cv.visitAttribute(attr);
}
}
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
if (cv != null) {
cv.visitInnerClass(name, outerName, innerName, access);
}
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if (cv != null) {
return cv.visitField(access, name, desc, signature, value);
}
return null;
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (cv != null) {
return cv.visitMethod(access, name, desc, signature, exceptions);
}
return null;
}
public void visitEnd() {
if (cv != null) {
cv.visitEnd();
}
}
}
可以看到ClassVisitor 中又有一个 ClassVisitor cv 成员变量,ClassVisitor 中方法实现都是调用 ClassVisitor cv 这个成员变量对应方法。
2.1.2 ClassFile 结构
首先让我们回忆一下,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];
}
分为魔数,版本,常量池,访问标志,类名,继承的父类名,实现的接口集合,字段列表,方法列表和ClassFile的属性列表。
2.1.3 ClassVisitor 中的方法
方法的调用顺序如下:
visit
[ visitSource ]
[ visitOuterClass ]
( visitAnnotation | visitTypeAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd.
-
void visit(int version, int access, String name, String signature,String superName, String[] interfaces)字节码文件的版本
version,访问标志access,类名name,类泛型签名属性signature,继承的父类名superName和 实现的接口集合interfaces。 -
void visitSource(String source, String debug)字节码文件中
SourceFile和SourceDebugExtension这两个属性信息。 -
void visitOuterClass(String owner, String name, String desc)字节码文件中
EnclosingMethod属性信息。 -
AnnotationVisitor visitAnnotation(String desc, boolean visible)- 字节码文件中
RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性信息。 -
desc表示注解类名的描述符,visible表示这个注解可不可见。
- 字节码文件中
- `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
- 字节码文件中
RuntimeVisibleTypeAnnotations和RuntimeInvisibleTypeAnnotations属性信息。 -
desc表示注解类名的描述符,visible表示这个注解可不可见。
- 字节码文件中
-
void visitAttribute(Attribute attr)这个是字节码文件中自定义的属性,不是当前
JVM规定的属性值。 -
void visitInnerClass(String name, String outerName, String innerName, int access)字节码文件中
InnerClasses属性信息。 -
FieldVisitor visitField(int access, String name, String desc, String signature, Object value)- 字节码文件中字段列表。
- 这里包含了字段的访问标志
access,字段名name,字段描述符desc, 字段签名信息signature和字段常量属性ConstantValue的值value。
-
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)- 字节码文件中方法列表。
- 这里包含了方法的访问标志
access,方法名name,方法描述符desc, 方法签名信息signature和方法抛出异常列表exceptions。
void visitEnd()
表示字节码文件访问结束。
2.2 注解访问器AnnotationVisitor
AnnotationVisitor主要是用来接收注解相关属性的,主要就是 RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault,RuntimeVisibleTypeAnnotations和RuntimeInvisibleTypeAnnotations这七个属性了。
注解的类名信息已经在
AnnotationVisitor visitAnnotation(String desc, boolean visible)和AnnotationVisitor visitTypeAnnotation(...)中获取了。
这个AnnotationVisitor只会获取注解的键值对信息。
2.2.1 AnnotationVisitor
public abstract class AnnotationVisitor {
protected final int api;
protected AnnotationVisitor av;
public AnnotationVisitor(final int api) {
this(api, null);
}
public AnnotationVisitor(final int api, final AnnotationVisitor av) {
if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
throw new IllegalArgumentException();
}
this.api = api;
this.av = av;
}
public void visit(String name, Object value) {
if (av != null) {
av.visit(name, value);
}
}
public void visitEnum(String name, String desc, String value) {
if (av != null) {
av.visitEnum(name, desc, value);
}
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
if (av != null) {
return av.visitAnnotation(name, desc);
}
return null;
}
public AnnotationVisitor visitArray(String name) {
if (av != null) {
return av.visitArray(name);
}
return null;
}
public void visitEnd() {
if (av != null) {
av.visitEnd();
}
}
}
和 ClassVisitor 一样,也有一个 AnnotationVisitor av 成员变量,来实现AnnotationVisitor 方法。
2.2.2 annotation 结构
还记得注解的数据结构么:
annotation {
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs[num_element_value_pairs];
}
element_value {
u1 tag;
union {
u2 const_value_index;
{
u2 type_name_index;
u2 const_name_index;
} enum_const_value;
u2 class_info_index;
annotation annotation_value;
{
u2 num_values;
element_value values[num_values];
} array_value;
} value;
}
- 其中
type_index表示注解描述符常量池索引。element_value_pairs[num_element_value_pairs]表示注解键值对信息。- 注解键值分为五种类型,常量
const_value_index,枚举enum_const_value,类class_info_index,注解annotation_value和数组类型array_value。
2.2.3 AnnotationVisitor 中的方法
方法调用顺序如下:
( visit | visitEnum | visitAnnotation | visitArray )*
visitEnd
-
void visit(String name, Object value)- 包括注解中常量类型键值对信息。
- 还包括注解中类
class_info_index类型键值对信息。
-
void visitEnum(String name, String desc, String value)注解中枚举类型键值对信息。
-
AnnotationVisitor visitAnnotation(String name, String desc)注解中注解类型键值对信息,返回这个注解类型键值的注解访问器
AnnotationVisitor。 -
AnnotationVisitor visitArray(String name)注解中数组类型键值对信息,也返回一个
AnnotationVisitor。
其实数组类型和注解类型的区别,注解类型有多个键值对集合,而数组类型只有键值的列表,没有键name。 -
void visitEnd()表示注解访问结束。
2.3 字段访问器 FieldVisitor
- 字段的访问标志
access,字段名name,字段描述符desc, 字段签名信息signature和字段常量属性ConstantValue的值value已经在visitField(...)方法中获取了。FieldVisitor只有字段其他属性的获取。
2.3.1 FieldVisitor
public abstract class FieldVisitor {
protected final int api;
protected FieldVisitor fv;
public FieldVisitor(final int api) {
this(api, null);
}
public FieldVisitor(final int api, final FieldVisitor fv) {
if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
throw new IllegalArgumentException();
}
this.api = api;
this.fv = fv;
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (fv != null) {
return fv.visitAnnotation(desc, visible);
}
return null;
}
public AnnotationVisitor visitTypeAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (fv != null) {
return fv.visitTypeAnnotation(typeRef, typePath, desc, visible);
}
return null;
}
public void visitAttribute(Attribute attr) {
if (fv != null) {
fv.visitAttribute(attr);
}
}
public void visitEnd() {
if (fv != null) {
fv.visitEnd();
}
}
}
2.3.2 field_info 结构
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
字段中属性有:
-
ConstantValue: 通过final关键字修饰的字段对应的常量值。 -
Synthetic: 标志字段,方法和类由编译器自动生成。 -
Deprecated: 声明字段,方法和类将弃用。 -
Signature: 记录字段,方法和类的泛型信息。 -
RuntimeVisibleAnnotations: 可见的字段,方法和类注解。 -
RuntimeInvisibleAnnotations: 不可见的字段,方法和类注解。 -
RuntimeVisibleTypeAnnotations: 可见的类型注解,主要用于实现JSR 308。 -
RuntimeInvisibleTypeAnnotations: 不可见的类型注解,主要用于实现JSR 308。
2.3.3 FieldVisitor 中的方法
方法调用顺序如下:
( visitAnnotation | visitTypeAnnotation | visitAttribute )*
visitEnd
-
AnnotationVisitor visitAnnotation(String desc, boolean visible)- 字节码文件中
RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性信息。 -
desc表示注解类名的描述符,visible表示这个注解可不可见。
- 字节码文件中
- `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
- 字节码文件中
RuntimeVisibleTypeAnnotations和RuntimeInvisibleTypeAnnotations属性信息。 -
desc表示注解类名的描述符,visible表示这个注解可不可见。
- 字节码文件中
-
void visitAttribute(Attribute attr)这个是字节码文件中自定义的属性,不是当前
JVM规定的属性值。 -
void visitEnd()表示字段访问结束。
2.4 方法访问器 MethodVisitor
2.4.1 MethodVisitor
public abstract class MethodVisitor {
protected final int api;
protected MethodVisitor mv;
public MethodVisitor(final int api) {
this(api, null);
}
public MethodVisitor(final int api, final MethodVisitor mv) {
if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
throw new IllegalArgumentException();
}
this.api = api;
this.mv = mv;
}
public void visitParameter(String name, int access) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (mv != null) {
mv.visitParameter(name, access);
}
}
public AnnotationVisitor visitAnnotationDefault() {
if (mv != null) {
return mv.visitAnnotationDefault();
}
return null;
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (mv != null) {
return mv.visitAnnotation(desc, visible);
}
return null;
}
public AnnotationVisitor visitTypeAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (mv != null) {
return mv.visitTypeAnnotation(typeRef, typePath, desc, visible);
}
return null;
}
public AnnotationVisitor visitParameterAnnotation(int parameter,
String desc, boolean visible) {
if (mv != null) {
return mv.visitParameterAnnotation(parameter, desc, visible);
}
return null;
}
public void visitAttribute(Attribute attr) {
if (mv != null) {
mv.visitAttribute(attr);
}
}
public void visitCode() {
if (mv != null) {
mv.visitCode();
}
}
public void visitFrame(int type, int nLocal, Object[] local, int nStack,
Object[] stack) {
if (mv != null) {
mv.visitFrame(type, nLocal, local, nStack, stack);
}
}
public void visitInsn(int opcode) {
if (mv != null) {
mv.visitInsn(opcode);
}
}
public void visitIntInsn(int opcode, int operand) {
if (mv != null) {
mv.visitIntInsn(opcode, operand);
}
}
public void visitVarInsn(int opcode, int var) {
if (mv != null) {
mv.visitVarInsn(opcode, var);
}
}
public void visitTypeInsn(int opcode, String type) {
if (mv != null) {
mv.visitTypeInsn(opcode, type);
}
}
public void visitFieldInsn(int opcode, String owner, String name,
String desc) {
if (mv != null) {
mv.visitFieldInsn(opcode, owner, name, desc);
}
}
@Deprecated
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
if (api >= Opcodes.ASM5) {
boolean itf = opcode == Opcodes.INVOKEINTERFACE;
visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
if (mv != null) {
mv.visitMethodInsn(opcode, owner, name, desc);
}
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if (api < Opcodes.ASM5) {
if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
throw new IllegalArgumentException(
"INVOKESPECIALTATIC on interfaces require ASM 5");
}
visitMethodInsn(opcode, owner, name, desc);
return;
}
if (mv != null) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
Object... bsmArgs) {
if (mv != null) {
mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
}
}
public void visitJumpInsn(int opcode, Label label) {
if (mv != null) {
mv.visitJumpInsn(opcode, label);
}
}
public void visitLabel(Label label) {
if (mv != null) {
mv.visitLabel(label);
}
}
public void visitLdcInsn(Object cst) {
if (mv != null) {
mv.visitLdcInsn(cst);
}
}
public void visitIincInsn(int var, int increment) {
if (mv != null) {
mv.visitIincInsn(var, increment);
}
}
public void visitTableSwitchInsn(int min, int max, Label dflt,
Label... labels) {
if (mv != null) {
mv.visitTableSwitchInsn(min, max, dflt, labels);
}
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
if (mv != null) {
mv.visitLookupSwitchInsn(dflt, keys, labels);
}
}
public void visitMultiANewArrayInsn(String desc, int dims) {
if (mv != null) {
mv.visitMultiANewArrayInsn(desc, dims);
}
}
public AnnotationVisitor visitInsnAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (mv != null) {
return mv.visitInsnAnnotation(typeRef, typePath, desc, visible);
}
return null;
}
public void visitTryCatchBlock(Label start, Label end, Label handler,
String type) {
if (mv != null) {
mv.visitTryCatchBlock(start, end, handler, type);
}
}
public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
TypePath typePath, String desc, boolean visible) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (mv != null) {
return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible);
}
return null;
}
public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) {
if (mv != null) {
mv.visitLocalVariable(name, desc, signature, start, end, index);
}
}
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
TypePath typePath, Label[] start, Label[] end, int[] index,
String desc, boolean visible) {
if (api < Opcodes.ASM5) {
throw new RuntimeException();
}
if (mv != null) {
return mv.visitLocalVariableAnnotation(typeRef, typePath, start,
end, index, desc, visible);
}
return null;
}
public void visitLineNumber(int line, Label start) {
if (mv != null) {
mv.visitLineNumber(line, start);
}
}
public void visitMaxs(int maxStack, int maxLocals) {
if (mv != null) {
mv.visitMaxs(maxStack, maxLocals);
}
}
public void visitEnd() {
if (mv != null) {
mv.visitEnd();
}
}
}
2.4.2 method_info 结构
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法中的属性有:
-
Code: 方法对应字节码指令。 -
Exceptions: 方法抛出的异常列表。 -
RuntimeVisibleParameterAnnotations: 可见的方法参数注解。 -
RuntimeInvisibleParameterAnnotations: 不可见的方法参数注解。 -
AnnotationDefault: 记录注解类元素默认值。 -
MethodParameters: 将方法参数参数名编译到字节码文件中(编译时加上 -parameters 参数)。 -
Synthetic: 标志字段,方法和类由编译器自动生成。 -
Deprecated: 声明字段,方法和类将弃用。 -
Signature: 记录字段,方法和类的泛型信息。 -
RuntimeVisibleAnnotations: 可见的字段,方法和类注解。 -
RuntimeInvisibleAnnotations: 不可见的字段,方法和类注解。 -
RuntimeVisibleTypeAnnotations: 可见的类型注解,主要用于实现JSR 308。 -
RuntimeInvisibleTypeAnnotations: 不可见的类型注解,主要用于实现JSR 308。
2.4.3 MethodVisitor 中的方法
方法调用顺序如下
( visitParameter )*
[ visitAnnotationDefault ]
( visitAnnotation | visitTypeAnnotation | visitAttribute )*
[ visitCode ( visitFrame | visitXInsn | visitLabel | visitInsnAnnotation | visitTryCatchBlock | visitTryCatchBlockAnnotation | visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )* visitMaxs ]
visitEnd
可以看出
MethodVisitor中的方法比较复杂,主要是因为Code属性数据,需要生成方法指令集。
-
void visitParameter(String name, int access)方法中
MethodParameters属性,获取方法参数的参数名name和参数访问标志access。 -
AnnotationVisitor visitAnnotationDefault()方法中
AnnotationDefault属性,只会出现在注解类中,注解类方法默认返回值信息。 -
AnnotationVisitor visitAnnotation(String desc, boolean visible)方法中
RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性 -
AnnotationVisitor visitTypeAnnotation(int typeRef,TypePath typePath, String desc, boolean visible)方法中
RuntimeVisibleTypeAnnotations和RuntimeInvisibleTypeAnnotations属性 -
AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible)方法中
RuntimeVisibleParameterAnnotations和RuntimeInvisibleParameterAnnotations属性。 -
void visitAttribute(Attribute attr)方法中自定义属性。
-
void visitEnd():方法访问结束。
2.4.3 Code 属性相关方法
我们知道方法的操作其实就是对应一个一个的JVM 指令,而这些指令就存在 Code 属性中。
2.4.3.1 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];
}
Code 中包含的属性有:
-
LineNumberTable:java源码中方法中字节指令和行号对应关系。 -
LocalVariableTable: 方法中局部变量描述。 -
LocalVariableTypeTable: 方法中泛型局部变量描述。 -
StackMapTable: 记录数据供类型检查验证器检查,来验证方法局部变量表和操作数栈所需类型是否匹配。 -
RuntimeVisibleTypeAnnotations和RuntimeInvisibleTypeAnnotations
2.4.3.2 Code 属性对应方法
方法流程如下:
visitCode
( visitFrame | visitXInsn | visitLabel | visitInsnAnnotation | visitTryCatchBlock | visitTryCatchBlockAnnotation | visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )*
visitMaxs
-
void visitCode(): 访问Code属性开始。 -
void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack): 访问StackMapTable属性。关于
StackMapTable详细说明请看字节码的属性。 -
void visitInsn(int opcode): 访问零操作数指令这些指令有
NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT. -
void visitIntInsn(int opcode, int operand): 访问操作数就是整型值的指令。- 也就是说这个操作数不代表索引这些,比如
BIPUSH指令,就是将一个byte类型数放入操作数栈。 - 这些指令有
BIPUSH,SIPUSH和NEWARRAY。
- 也就是说这个操作数不代表索引这些,比如
-
void visitVarInsn(int opcode, int var): 访问局部变量指令这些指令包括
ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET -
void visitTypeInsn(int opcode, String type): 访问类型指令。这些指令包括
NEW, ANEWARRAY, CHECKCAST or INSTANCEOF。 -
void visitFieldInsn(int opcode, String owner, String name, String desc): 访问字段指令。- 字段所属的类名
owner,字段名name和字段描述符desc。 - 这些指令包括
GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD。
- 字段所属的类名
-
void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf): 访问方法指令。- 方法所属的类名
owner,方法名name,方法描述符desc和是否为接口方法itf。 - 这些指令包括
INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE。
- 方法所属的类名
-
visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs): 访问动态方法指令。- 动态方法名
name,动态方法描述desc,引导方法处理器bsm和引导方法处理器参数bsmArgs。 - 这个指令就是
invokedynamic。
- 动态方法名
-
visitJumpInsn(int opcode, Label label): 访问程序跳转指令。这些指令包括
IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL。 -
void visitLabel(Label label): 主要是通过label记录当前Code指令的地址。 -
void visitLdcInsn(Object cst): 访问LDC指令。 -
void visitIincInsn(int var, int increment): 访问IINC指令。 -
void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels): 访问TABLESWITCH指令。 -
void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels): 访问LOOKUPSWITCH指令。 -
void visitMultiANewArrayInsn(String desc, int dims): 访问MULTIANEWARRAY指令。 -
void visitTryCatchBlock(Label start, Label end, Label handler, String type): 访问try catch代码块,即Code属性中exception_table中的数据。 -
AnnotationVisitor visitInsnAnnotation(int typeRef,TypePath typePath, String desc, boolean visible): 访问RuntimeVisibleTypeAnnotations属性值。 -
void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index): 访问LocalVariableTable和LocalVariableTypeTable属性值。 -
void visitLineNumber(int line, Label start): 访问LineNumberTable属性值。 -
void visitMaxs(int maxStack, int maxLocals): 访问Code属性中max_stack和max_locals值。
三. 类读取器 ClassReader
ClassReader 可以读取字节码文件,并分发给各个访问器 Visitor。
3.1 初始化方法
public ClassReader(final InputStream is) throws IOException {
this(readClass(is, false));
}
public ClassReader(final String name) throws IOException {
this(readClass(
ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
+ ".class"), true));
}
public ClassReader(final byte[] b) {
this(b, 0, b.length);
}
public ClassReader(final byte[] b, final int off, final int len) {
this.b = b;
// checks the class version
// 字节码版本超过 1.8,直接报错
if (readShort(off + 6) > Opcodes.V1_8) {
throw new IllegalArgumentException();
}
// parses the constant pool
// 解析常量池
items = new int[readUnsignedShort(off + 8)];
int n = items.length;
strings = new String[n];
int max = 0;
// 10个字节包括 u4 magic,u2 minor_version,u2 major_version 和 u2 constant_pool_count
int index = off + 10;
for (int i = 1; i < n; ++i) {
// +1 是为了去除常量池类型 u1 tag
// items 中保存常量数据开始位置
items[i] = index + 1;
int size;
switch (b[index]) {
case ClassWriter.FIELD:
case ClassWriter.METH:
case ClassWriter.IMETH:
case ClassWriter.INT:
case ClassWriter.FLOAT:
case ClassWriter.NAME_TYPE:
case ClassWriter.INDY:
size = 5;
break;
case ClassWriter.LONG:
case ClassWriter.DOUBLE:
size = 9;
++i;
break;
case ClassWriter.UTF8:
size = 3 + readUnsignedShort(index + 1);
if (size > max) {
max = size;
}
break;
case ClassWriter.HANDLE:
size = 4;
break;
// case ClassWriter.CLASS:
// case ClassWriter.STR:
// case ClassWriter.MTYPE
default:
size = 3;
break;
}
index += size;
}
// 记录最大 UTF8 类型常量的字节数。
maxStringLength = max;
// the class header information starts just after the constant pool
// header 就是 access_flags 开始位置
header = index;
}
主要就是获取字节码文件的字节数组byte[] b, 然后解析字节码的常量池,得到三个成员变量:
-
items: 记录常量池中所有常量数据在字节数组b的位置,方便常量数据。 -
strings: 用来缓存UTF8类型常量的字符串。 -
header: 字节码中access_flags开始位置。
3.2 accept 方法
accept 方法就是接收一个 ClassVisitor 对象,将字节码文件中的数据发送到 类访问器ClassVisitor 。
接下来我们一步一步分析:
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
int u = header; // current offset in the class file
char[] c = new char[maxStringLength]; // buffer used to read strings
Context context = new Context();
context.attrs = attrs;
context.flags = flags;
context.buffer = c;
// reads the class declaration
int access = readUnsignedShort(u);
String name = readClass(u + 2, c);
String superClass = readClass(u + 4, c);
String[] interfaces = new String[readUnsignedShort(u + 6)];
u += 8;
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(u, c);
u += 2;
}
.......
}
首先读取了字节码文件中的访问标志
access,类名name,父类名superClass和接口列表interfaces。
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
......
Attribute attributes = null;
u = getAttributes();
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("SourceFile".equals(attrName)) {
sourceFile = readUTF8(u + 8, c);
} else if ("InnerClasses".equals(attrName)) {
innerClasses = u + 8;
}
......
}
- 先通过
getAttributes()方法获取类属性列表的开始位置。- 然后获取属性个数,通过遍历获取所有类属性值。
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
......
// visits the class declaration
classVisitor.visit(readInt(items[1] - 7), access, name, signature,
superClass, interfaces);
// visits the source and debug info
if ((flags & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebug != null)) {
classVisitor.visitSource(sourceFile, sourceDebug);
}
// visits the outer class
if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner, enclosingName,
enclosingDesc);
}
// visits the class annotations and type annotations
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// visits the attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
classVisitor.visitAttribute(attributes);
attributes = attr;
}
// visits the inner classes
if (innerClasses != 0) {
int v = innerClasses + 2;
for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
classVisitor.visitInnerClass(readClass(v, c),
readClass(v + 2, c), readUTF8(v + 4, c),
readUnsignedShort(v + 6));
v += 8;
}
}
// visits the fields and methods
u = header + 10 + 2 * interfaces.length;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readField(classVisitor, context, u);
}
u += 2;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readMethod(classVisitor, context, u);
}
// visits the end of the class
classVisitor.visitEnd();
}
这里就可以看到
ClassVisitor中方法访问顺序了。
3.3 readAnnotationValues 方法
private int readAnnotationValues(int v, final char[] buf,
final boolean named, final AnnotationVisitor av) {
// 获取注解键值对个数,或者键值的个数,如果 named 为false
int i = readUnsignedShort(v);
v += 2;
if (named) {
for (; i > 0; --i) {
v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
}
} else {
for (; i > 0; --i) {
// 肯定是 array 类型,没有键'name'
v = readAnnotationValue(v, buf, null, av);
}
}
if (av != null) {
av.visitEnd();
}
return v;
}
这里有一点不好,应该增加
readAnnotationElementValue方法,这样就可以区分{ u2 element_name_index; element_value value;}和element_value value区别,就不需要通过named进行区别了。
private int readAnnotationValue(int v, final char[] buf, final String name,
final AnnotationVisitor av) {
int i;
if (av == null) {
switch (b[v] & 0xFF) {
case 'e': // enum_const_value
return v + 5;
case '@': // annotation_value
return readAnnotationValues(v + 3, buf, true, null);
case '[': // array_value
return readAnnotationValues(v + 1, buf, false, null);
default:
return v + 3;
}
}
switch (b[v++] & 0xFF) {
case 'I': // pointer to CONSTANT_Integer
case 'J': // pointer to CONSTANT_Long
case 'F': // pointer to CONSTANT_Float
case 'D': // pointer to CONSTANT_Double
av.visit(name, readConst(readUnsignedShort(v), buf));
v += 2;
break;
case 'B': // pointer to CONSTANT_Byte
av.visit(name,
(byte) readInt(items[readUnsignedShort(v)]));
v += 2;
break;
case 'Z': // pointer to CONSTANT_Boolean
av.visit(name,
readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
: Boolean.TRUE);
v += 2;
break;
case 'S': // pointer to CONSTANT_Short
av.visit(name,
(short) readInt(items[readUnsignedShort(v)]));
v += 2;
break;
case 'C': // pointer to CONSTANT_Char
av.visit(name,
(char) readInt(items[readUnsignedShort(v)]));
v += 2;
break;
case 's': // pointer to CONSTANT_Utf8
av.visit(name, readUTF8(v, buf));
v += 2;
break;
case 'e': // enum_const_value
av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
v += 4;
break;
case 'c': // class_info
av.visit(name, Type.getType(readUTF8(v, buf)));
v += 2;
break;
case '@': // annotation_value
v = readAnnotationValues(v + 2, buf, true,
av.visitAnnotation(name, readUTF8(v, buf)));
break;
case '[': // array_value
int size = readUnsignedShort(v);
v += 2;
if (size == 0) {
return readAnnotationValues(v - 2, buf, false,
av.visitArray(name));
}
switch (this.b[v++] & 0xFF) {
case 'B':
byte[] bv = new byte[size];
for (i = 0; i < size; i++) {
bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, bv);
--v;
break;
case 'Z':
boolean[] zv = new boolean[size];
for (i = 0; i < size; i++) {
zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
v += 3;
}
av.visit(name, zv);
--v;
break;
case 'S':
short[] sv = new short[size];
for (i = 0; i < size; i++) {
sv[i] = (short) readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, sv);
--v;
break;
case 'C':
char[] cv = new char[size];
for (i = 0; i < size; i++) {
cv[i] = (char) readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, cv);
--v;
break;
case 'I':
int[] iv = new int[size];
for (i = 0; i < size; i++) {
iv[i] = readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, iv);
--v;
break;
case 'J':
long[] lv = new long[size];
for (i = 0; i < size; i++) {
lv[i] = readLong(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, lv);
--v;
break;
case 'F':
float[] fv = new float[size];
for (i = 0; i < size; i++) {
fv[i] = Float
.intBitsToFloat(readInt(items[readUnsignedShort(v)]));
v += 3;
}
av.visit(name, fv);
--v;
break;
case 'D':
double[] dv = new double[size];
for (i = 0; i < size; i++) {
dv[i] = Double
.longBitsToDouble(readLong(items[readUnsignedShort(v)]));
v += 3;
}
av.visit(name, dv);
--v;
break;
default:
v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
}
}
return v;
}
读取注解不同类型键值对的数据,值得注意的是读取
array类型时,它选择在当前方法中直接通过循环处理,而不是使用递归。
3.4 readField 方法
private int readField(final ClassVisitor classVisitor,
final Context context, int u) {
// reads the field declaration
char[] c = context.buffer;
int access = readUnsignedShort(u);
String name = readUTF8(u + 2, c);
String desc = readUTF8(u + 4, c);
u += 6;
......
}
先读取字段的访问标志
access,字段名name和字段描述符desc。
private int readField(final ClassVisitor classVisitor,
final Context context, int u) {
......
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("ConstantValue".equals(attrName)) {
int item = readUnsignedShort(u + 8);
value = item == 0 ? null : readConst(item, c);
} else if (SIGNATURES && "Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
} else if ("Deprecated".equals(attrName)) {
access |= Opcodes.ACC_DEPRECATED;
} else if ("Synthetic".equals(attrName)) {
access |= Opcodes.ACC_SYNTHETIC
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
}
......
}
......
}
然后读取字段的属性值。
private int readField(final ClassVisitor classVisitor,
final Context context, int u) {
......
FieldVisitor fv = classVisitor.visitField(access, name, desc,
signature, value);
if (fv == null) {
return u;
}
// visits the field annotations and type annotations
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
fv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
fv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// visits the field attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
fv.visitAttribute(attributes);
attributes = attr;
}
// visits the end of the field
fv.visitEnd();
return u;
}
这个就是
FieldVisitor中方法调用顺序。
3.5 readMethod 方法
private int readMethod(final ClassVisitor classVisitor,
final Context context, int u) {
// reads the method declaration
char[] c = context.buffer;
context.access = readUnsignedShort(u);
context.name = readUTF8(u + 2, c);
context.desc = readUTF8(u + 4, c);
u += 6;
.......
}
先读取方法的访问标志
access,方法名name和方法描述符desc。
private int readMethod(final ClassVisitor classVisitor,
final Context context, int u) {
.......
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("Code".equals(attrName)) {
if ((context.flags & SKIP_CODE) == 0) {
code = u + 8;
}
} else if ("Exceptions".equals(attrName)) {
exceptions = new String[readUnsignedShort(u + 8)];
exception = u + 10;
for (int j = 0; j < exceptions.length; ++j) {
exceptions[j] = readClass(exception, c);
exception += 2;
}
} else if (SIGNATURES && "Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
} else if ("Deprecated".equals(attrName)) {
context.access |= Opcodes.ACC_DEPRECATED;
}
......
}
.......
}
然后读取方法的属性值。
private int readMethod(final ClassVisitor classVisitor,
final Context context, int u) {
.......
// visits the method declaration
MethodVisitor mv = classVisitor.visitMethod(context.access,
context.name, context.desc, signature, exceptions);
if (mv == null) {
return u;
}
if (WRITER && mv instanceof MethodWriter) {
MethodWriter mw = (MethodWriter) mv;
if (mw.cw.cr == this && signature == mw.signature) {
boolean sameExceptions = false;
if (exceptions == null) {
sameExceptions = mw.exceptionCount == 0;
} else if (exceptions.length == mw.exceptionCount) {
sameExceptions = true;
for (int j = exceptions.length - 1; j >= 0; --j) {
exception -= 2;
if (mw.exceptions[j] != readUnsignedShort(exception)) {
sameExceptions = false;
break;
}
}
}
if (sameExceptions) {
/*
* we do not copy directly the code into MethodWriter to
* save a byte array copy operation. The real copy will be
* done in ClassWriter.toByteArray().
*/
mw.classReaderOffset = firstAttribute;
mw.classReaderLength = u - firstAttribute;
return u;
}
}
}
// visit the method parameters
if (methodParameters != 0) {
for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
}
}
// visits the method annotations
if (ANNOTATIONS && dann != 0) {
AnnotationVisitor dv = mv.visitAnnotationDefault();
readAnnotationValue(dann, c, null, dv);
if (dv != null) {
dv.visitEnd();
}
}
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
mv.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
mv.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
mv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
mv.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
if (ANNOTATIONS && mpanns != 0) {
readParameterAnnotations(mv, context, mpanns, true);
}
if (ANNOTATIONS && impanns != 0) {
readParameterAnnotations(mv, context, impanns, false);
}
// visits the method attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
mv.visitAttribute(attributes);
attributes = attr;
}
// visits the method code
if (code != 0) {
mv.visitCode();
readCode(mv, context, code);
}
// visits the end of the method
mv.visitEnd();
return u;
}
- 这就是
MethodVisitor中方法调用顺序,不过Code属性相关方法,还是通过readCode(mv, context, code)方法决定。if (WRITER && mv instanceof MethodWriter)这个判断主要是为了没有修改的方法可以直接复制,而不用重新计算。
3.6 readCode 方法
这个方法比较复杂,源码有500多行代码,我就简单介绍一下。
private void readCode(final MethodVisitor mv, final Context context, int u) {
byte[] b = this.b;
char[] c = context.buffer;
int maxStack = readUnsignedShort(u);
int maxLocals = readUnsignedShort(u + 2);
int codeLength = readInt(u + 4);
u += 8;
if (codeLength > b.length - u) {
throw new IllegalArgumentException();
}
// reads the bytecode to find the labels
int codeStart = u;
int codeEnd = u + codeLength;
......
}
获取
Code属性的操作数栈最大深度maxStack, 局部变量表大小maxLocals,指令集的大小codeLength,指令开始位置codeStart和指令结束位置codeEnd。
private void readCode(final MethodVisitor mv, final Context context, int u) {
......
while (u < codeEnd) {
int offset = u - codeStart;
int opcode = b[u] & 0xFF;
switch (ClassWriter.TYPE[opcode]) {
case ClassWriter.NOARG_INSN:
case ClassWriter.IMPLVAR_INSN:
u += 1;
break;
case ClassWriter.LABEL_INSN:
readLabel(offset + readShort(u + 1), labels);
u += 3;
.....
}
......
}
这个
while循环为了记录程序跳转指令对应的指令地址(即Label)。
private void readCode(final MethodVisitor mv, final Context context, int u) {
......
for (int i = readUnsignedShort(u); i > 0; --i) {
Label start = readLabel(readUnsignedShort(u + 2), labels);
Label end = readLabel(readUnsignedShort(u + 4), labels);
Label handler = readLabel(readUnsignedShort(u + 6), labels);
String type = readUTF8(items[readUnsignedShort(u + 8)], c);
mv.visitTryCatchBlock(start, end, handler, type);
u += 8;
}
......
}
处理
try catch的代码块,即异常处理,对应Code属性中的exception_table。
private void readCode(final MethodVisitor mv, final Context context, int u) {
......
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
if ("LocalVariableTable".equals(attrName)) {
if ((context.flags & SKIP_DEBUG) == 0) {
varTable = u + 8;
for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
int label = readUnsignedShort(v + 10);
if (labels[label] == null) {
readLabel(label, labels).status |= Label.DEBUG;
}
label += readUnsignedShort(v + 12);
if (labels[label] == null) {
readLabel(label, labels).status |= Label.DEBUG;
}
v += 10;
}
}
} else if ("LocalVariableTypeTable".equals(attrName)) {
varTypeTable = u + 8;
}
......
}
......
}
读取
Code中的属性列表。
private void readCode(final MethodVisitor mv, final Context context, int u) {
......
// visits the instructions
u = codeStart;
while (u < codeEnd) {
int offset = u - codeStart;
// visits the label and line number for this offset, if any
Label l = labels[offset];
if (l != null) {
mv.visitLabel(l);
if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
mv.visitLineNumber(l.line, l);
}
}
......
}
......
}
读取
Code中的指令集,并调用MethodVisitor中相关visitXInsn方法。
四. 类写入器ClassWriter
ClassReader 只能读取已经编译好的字节码文件,但是如果我们想要生成一个字节码文件,那么就必须使用这个类写入器ClassWriter了。
创建
ClassWriter必须传递一个flags标志,这个flags可以选择三个值,分别是:
0: 表示不做特殊处理。COMPUTE_MAXS: 动态计算方法的Code属性中的max_stack和max_locals。如果我们改变了Code属性中指令,那么就可能需要计算max_stack和max_locals值了,否则JVM会执行方法失败。COMPUTE_FRAMES: 动态计算方法的StackMapTable,如果方法中有程序跳转指令,那么必须要有StackMapTable,否则JVM方法验证不通过,拒绝执行。
我们仔细想一下字节码文件中,最难处理的是那部分呢?
肯定就是常量池了,因为我们的常量,类引用,字段引用,方法引用等等,都必须先在常量池中定义,而用到的地方只需要使用对应常量池索引就可以了。
那么我们先看看ClassWriter如何处理常量池,用到一个常量项Item。
4.1 常量项Item
final class Item {
int index;
int type;
int intVal;
long longVal;
String strVal1;
String strVal2;
String strVal3;
int hashCode;
Item next;
}
-
index: 表示这个常量项在常量池中的索引。 -
type: 表示这个常量项的类型,目前JVM8有14种常量类型。 -
intVal: 表示int类型常量的值,包括INT和FLOAT。 -
longVal: 表示long类型常量的值,包括LONG和DOUBLE。 -
strVal1,strVal2和strVal3: 记录字符串的值,有些类型可能有多个字符串值。 -
hashCode: 表示这个常量项的哈希值。 -
next: 下一个常量项,因为通过哈希表储存常量,这里next就是通过链地址法解决哈希冲突。
4.2 创建常量方法
通过一系列new 方法创建常量。
Item newConstItem(final Object cst) {
if (cst instanceof Integer) {
int val = ((Integer) cst).intValue();
return newInteger(val);
} else if (cst instanceof Byte) {
int val = ((Byte) cst).intValue();
return newInteger(val);
} else if (cst instanceof Character) {
int val = ((Character) cst).charValue();
return newInteger(val);
} else if (cst instanceof Short) {
int val = ((Short) cst).intValue();
return newInteger(val);
} else if (cst instanceof Boolean) {
int val = ((Boolean) cst).booleanValue() ? 1 : 0;
return newInteger(val);
} else if (cst instanceof Float) {
float val = ((Float) cst).floatValue();
return newFloat(val);
} else if (cst instanceof Long) {
long val = ((Long) cst).longValue();
return newLong(val);
} else if (cst instanceof Double) {
double val = ((Double) cst).doubleValue();
return newDouble(val);
} else if (cst instanceof String) {
return newString((String) cst);
} else if (cst instanceof Type) {
Type t = (Type) cst;
int s = t.getSort();
if (s == Type.OBJECT) {
return newClassItem(t.getInternalName());
} else if (s == Type.METHOD) {
return newMethodTypeItem(t.getDescriptor());
} else { // s == primitive type or array
return newClassItem(t.getDescriptor());
}
} else if (cst instanceof Handle) {
Handle h = (Handle) cst;
return newHandleItem(h.tag, h.owner, h.name, h.desc);
} else {
throw new IllegalArgumentException("value " + cst);
}
}
根据 cst 类型调用不同的 new 方法创建对应的常量项。
private Item get(final Item key) {
Item i = items[key.hashCode % items.length];
while (i != null && (i.type != key.type || !key.isEqualTo(i))) {
i = i.next;
}
return i;
}
Item newInteger(final int value) {
key.set(value);
Item result = get(key);
if (result == null) {
pool.putByte(INT).putInt(value);
result = new Item(index++, key);
put(result);
}
return result;
}
items就是一个哈希表,存储着所有常量。get(final Item key)方法尝试从哈希表items中获取key对应的常量。- 如果哈希表中没有这个常量项,那么就创建。先将常量存到常量池
pool中,然后创建对应常量项Item,并存到哈希表items中。
4.3 访问器方法
4.3.1 visit(...)
public final void visit(final int version, final int access,
final String name, final String signature, final String superName,
final String[] interfaces) {
this.version = version;
this.access = access;
this.name = newClass(name);
thisName = name;
if (ClassReader.SIGNATURES && signature != null) {
this.signature = newUTF8(signature);
}
this.superName = superName == null ? 0 : newClass(superName);
if (interfaces != null && interfaces.length > 0) {
interfaceCount = interfaces.length;
this.interfaces = new int[interfaceCount];
for (int i = 0; i < interfaceCount; ++i) {
this.interfaces[i] = newClass(interfaces[i]);
}
}
}
通过
newClass(...)方法保证类名,父类名,接口类名都在常量池中存在,并获取对应的常量池索引。
其他的visitSource,visitOuterClass 和 visitInnerClass 大体都是如此,保证在常量池中有对应项,并获取索引。
@Override
public final void visitSource(final String file, final String debug) {
if (file != null) {
sourceFile = newUTF8(file);
}
if (debug != null) {
sourceDebug = new ByteVector().encodeUTF8(debug, 0,
Integer.MAX_VALUE);
}
}
@Override
public final void visitOuterClass(final String owner, final String name,
final String desc) {
enclosingMethodOwner = newClass(owner);
if (name != null && desc != null) {
enclosingMethod = newNameType(name, desc);
}
}
@Override
public final void visitInnerClass(final String name,
final String outerName, final String innerName, final int access) {
if (innerClasses == null) {
innerClasses = new ByteVector();
}
Item nameItem = newClassItem(name);
if (nameItem.intVal == 0) {
++innerClassesCount;
innerClasses.putShort(nameItem.index);
innerClasses.putShort(outerName == null ? 0 : newClass(outerName));
innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName));
innerClasses.putShort(access);
nameItem.intVal = innerClassesCount;
} else {
// Compare the inner classes entry nameItem.intVal - 1 with the
// arguments of this method and throw an exception if there is a
// difference?
}
}
4.3.2 visitAnnotation(...)
public final AnnotationVisitor visitAnnotation(final String desc,
final boolean visible) {
if (!ClassReader.ANNOTATIONS) {
return null;
}
ByteVector bv = new ByteVector();
// write type, and reserve space for values count
bv.putShort(newUTF8(desc)).putShort(0);
AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2);
if (visible) {
aw.next = anns;
anns = aw;
} else {
aw.next = ianns;
ianns = aw;
}
return aw;
}
bv.putShort(newUTF8(desc)).putShort(0)中.putShort(0)作用就是先将属性大小size位置占了。ianns和anns使用链表记录当前类所有类注解。
4.3.3 visitField(...)
@Override
public final FieldVisitor visitField(final int access, final String name,
final String desc, final String signature, final Object value) {
return new FieldWriter(this, access, name, desc, signature, value);
}
通过
ClassWriter的firstField和lastField来记录当前类所有的字段。
4.3.4 visitMethod(...)
public final MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
return new MethodWriter(this, access, name, desc, signature,
exceptions, computeMaxs, computeFrames);
}
通过
ClassWriter的firstMethod和lastMethod来记录当前类所有的方法。
4.4 toByteArray 方法
生成字节码的方法,下面我们来一步一步分析。
public byte[] toByteArray() {
if (index > 0xFFFF) {
throw new RuntimeException("Class file too large!");
}
int size = 24 + 2 * interfaceCount;
int nbFields = 0;
FieldWriter fb = firstField;
while (fb != null) {
++nbFields;
size += fb.getSize();
fb = (FieldWriter) fb.fv;
}
int nbMethods = 0;
.......
if (attrs != null) {
attributeCount += attrs.getCount();
size += attrs.getSize(this, null, 0, -1, -1);
}
size += pool.length;
.....
}
代码到这里都是为了计算生成字节码的大小
size。
public byte[] toByteArray() {
.......
/**
* 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];
* }
*/
ByteVector out = new ByteVector(size);
out.putInt(0xCAFEBABE).putInt(version);
out.putShort(index).putByteArray(pool.data, 0, pool.length);
int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
| ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
out.putShort(access & ~mask).putShort(name).putShort(superName);
out.putShort(interfaceCount);
for (int i = 0; i < interfaceCount; ++i) {
out.putShort(interfaces[i]);
}
out.putShort(nbFields);
fb = firstField;
while (fb != null) {
fb.put(out);
fb = (FieldWriter) fb.fv;
}
out.putShort(nbMethods);
mb = firstMethod;
while (mb != null) {
mb.put(out);
mb = (MethodWriter) mb.mv;
}
out.putShort(attributeCount);
if (bootstrapMethods != null) {
out.putShort(newUTF8("BootstrapMethods"));
out.putInt(bootstrapMethods.length + 2).putShort(
bootstrapMethodsCount);
out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
}
.......
}
按照
ClassFile格式,写入字节码数据。
五. 例子
5.1 读取字节码文件
public class T {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) throws Exception {
ClassReader classReader = new ClassReader(T.class.getName());
classReader.accept(new ClassVisitor(ASM5) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println(String.format("version:[%s] access:[%s] name:[%s] signature:[%s] superName:[%s] interfaces:[%s]",
version, access, name, signature, superName, Arrays.toString(interfaces)));
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println(String.format("visitField access:[%s] name:[%s] desc:[%s] signature:[%s] value:[%s]",
access, name, desc, signature, value));
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println(String.format("visitMethod access:[%s] name:[%s] desc:[%s] signature:[%s] exceptions:[%s]",
access, name, desc, signature, Arrays.toString(exceptions)));
return super.visitMethod(access, name, desc, signature, exceptions);
}
}, 0);
}
运行结果:
version:[52] access:[33] name:[com/zhang/bytecode/ams/_3/T] signature:[null] superName:[java/lang/Object] interfaces:[[]]
visitField access:[2] name:[name] desc:[Ljava/lang/String;] signature:[null] value:[null]
visitMethod access:[1] name:[<init>] desc:[()V] signature:[null] exceptions:[null]
visitMethod access:[1] name:[getName] desc:[()Ljava/lang/String;] signature:[null] exceptions:[null]
visitMethod access:[1] name:[setName] desc:[(Ljava/lang/String;)V] signature:[null] exceptions:[null]
ASM 也提供了非常方便地查看类信息的工具类,分别是 TraceClassVisitor 和 ASMifier。
5.1.1 TraceClassVisitor
public class T {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) throws Exception {
ClassReader classReader = new ClassReader(T.class.getName());
classReader.accept(new TraceClassVisitor(new PrintWriter(System.out)),0);
}
}
将打印结果直接输出到控制台
System.out。
运行结果如下:
// class version 52.0 (52)
// access flags 0x21
public class com/zhang/bytecode/ams/_2/T {
// compiled from: T.java
// access flags 0x2
private Ljava/lang/String; name
// access flags 0x1
public <init>()V
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/zhang/bytecode/ams/_2/T; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public getName()Ljava/lang/String;
L0
LINENUMBER 10 L0
ALOAD 0
GETFIELD com/zhang/bytecode/ams/_2/T.name : Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lcom/zhang/bytecode/ams/_2/T; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public setName(Ljava/lang/String;)V
L0
LINENUMBER 14 L0
ALOAD 0
ALOAD 1
PUTFIELD com/zhang/bytecode/ams/_2/T.name : Ljava/lang/String;
L1
LINENUMBER 15 L1
RETURN
L2
LOCALVARIABLE this Lcom/zhang/bytecode/ams/_2/T; L0 L2 0
LOCALVARIABLE name Ljava/lang/String; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
}
很清晰地打印出类的信息,包括方法中的指令集。例如
setName(name)方法:
public setName(Ljava/lang/String;)V方法的访问标志,方法名和方法描述符。L0其实对应了this.name = name;这句代码。L1对应着方法默认无返回值的return,虽然源码中没有,但是javac编译器会帮我们添加到字节码文件的方法中。L2表示方法Code属性指令集最后一个指令的地址,用来计算LocalVariableTable和LocalVariableTypeTable属性的。
5.1.2 ASMifier
public class T {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) throws Exception {
ClassReader classReader = new ClassReader(T.class.getName());
classReader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(System.out)),0);
}
}
将 ASMifier 对象作为参数传递给 TraceClassVisitor 实例,这样我们就可以得到如下结果:
import java.util.*;
import zhang.asm.*;
public class TDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/zhang/bytecode/ams/_2/T", null, "java/lang/Object", null);
cw.visitSource("T.java", null);
{
fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(6, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Lcom/zhang/bytecode/ams/_2/T;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(10, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "com/zhang/bytecode/ams/_2/T", "name", "Ljava/lang/String;");
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Lcom/zhang/bytecode/ams/_2/T;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(14, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "com/zhang/bytecode/ams/_2/T", "name", "Ljava/lang/String;");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(15, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "Lcom/zhang/bytecode/ams/_2/T;", null, l0, l2, 0);
mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
使用了
ASMifier对象之后,输出的结果不在是类信息,而是如何使用ASM生成T这个字节码类。
5.2 生成简单字节码
public class Test {
public static void main(String[] args) throws Exception {
ClassWriter classWriter = new ClassWriter(ASM5);
classWriter.visit(52, ACC_PUBLIC, "com/zhang/bytecode/ams/_3/TG",null, "java/lang/Object", null);
classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "age", Type.getDescriptor(int.class), null, 10);
classWriter.visitField(ACC_PRIVATE, "name", Type.getDescriptor(String.class), null, null);
Files.write(Paths.get("TG.class"), classWriter.toByteArray());
}
}
生成后,使用 javap -v -p TG.class 命令:
Classfile /Users/zhangxinhao/work/java/test/example/TG.class
Last modified 2021-12-19; size 163 bytes
MD5 checksum b015e3451e79ceda7d55f10215b44595
public class com.zhang.bytecode.ams._3.TG
minor version: 0
major version: 52
flags: ACC_PUBLIC
Constant pool:
#1 = Utf8 com/zhang/bytecode/ams/_3/TG
#2 = Class #1 // com/zhang/bytecode/ams/_3/TG
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 age
#6 = Utf8 I
#7 = Integer 10
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 ConstantValue
{
public static final int age;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 10
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
}
你会发现一个巨大问题,它居然没有构造函数,是没有办法创建对象的,那么我们将构造方法加上。
public class CustomClassLoader extends ClassLoader {
public static final CustomClassLoader INSTANCE = new CustomClassLoader();
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
public class Test {
public static void main(String[] args) throws Exception {
ClassWriter classWriter = new ClassWriter(ASM5);
classWriter.visit(52, ACC_PUBLIC, "com/zhang/bytecode/ams/_3/TG",null, "java/lang/Object", null);
classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "age", Type.getDescriptor(int.class), null, 10);
classWriter.visitField(ACC_PRIVATE, "name", Type.getDescriptor(String.class), null, null);
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
byte[] bytes = classWriter.toByteArray();
Files.write(Paths.get("TG.class"), bytes);
Class clazz = CustomClassLoader.INSTANCE.defineClass("com.zhang.bytecode.ams._3.TG", bytes);
System.out.println(clazz);
Object obj = clazz.newInstance();
System.out.println(obj);
}
}
运行结果
class com.zhang.bytecode.ams._3.TG
com.zhang.bytecode.ams._3.TG@55f96302
我们自定义的
class创建成功。
使用 javap -v -p TG.class 命令结果是:
Classfile /Users/zhangxinhao/work/java/test/example/TG.class
Last modified 2021-12-19; size 226 bytes
MD5 checksum 3cc7120fe6f4573bfac44d4f056c8660
public class com.zhang.bytecode.ams._3.TG
minor version: 0
major version: 52
flags: ACC_PUBLIC
Constant pool:
#1 = Utf8 com/zhang/bytecode/ams/_3/TG
#2 = Class #1 // com/zhang/bytecode/ams/_3/TG
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 age
#6 = Utf8 I
#7 = Integer 10
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = NameAndType #10:#11 // "<init>":()V
#13 = Methodref #4.#12 // java/lang/Object."<init>":()V
#14 = Utf8 ConstantValue
#15 = Utf8 Code
{
public static final int age;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 10
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
public com.zhang.bytecode.ams._3.TG();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #13 // Method java/lang/Object."<init>":()V
4: return
}
5.3 修改已编译字节码文件中方法
假如我们现在需要打印每个方法执行时间,那么怎么修改字节码的方式搞定。
class MyMethodVisitor extends MethodVisitor {
int firstLocal;
int insertCount = 2; // long 类型是 2
int lastMaxVar;
public MyMethodVisitor(int access, String desc, MethodVisitor mv) {
super(ASM5, mv);
Type[] args = Type.getArgumentTypes(desc);
// 实例方法有 this
firstLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (int i = 0; i < args.length; i++) {
firstLocal += args[i].getSize();
}
lastMaxVar = firstLocal;
}
@Override
public void visitCode() {
super.visitCode();
visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
super.visitVarInsn(LSTORE, firstLocal);
lastMaxVar += 2;
}
@Override
public void visitVarInsn(int opcode, int var) {
if (var >= firstLocal) {
var += insertCount;
}
if (var > lastMaxVar) {
lastMaxVar = var;
}
super.visitVarInsn(opcode, var);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
super.visitVarInsn(LLOAD, firstLocal);
visitInsn(LSUB);
visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
}
super.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack , maxLocals);
}
}
public static void main(String[] args) throws Exception {
ClassReader classReader = new ClassReader(T.class.getName());
ClassWriter classWriter = new ClassWriter(COMPUTE_FRAMES);
classReader.accept(new ClassVisitor(ASM5, classWriter) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MyMethodVisitor(access, desc,
super.visitMethod(access, name, desc, signature, exceptions));
}
}, EXPAND_FRAMES);
byte[] bytes = classWriter.toByteArray();
Class clazz = CustomClassLoader.INSTANCE.defineClass(T.class.getName(), bytes);
Object object = clazz.newInstance();
for (Method method : clazz.getDeclaredMethods()) {
System.out.print("方法[" + method.getName() + "]:\t");
method.setAccessible(true);
method.invoke(object);
}
}
public class T {
private void t1() {
}
private void t2() throws InterruptedException {
Thread.sleep(1000);
}
}
- 通过
ClassReader读取原字节码文件T.class,然后自定义MethodVisitor对象,向方法中添加自定义指令。visitCode()在方法刚开始时调用,我们插入long var1 = System.currentTimeMillis();语句;这里非常需要注意的一点,就是我们向方法局部变量表中插入了一个局部变量var1,类型是long,位置就是在方法参数变量后。- 因为改变了局部变量表,所以要复写
visitVarInsn(...)方法,让原字节码文件中的方法,访问局部变量正确。- 最后复写
visitInsn(...)方法,再跳出方法指令之前,插入System.out.println(System.currentTimeMillis() - var1);语句。- 注意
new ClassWriter(COMPUTE_FRAMES)一定要使用COMPUTE_FRAMES,动态计算StackMapTable,不然方法中有程序跳转指令(例如goto),那么原来的StackMapTable就是错误的,导致方法验证不通过。
运行结果:
0
方法[t1]: 0
方法[t2]: 1004
方法[t3]: 2002
得到的反编译字节码文件
public class T {
public T() {
long var1 = System.currentTimeMillis();
super();
System.out.println(System.currentTimeMillis() - var1);
}
private void t1() {
long var1 = System.currentTimeMillis();
System.out.println(System.currentTimeMillis() - var1);
}
private void t2() throws InterruptedException {
long var1 = System.currentTimeMillis();
Thread.sleep(1000L);
System.out.println(System.currentTimeMillis() - var1);
}
private void t3() {
long var1 = System.currentTimeMillis();
try {
Thread.sleep(2000L);
} catch (InterruptedException var4) {
var4.printStackTrace();
}
System.out.println(System.currentTimeMillis() - var1);
}
}