在前面的文章中,我们分析了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);
}
}