在前面的文章中,我們分析了Class
這個字節(jié)碼文件的格式锯茄,知道了字節(jié)碼的作用厢塘,那么我們就可以直接生成字節(jié)碼文件,加載到當(dāng)前的 JVM
中運行肌幽,這個在AOP
場景中經(jīng)常用到晚碾。
當(dāng)然直接手動寫字節(jié)碼難度比較大,太過麻煩喂急。這里就介紹一個非常重要也非常高效的字節(jié)碼生成框架ASM
格嘁。
Java8
中Lambda
表達式,也是通過ASM
動態(tài)生成類的廊移。
一. ASM
介紹
ASM
可以高效地生成.class
字節(jié)碼文件糕簿,分為兩種方式:
- 核心
API
: 流式讀取字節(jié)碼文件內(nèi)容涣易,并通過訪問器的模式,將讀取到內(nèi)容數(shù)據(jù)暴露出去冶伞,類比解析XML
文件中的SAX
方式新症。 - 樹形
API
: 底層就是采用核心API
,只不過它將讀取到的內(nèi)容節(jié)點响禽,保存在內(nèi)存中猫妙,形成一個樹形結(jié)構(gòu)挤茄,類比解析XML
文件中的DOM
方式。
因此我們只要了解核心API
原理就行了,主要就是以下幾個方面:
- 各種訪問器
Visitor
谈秫,用來接收到字節(jié)碼中的各部分?jǐn)?shù)據(jù)萍倡。 - 各種寫入器
Writer
膝昆,它們是Visitor
子類以清,接收到數(shù)據(jù),然后寫入到字節(jié)數(shù)組中贮竟,生成一個字節(jié)碼文件丽焊。 -
ClassReader
,它比較特殊,就是用來讀取一個字節(jié)碼文件咕别,并且可以將字節(jié)碼中的各部分?jǐn)?shù)據(jù)發(fā)送給各個訪問器Visitor
技健。
因為我們之前介紹
JVM8
的字節(jié)碼文件格式,因此我們選擇ASM
也是適配JVM8
惰拱,其實就是openjdk8
中源碼雌贱。
如果對字節(jié)碼中結(jié)構(gòu)和信息不清楚的,請先閱讀這些文章:字節(jié)碼文件(ClassFile)詳解,字節(jié)碼的屬性,指令集偿短。
二. 訪問器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
中方法實現(xiàn)都是調(diào)用 ClassVisitor cv
這個成員變量對應(yīng)方法。
2.1.2 ClassFile
結(jié)構(gòu)
首先讓我們回憶一下昔逗,ClassFile
文件結(jié)構(gòu):
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];
}
分為魔數(shù)降传,版本,常量池纤子,訪問標(biāo)志搬瑰,類名,繼承的父類名控硼,實現(xiàn)的接口集合,字段列表艾少,方法列表和ClassFile
的屬性列表卡乾。
2.1.3 ClassVisitor
中的方法
方法的調(diào)用順序如下:
visit
[ visitSource ]
[ visitOuterClass ]
( visitAnnotation | visitTypeAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd.
-
void visit(int version, int access, String name, String signature,String superName, String[] interfaces)
字節(jié)碼文件的版本
version
,訪問標(biāo)志access
,類名name
,類泛型簽名屬性signature
,繼承的父類名superName
和 實現(xiàn)的接口集合interfaces
。 -
void visitSource(String source, String debug)
字節(jié)碼文件中
SourceFile
和SourceDebugExtension
這兩個屬性信息缚够。 -
void visitOuterClass(String owner, String name, String desc)
字節(jié)碼文件中
EnclosingMethod
屬性信息幔妨。 -
AnnotationVisitor visitAnnotation(String desc, boolean visible)
- 字節(jié)碼文件中
RuntimeVisibleAnnotations
和RuntimeInvisibleAnnotations
屬性信息鹦赎。 -
desc
表示注解類名的描述符,visible
表示這個注解可不可見误堡。
- 字節(jié)碼文件中
- `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
- 字節(jié)碼文件中
RuntimeVisibleTypeAnnotations
和RuntimeInvisibleTypeAnnotations
屬性信息古话。 -
desc
表示注解類名的描述符,visible
表示這個注解可不可見锁施。
- 字節(jié)碼文件中
-
void visitAttribute(Attribute attr)
這個是字節(jié)碼文件中自定義的屬性陪踩,不是當(dāng)前
JVM
規(guī)定的屬性值。 -
void visitInnerClass(String name, String outerName, String innerName, int access)
字節(jié)碼文件中
InnerClasses
屬性信息悉抵。 -
FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
- 字節(jié)碼文件中字段列表肩狂。
- 這里包含了字段的訪問標(biāo)志
access
,字段名name
,字段描述符desc
, 字段簽名信息signature
和字段常量屬性ConstantValue
的值value
。
-
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
- 字節(jié)碼文件中方法列表姥饰。
- 這里包含了方法的訪問標(biāo)志
access
,方法名name
,方法描述符desc
, 方法簽名信息signature
和方法拋出異常列表exceptions
傻谁。
void visitEnd()
表示字節(jié)碼文件訪問結(jié)束。
2.2 注解訪問器AnnotationVisitor
AnnotationVisitor
主要是用來接收注解相關(guān)屬性的列粪,主要就是 RuntimeVisibleAnnotations
,RuntimeInvisibleAnnotations
,RuntimeVisibleParameterAnnotations
,RuntimeInvisibleParameterAnnotations
,AnnotationDefault
,RuntimeVisibleTypeAnnotations
和RuntimeInvisibleTypeAnnotations
這七個屬性了审磁。
注解的類名信息已經(jīng)在
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
成員變量,來實現(xiàn)AnnotationVisitor
方法掺逼。
2.2.2 annotation
結(jié)構(gòu)
還記得注解的數(shù)據(jù)結(jié)構(gòu)么:
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
和數(shù)組類型array_value
赘那。
2.2.3 AnnotationVisitor
中的方法
方法調(diào)用順序如下:
( 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)
注解中數(shù)組類型鍵值對信息拱礁,也返回一個
AnnotationVisitor
。
其實數(shù)組類型和注解類型的區(qū)別辕漂,注解類型有多個鍵值對集合呢灶,而數(shù)組類型只有鍵值的列表,沒有鍵name
钉嘹。 -
void visitEnd()
表示注解訪問結(jié)束鸯乃。
2.3 字段訪問器 FieldVisitor
- 字段的訪問標(biāo)志
access
,字段名name
,字段描述符desc
, 字段簽名信息signature
和字段常量屬性ConstantValue
的值value
已經(jīng)在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
結(jié)構(gòu)
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
字段中屬性有:
-
ConstantValue
: 通過final
關(guān)鍵字修飾的字段對應(yīng)的常量值缨睡。 -
Synthetic
: 標(biāo)志字段鸟悴,方法和類由編譯器自動生成。 -
Deprecated
: 聲明字段奖年,方法和類將棄用细诸。 -
Signature
: 記錄字段,方法和類的泛型信息陋守。 -
RuntimeVisibleAnnotations
: 可見的字段震贵,方法和類注解。 -
RuntimeInvisibleAnnotations
: 不可見的字段嗅义,方法和類注解屏歹。 -
RuntimeVisibleTypeAnnotations
: 可見的類型注解,主要用于實現(xiàn)JSR 308
之碗。 -
RuntimeInvisibleTypeAnnotations
: 不可見的類型注解蝙眶,主要用于實現(xiàn)JSR 308
。
2.3.3 FieldVisitor
中的方法
方法調(diào)用順序如下:
( visitAnnotation | visitTypeAnnotation | visitAttribute )*
visitEnd
-
AnnotationVisitor visitAnnotation(String desc, boolean visible)
- 字節(jié)碼文件中
RuntimeVisibleAnnotations
和RuntimeInvisibleAnnotations
屬性信息褪那。 -
desc
表示注解類名的描述符幽纷,visible
表示這個注解可不可見。
- 字節(jié)碼文件中
- `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
- 字節(jié)碼文件中
RuntimeVisibleTypeAnnotations
和RuntimeInvisibleTypeAnnotations
屬性信息博敬。 -
desc
表示注解類名的描述符友浸,visible
表示這個注解可不可見。
- 字節(jié)碼文件中
-
void visitAttribute(Attribute attr)
這個是字節(jié)碼文件中自定義的屬性偏窝,不是當(dāng)前
JVM
規(guī)定的屬性值收恢。 -
void visitEnd()
表示字段訪問結(jié)束。
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
結(jié)構(gòu)
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法中的屬性有:
-
Code
: 方法對應(yīng)字節(jié)碼指令祭往。 -
Exceptions
: 方法拋出的異常列表伦意。 -
RuntimeVisibleParameterAnnotations
: 可見的方法參數(shù)注解。 -
RuntimeInvisibleParameterAnnotations
: 不可見的方法參數(shù)注解硼补。 -
AnnotationDefault
: 記錄注解類元素默認值驮肉。 -
MethodParameters
: 將方法參數(shù)參數(shù)名編譯到字節(jié)碼文件中(編譯時加上 -parameters 參數(shù))。 -
Synthetic
: 標(biāo)志字段已骇,方法和類由編譯器自動生成离钝。 -
Deprecated
: 聲明字段,方法和類將棄用褪储。 -
Signature
: 記錄字段卵渴,方法和類的泛型信息。 -
RuntimeVisibleAnnotations
: 可見的字段乱豆,方法和類注解奖恰。 -
RuntimeInvisibleAnnotations
: 不可見的字段,方法和類注解宛裕。 -
RuntimeVisibleTypeAnnotations
: 可見的類型注解瑟啃,主要用于實現(xiàn)JSR 308
。 -
RuntimeInvisibleTypeAnnotations
: 不可見的類型注解揩尸,主要用于實現(xiàn)JSR 308
蛹屿。
2.4.3 MethodVisitor
中的方法
方法調(diào)用順序如下
( visitParameter )*
[ visitAnnotationDefault ]
( visitAnnotation | visitTypeAnnotation | visitAttribute )*
[ visitCode ( visitFrame | visitXInsn | visitLabel | visitInsnAnnotation | visitTryCatchBlock | visitTryCatchBlockAnnotation | visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )* visitMaxs ]
visitEnd
可以看出
MethodVisitor
中的方法比較復(fù)雜,主要是因為Code
屬性數(shù)據(jù)岩榆,需要生成方法指令集错负。
-
void visitParameter(String name, int access)
方法中
MethodParameters
屬性,獲取方法參數(shù)的參數(shù)名name
和參數(shù)訪問標(biāo)志access
勇边。 -
AnnotationVisitor visitAnnotationDefault()
方法中
AnnotationDefault
屬性犹撒,只會出現(xiàn)在注解類中,注解類方法默認返回值信息粒褒。 -
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()
:方法訪問結(jié)束奕坟。
2.4.3 Code
屬性相關(guān)方法
我們知道方法的操作其實就是對應(yīng)一個一個的JVM
指令祥款,而這些指令就存在 Code
屬性中。
2.4.3.1 Code
屬性結(jié)構(gòu)
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
源碼中方法中字節(jié)指令和行號對應(yīng)關(guān)系月杉。 -
LocalVariableTable
: 方法中局部變量描述刃跛。 -
LocalVariableTypeTable
: 方法中泛型局部變量描述。 -
StackMapTable
: 記錄數(shù)據(jù)供類型檢查驗證器檢查苛萎,來驗證方法局部變量表和操作數(shù)棧所需類型是否匹配桨昙。 -
RuntimeVisibleTypeAnnotations
和RuntimeInvisibleTypeAnnotations
2.4.3.2 Code
屬性對應(yīng)方法
方法流程如下:
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
屬性腌歉。關(guān)于
StackMapTable
詳細說明請看字節(jié)碼的屬性蛙酪。 -
void visitInsn(int opcode)
: 訪問零操作數(shù)指令這些指令有
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)
: 訪問操作數(shù)就是整型值的指令。- 也就是說這個操作數(shù)不代表索引這些究履,比如
BIPUSH
指令滤否,就是將一個byte
類型數(shù)放入操作數(shù)棧。 - 這些指令有
BIPUSH
,SIPUSH
和NEWARRAY
最仑。
- 也就是說這個操作數(shù)不代表索引這些究履,比如
-
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)
: 訪問動態(tài)方法指令。- 動態(tài)方法名
name
,動態(tài)方法描述desc
,引導(dǎo)方法處理器bsm
和引導(dǎo)方法處理器參數(shù)bsmArgs
浙宜。 - 這個指令就是
invokedynamic
官辽。
- 動態(tài)方法名
-
visitJumpInsn(int opcode, Label label)
: 訪問程序跳轉(zhuǎn)指令。這些指令包括
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
記錄當(dāng)前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
中的數(shù)據(jù)。 -
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
可以讀取字節(jié)碼文件蝠筑,并分發(fā)給各個訪問器 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
// 字節(jié)碼版本超過 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個字節(jié)包括 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 中保存常量數(shù)據(jù)開始位置
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 類型常量的字節(jié)數(shù)已球。
maxStringLength = max;
// the class header information starts just after the constant pool
// header 就是 access_flags 開始位置
header = index;
}
主要就是獲取字節(jié)碼文件的字節(jié)數(shù)組byte[] b
, 然后解析字節(jié)碼的常量池臣镣,得到三個成員變量:
-
items
: 記錄常量池中所有常量數(shù)據(jù)在字節(jié)數(shù)組b
的位置,方便常量數(shù)據(jù)智亮。 -
strings
: 用來緩存UTF8
類型常量的字符串忆某。 -
header
: 字節(jié)碼中access_flags
開始位置。
3.2 accept
方法
accept
方法就是接收一個 ClassVisitor
對象阔蛉,將字節(jié)碼文件中的數(shù)據(jù)發(fā)送到 類訪問器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;
}
.......
}
首先讀取了字節(jié)碼文件中的訪問標(biāo)志
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()
方法獲取類屬性列表的開始位置状原。- 然后獲取屬性個數(shù)聋呢,通過遍歷獲取所有類屬性值。
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) {
// 獲取注解鍵值對個數(shù)削锰,或者鍵值的個數(shù),如果 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;
}
這里有一點不好器贩,應(yīng)該增加
readAnnotationElementValue
方法颅夺,這樣就可以區(qū)分{ u2 element_name_index; element_value value;}
和element_value value
區(qū)別,就不需要通過named
進行區(qū)別了蛹稍。
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;
}
讀取注解不同類型鍵值對的數(shù)據(jù)吧黄,值得注意的是讀取
array
類型時,它選擇在當(dāng)前方法中直接通過循環(huán)處理稳摄,而不是使用遞歸稚字。
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;
......
}
先讀取字段的訪問標(biāo)志
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
中方法調(diào)用順序。
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;
.......
}
先讀取方法的訪問標(biāo)志
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
中方法調(diào)用順序,不過Code
屬性相關(guān)方法国夜,還是通過readCode(mv, context, code)
方法決定减噪。if (WRITER && mv instanceof MethodWriter)
這個判斷主要是為了沒有修改的方法可以直接復(fù)制,而不用重新計算车吹。
3.6 readCode
方法
這個方法比較復(fù)雜筹裕,源碼有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
屬性的操作數(shù)棧最大深度maxStack
, 局部變量表大小maxLocals
朝卒,指令集的大小codeLength
,指令開始位置codeStart
和指令結(jié)束位置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
循環(huán)為了記錄程序跳轉(zhuǎn)指令對應(yīng)的指令地址(即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
的代碼塊,即異常處理丈咐,對應(yīng)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
中的指令集棵逊,并調(diào)用MethodVisitor
中相關(guān)visitXInsn
方法伤疙。
四. 類寫入器ClassWriter
ClassReader
只能讀取已經(jīng)編譯好的字節(jié)碼文件,但是如果我們想要生成一個字節(jié)碼文件辆影,那么就必須使用這個類寫入器ClassWriter
了徒像。
創(chuàng)建
ClassWriter
必須傳遞一個flags
標(biāo)志,這個flags
可以選擇三個值秸歧,分別是:
0
: 表示不做特殊處理厨姚。COMPUTE_MAXS
: 動態(tài)計算方法的Code
屬性中的max_stack
和max_locals
。如果我們改變了Code
屬性中指令键菱,那么就可能需要計算max_stack
和max_locals
值了谬墙,否則JVM
會執(zhí)行方法失敗今布。COMPUTE_FRAMES
: 動態(tài)計算方法的StackMapTable
,如果方法中有程序跳轉(zhuǎn)指令拭抬,那么必須要有StackMapTable
部默,否則JVM
方法驗證不通過,拒絕執(zhí)行造虎。
我們仔細想一下字節(jié)碼文件中傅蹂,最難處理的是那部分呢?
肯定就是常量池了算凿,因為我們的常量份蝴,類引用,字段引用氓轰,方法引用等等婚夫,都必須先在常量池中定義,而用到的地方只需要使用對應(yīng)常量池索引就可以了署鸡。
那么我們先看看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 創(chuàng)建常量方法
通過一系列new
方法創(chuàng)建常量蟀伸。
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);
}
}
根據(jù) cst
類型調(diào)用不同的 new
方法創(chuàng)建對應(yīng)的常量項。
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
對應(yīng)的常量。- 如果哈希表中沒有這個常量項衰猛,那么就創(chuàng)建迟蜜。先將常量存到常量池
pool
中,然后創(chuàng)建對應(yīng)常量項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(...)
方法保證類名娜睛,父類名髓霞,接口類名都在常量池中存在,并獲取對應(yīng)的常量池索引畦戒。
其他的visitSource
,visitOuterClass
和 visitInnerClass
大體都是如此方库,保證在常量池中有對應(yīng)項,并獲取索引障斋。
@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
使用鏈表記錄當(dāng)前類所有類注解。
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
來記錄當(dāng)前類所有的字段。
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
來記錄當(dāng)前類所有的方法。
4.4 toByteArray
方法
生成字節(jié)碼的方法微饥,下面我們來一步一步分析。
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;
.....
}
代碼到這里都是為了計算生成字節(jié)碼的大小
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
格式,寫入字節(jié)碼數(shù)據(jù)涧团。
五. 例子
5.1 讀取字節(jié)碼文件
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);
}
運行結(jié)果:
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);
}
}
將打印結(jié)果直接輸出到控制臺
System.out
泌绣。
運行結(jié)果如下:
// 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
方法的訪問標(biāo)志阿迈,方法名和方法描述符。L0
其實對應(yīng)了this.name = name;
這句代碼轧叽。L1
對應(yīng)著方法默認無返回值的return
苗沧,雖然源碼中沒有,但是javac
編譯器會幫我們添加到字節(jié)碼文件的方法中炭晒。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
對象作為參數(shù)傳遞給 TraceClassVisitor
實例网严,這樣我們就可以得到如下結(jié)果:
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
對象之后识樱,輸出的結(jié)果不在是類信息,而是如何使用ASM
生成T
這個字節(jié)碼類震束。
5.2 生成簡單字節(jié)碼
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
}
你會發(fā)現(xiàn)一個巨大問題,它居然沒有構(gòu)造函數(shù)垢村,是沒有辦法創(chuàng)建對象的割疾,那么我們將構(gòu)造方法加上。
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);
}
}
運行結(jié)果
class com.zhang.bytecode.ams._3.TG
com.zhang.bytecode.ams._3.TG@55f96302
我們自定義的
class
創(chuàng)建成功嘉栓。
使用 javap -v -p TG.class
命令結(jié)果是:
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 修改已編譯字節(jié)碼文件中方法
假如我們現(xiàn)在需要打印每個方法執(zhí)行時間宏榕,那么怎么修改字節(jié)碼的方式搞定驰凛。
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
讀取原字節(jié)碼文件T.class
,然后自定義MethodVisitor
對象担扑,向方法中添加自定義指令恰响。visitCode()
在方法剛開始時調(diào)用,我們插入long var1 = System.currentTimeMillis();
語句涌献;這里非常需要注意的一點胚宦,就是我們向方法局部變量表中插入了一個局部變量var1
,類型是long
燕垃,位置就是在方法參數(shù)變量后枢劝。- 因為改變了局部變量表,所以要復(fù)寫
visitVarInsn(...)
方法卜壕,讓原字節(jié)碼文件中的方法您旁,訪問局部變量正確。- 最后復(fù)寫
visitInsn(...)
方法轴捎,再跳出方法指令之前鹤盒,插入System.out.println(System.currentTimeMillis() - var1);
語句。- 注意
new ClassWriter(COMPUTE_FRAMES)
一定要使用COMPUTE_FRAMES
侦副,動態(tài)計算StackMapTable
侦锯,不然方法中有程序跳轉(zhuǎn)指令(例如goto
),那么原來的StackMapTable
就是錯誤的秦驯,導(dǎo)致方法驗證不通過尺碰。
運行結(jié)果:
0
方法[t1]: 0
方法[t2]: 1004
方法[t3]: 2002
得到的反編譯字節(jié)碼文件
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);
}
}