最近進(jìn)行組內(nèi)分享時(shí)選擇了這個(gè)Java字節(jié)碼處理這個(gè)主題柒莉,特此記錄下來。眾所周知货岭,Java是一門運(yùn)行在虛擬機(jī)上的語(yǔ)言忘古,在創(chuàng)建之初就是為了"write once 徘禁,run anywhere "的目的,為了解決不同架構(gòu)處理器區(qū)別髓堪,通過虛擬機(jī)來屏蔽不同的各個(gè)操作系統(tǒng)之間的區(qū)別送朱,其虛擬機(jī)上運(yùn)行的平臺(tái)中立的二進(jìn)制文件正是class字節(jié)碼文件娘荡,當(dāng)然JIT,AOT之類的技術(shù)是后來為了讓Java運(yùn)行更快加入的,不過這里我們只是對(duì)Java的字節(jié)碼文件的處理驶沼。
ASM是什么呢炮沐,ASM是一個(gè)Java字節(jié)碼層次的處理框架。它可以直接對(duì)class文件進(jìn)行增刪改的操作回怜,Java中許多的框架的實(shí)現(xiàn)正是基于ASM大年,比如AOP的實(shí)現(xiàn),Java自身的動(dòng)態(tài)代理只能支持接口的形式玉雾,而使用ASM就能很方便的擴(kuò)展到類的代理翔试。可以說ASM就是一把利劍复旬,是深入Java必須學(xué)習(xí)的一個(gè)點(diǎn)遏餐。
本章主要通過以下幾點(diǎn)來解析ASM
- ASM的基礎(chǔ)使用
- ASM的設(shè)計(jì)模式
- Class的文件格式
- ASM的源碼解析
- ASM總結(jié)
ASM的基礎(chǔ)使用
對(duì)于ASM的使用最推薦的莫過于官方的 《ASM Guide》,介紹的十分詳細(xì)赢底。這里先拋磚引玉給出一個(gè)比較簡(jiǎn)單的例子,引出ASM中最重要的三個(gè)類柏蘑。假如我們有一個(gè)需求是給一個(gè)class文件添加一個(gè)field字段幸冻,代碼如下
public class AddField extends ClassVisitor {
private String name;
private int access;
private String desc;
private Object value;
private boolean duplicate;
public AddField(ClassVisitor cv, String name, int access, String desc, Object value) {
super(ASM5, cv);
this.name = name;
this.access = access;
this.desc = desc;
this.value = value;
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if (this.name.equals(name)) {
duplicate = true;
}
return super.visitField(access, name, desc, signature, value);
}
@Override
public void visitEnd() {
if (!duplicate) {
FieldVisitor fv = super.visitField(access, name, desc, null, value);
if (fv != null) {
fv.visitEnd();
}
}
super.visitEnd();
}
public static void main(String[] args) throws Exception {
String output = System.getProperty("user.dir") + "/libjava/output";
String classDir = System.getProperty("user.dir") + "/libjava/output/MainActivity.class";
ClassReader classReader = new ClassReader(new FileInputStream(classDir));
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
ClassVisitor addField = new AddField(classWriter,
"field",
Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL,
Type.getDescriptor(String.class),
"value"
);
classReader.accept(addField, ClassReader.EXPAND_FRAMES);
byte[] newClass = classWriter.toByteArray();
File newFile = new File(output, "MainActivity1.class");
new FileOutputStream(newFile).write(newClass);
}
}
這段代碼是給一個(gè)Class文件多加一個(gè)字段的操作,在ASM的封裝下咳焚,這樣的操作實(shí)現(xiàn)非常簡(jiǎn)單洽损,下面我們就將學(xué)習(xí)到ASM是如何做到這一切的。
ASM的設(shè)計(jì)模式
從前面的ClassVisitor以及accept方法就能看出這是明顯的visitor(訪問者)模式革半,visitor模式在Java的設(shè)計(jì)模式中是一種比較冷門的設(shè)計(jì)模式碑定,冷門并不是因?yàn)檫@個(gè)模式的缺陷,而是相較于一些風(fēng)格比較明顯的比如單例又官,工廠延刘,觀察者模式,訪問者模式的實(shí)際應(yīng)用場(chǎng)景是比較少的六敬,后面會(huì)介紹到這個(gè)模式實(shí)際存在的一個(gè)不小的問題碘赖。
訪問者模式主要包含被訪問的元素以及訪問者兩部分,元素一般是不同的類型外构,不同的訪問者對(duì)于這些元素的操作一般也是不同的普泡,訪問者模式使得用戶可以在不修改現(xiàn)有系統(tǒng)的情況下擴(kuò)展系統(tǒng)的功能,為這些不同類型的元素增加新的操作审编。
訪問者的模式講解比較好的文章可以看這篇撼班,操作復(fù)雜對(duì)象結(jié)構(gòu)——訪問者模式
一般訪問者模式的元素會(huì)接受一個(gè)訪問者的參數(shù),在元素內(nèi)部這個(gè)訪問者會(huì)直接訪問這個(gè)元素垒酬,說的比較繞砰嘁,用代碼來解釋
public interface Employee {
public void accept(Department handler);
}
首先對(duì)于元素類我們提供一個(gè)接口件炉,內(nèi)部的方法就是accept,用來接收一個(gè)訪問者類
public abstract class Department {
public abstract void visit(FulltimeEmployee employee);
public abstract void visit(ParttimeEmployee employee);
}
這個(gè)抽象的訪問者類提供了兩個(gè)訪問具體元素的抽象方法般码,然后我們具體的實(shí)現(xiàn)元素類和訪問者類妻率。
public class FADepartment extends Department {
@Override
public void visit(FulltimeEmployee employee) {
System.out.println("正式員工 : " + employee.getName());
}
@Override
public void visit(ParttimeEmployee employee) {
System.out.println("臨時(shí)工 : " + employee.getName());
}
}
public class FulltimeEmployee implements Employee {
private String name;
private String salary;
public FulltimeEmployee(String name, String salary) {
this.name = name;
this.salary = salary;
}
@Override
public void accept(Department handler) {
handler.visit(this);
}
}
這個(gè)的元素實(shí)現(xiàn)類中的accept方法中傳進(jìn)來的是抽象的訪問者類,然后將自身轉(zhuǎn)發(fā)出去板祝,達(dá)到外界訪問這個(gè)元素的目的宫静。
public class EmployeeList {
private List<Employee> list = new ArrayList<>();
public void addEmployee(Employee employee) {
list.add(employee);
}
public void accept(Department department) {
for (Employee employee : list) {
employee.accept(department);
}
}
public static void main(String[] args) {
EmployeeList list = new EmployeeList();
Employee employee1 = new FulltimeEmployee("張三", "1000");
Employee employee2 = new ParttimeEmployee("李四", "500");
Employee employee3 = new ParttimeEmployee("王五", "400");
list.addEmployee(employee1);
list.addEmployee(employee2);
list.addEmployee(employee3);
Department department1 = new FADepartment();
list.accept(department1);
}
}
看上面這個(gè)例子,通過這種轉(zhuǎn)發(fā)的方式券时,我們就能在不修改元素的條件下孤里,添加對(duì)于元素訪問的操作,只需要讓元素類accept一個(gè)新的訪問者就行橘洞。這樣看起來我們通過accept的轉(zhuǎn)發(fā)行為讓元素類和操作類進(jìn)行了解耦捌袜,所以這種模式這種模式對(duì)于添加新的訪問者的操作是符合“開閉原則”的,但是如果我們一旦要增加新的元素時(shí)炸枣,就會(huì)導(dǎo)致所有的訪問者類都需要增加相應(yīng)的訪問方法虏等,這是明顯違反設(shè)計(jì)模式,所以訪問者模式并不適合元素類頻繁變動(dòng)的場(chǎng)景适肠,這也是訪問者模式自身最大的缺陷霍衫。
對(duì)于Java class文件來說,其中的元素從設(shè)計(jì)到現(xiàn)在侯养,基本沒有變化敦跌,僅僅只添加了一些細(xì)節(jié),比如泛型的引入逛揩,其實(shí)Java就是為了保證低版本的兼容性柠傍,才導(dǎo)致了一些不合理的存在,比如為了引入泛型辩稽,但是又不想破壞兼容性惧笛,才有了泛型擦除這樣的坑爹操作,相比C#這樣的語(yǔ)言搂誉,Java的泛型可以說只是偽泛型徐紧,但是Java的優(yōu)勢(shì)就是兼容性好,對(duì)于訪問者模式也就是元素類不會(huì)隨意改動(dòng)炭懊,這個(gè)缺陷也就不存在了并级。
Class的文件格式
前面說到Class文件結(jié)構(gòu),想要理解ASM的內(nèi)部運(yùn)行原理侮腹,首先需要了解Class文件嘲碧,這樣才能知道ASM如何對(duì)于Class文件進(jìn)行操作,Class文件作為虛擬機(jī)所執(zhí)行的平臺(tái)中立文件父阻,內(nèi)部結(jié)構(gòu)設(shè)計(jì)的十分的清晰愈涩,每一個(gè)Class文件都對(duì)應(yīng)著唯一一個(gè)類或接口的定義信息望抽。每個(gè)Class文件都由以8位為單位的字節(jié)流組成,下面是一個(gè)Class文件中所包括的項(xiàng)履婉,在Class文件中煤篙,各項(xiàng)按照嚴(yán)格順序連續(xù)存放,中間沒有任何填充或者對(duì)齊作為各項(xiàng)間的分隔符號(hào)毁腿。
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];
}
在Class文件結(jié)構(gòu)中辑奈,上面各項(xiàng)的含義如下:
magic: 作為一個(gè)魔數(shù),確定這個(gè)文件是否是一個(gè)能被虛擬機(jī)接受的class文件已烤,值固定為0xCAFEBABE鸠窗。
minor_version,major_version:分別表示class文件的副胯究,主版本號(hào)稍计,不同版本的虛擬機(jī)實(shí)現(xiàn)支持的Class文件版本號(hào)不同。
constant_pool_count:常量池計(jì)數(shù)器裕循,constant_pool_count的值等于常量池表中的成員數(shù)加1臣嚣。
constant_pool:常量池,constant_pool是一種表結(jié)構(gòu)剥哑,包含class文件結(jié)構(gòu)及其子結(jié)構(gòu)中引用的所有字符常量茧球、類或接口名、字段名和其他常量星持。
access_flags:access_flags是一種訪問標(biāo)志,表示這個(gè)類或者接口的訪問權(quán)限及屬性弹灭,包括有ACC_PUBLIC督暂,ACC_FINAL,ACC_SUPER等等穷吮。
this_class:類索引逻翁,指向常量池表中項(xiàng)的一個(gè)索引。
super_class:父類索引捡鱼,這個(gè)值必須為0或者是對(duì)常量池中項(xiàng)的一個(gè)有效索引值八回,如果為0,表示這個(gè)class只能是Object類驾诈,只有它是唯一沒有父類的類缠诅。
interfaces_count:接口計(jì)算器,表示當(dāng)前類或者接口的直接父接口數(shù)量乍迄。
interfaces[]:接口表管引,里面的每個(gè)成員的值必須是一個(gè)對(duì)常量池表中項(xiàng)的一個(gè)有效索引值。
fields_count:字段計(jì)算器闯两,表示當(dāng)前class文件中fields表的成員個(gè)數(shù)褥伴,每個(gè)成員都是一個(gè)field_info谅将。
fields:字段表,每個(gè)成員都是一個(gè)完整的fields_info結(jié)構(gòu)重慢,表示當(dāng)前類或接口中某個(gè)字段的完整描述饥臂,不包括父類或父接口的部分。
methods_count:方法計(jì)數(shù)器似踱,表示當(dāng)前class文件methos表的成員個(gè)數(shù)隅熙。
methods:方法表,每個(gè)成員都是一個(gè)完整的method_info結(jié)構(gòu)屯援,可以表示類或接口中定義的所有方法猛们,包括實(shí)例方法,類方法狞洋,以及類或接口初始化方法弯淘。
attributes_count:屬性表,其中是每一個(gè)attribute_info吉懊,包含以下這些屬性庐橙,InnerClasses,EnclosingMethod借嗽,Synthetic态鳖,Signature,Annonation等恶导。
ASM的源碼解析
前面我們分析了ASM的場(chǎng)景剛好適合訪問者模式浆竭,同時(shí)也是這么做的,這里我們就從accept
方法開始分析ASM惨寿,限于篇幅邦泄,這里省略了accept方法中部分的visitor調(diào)用,不過原理都是一樣的裂垦,可以自行對(duì)照源碼參考
//ClassReader.java
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;
}
// reads the class attributes
......
// 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();
}
在accept方法中傳進(jìn)來了一個(gè)參數(shù)ClassVisitor
顺囊,然后接著往下看,這里我們省略了解析class文件的過程蕉拢,整個(gè)class的解析過程遵循上一小節(jié)中Class的文件格式特碳,通過不斷的讀取ClassReader的構(gòu)造函數(shù)中class二進(jìn)制byte[],然后在解析后通過參數(shù)classVisitor的抽象visitXXX方法將屬性全部轉(zhuǎn)發(fā)出去晕换,將其中的visitXXX方法按順序抽離出來就是
classVisitor.visit(readInt(items[1] - 7), access, name, signature,superClass, interfaces);
classVisitor.visitSource(sourceFile, sourceDebug);
classVisitor.visitOuterClass(enclosingOwner, enclosingName,enclosingDesc);
classVisitor.visitAnnotation(readUTF8(v, c);
classVisitor.visitTypeAnnotation(context.typeRef,context.typePath, readUTF8(v, c);
classVisitor.visitAttribute(attributes);
classVisitor.visitInnerClass(readClass(v, c),readClass(v + 2, c), readUTF8(v + 4, c),readUnsignedShort(v + 6));
classVisitor.visitField(access, name, desc,signature, value);
classVisitor.visitMethod(context.access,context.name, context.desc, signature, exceptions);
classVisitor.visitEnd();
整個(gè)class文件在accept這個(gè)方法中午乓,相當(dāng)于以庖丁解牛的方式,肢解成了上一小節(jié)中ClassFile中的每一項(xiàng)闸准,而這個(gè)classVisitor實(shí)際上是一個(gè)抽象類硅瞧。
public abstract class ClassVisitor
有抽象類,自然會(huì)有實(shí)現(xiàn)類恕汇,也就是前面的ClassWriter腕唧。
public class ClassWriter extends ClassVisitor
在前面我們添加字段的示例之中或辖,在最后處理完class文件后,通過toByteArray
方法生成了新的class文件枣接,所以這里有兩個(gè)疑問颂暇,第一,這個(gè)toByteArray
在這里擔(dān)當(dāng)了什么角色但惶,為什么能夠生成新的Class文件耳鸯,第二,在參數(shù)轉(zhuǎn)發(fā)出來之后膀曾,我們是如何通過visit系列方法改變整個(gè)Class文件的县爬。帶著這兩個(gè)問題,我們繼續(xù)往下看添谊。
首先看一下 toByteArray
這個(gè)方法的前半部分
public byte[] toByteArray() {
if (index > 0xFFFF) {
throw new RuntimeException("Class file too large!");
}
// computes the real size of the bytecode of this class
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;
MethodWriter mb = firstMethod;
while (mb != null) {
++nbMethods;
size += mb.getSize();
mb = (MethodWriter) mb.mv;
}
int attributeCount = 0;
......
if (ClassReader.ANNOTATIONS && itanns != null) {
++attributeCount;
size += 8 + itanns.getSize();
newUTF8("RuntimeInvisibleTypeAnnotations");
}
if (attrs != null) {
attributeCount += attrs.getCount();
size += attrs.getSize(this, null, 0, -1, -1);
}
size += pool.length;
// allocates a byte vector of this size, in order to avoid unnecessary
// arraycopy operations in the ByteVector.enlarge() method
ByteVector out = new ByteVector(size);
同樣限于篇幅财喳,這里省略了中間的一部分計(jì)算size的部分,作者在里面給出了注釋斩狱,計(jì)算Class字節(jié)的真實(shí)大小耳高,這個(gè)字節(jié)怎么計(jì)算呢,這里首先給了一個(gè)24所踊,想必是前面ClassFile中的魔數(shù)以及版本號(hào)之類的字節(jié)數(shù)大小泌枪,然后在后面分別添加了字段,方法秕岛,屬性等等的大小碌燕,通過這個(gè)最終的size,構(gòu)造了一個(gè)ByteVetcor继薛。
public byte[] toByteArray() {
......
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);
}
if (ClassReader.SIGNATURES && signature != 0) {
out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);
}
if (sourceFile != 0) {
out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);
}
if (sourceDebug != null) {
int len = sourceDebug.length;
out.putShort(newUTF8("SourceDebugExtension")).putInt(len);
out.putByteArray(sourceDebug.data, 0, len);
}
if (enclosingMethodOwner != 0) {
out.putShort(newUTF8("EnclosingMethod")).putInt(4);
out.putShort(enclosingMethodOwner).putShort(enclosingMethod);
}
if ((access & Opcodes.ACC_DEPRECATED) != 0) {
out.putShort(newUTF8("Deprecated")).putInt(0);
}
if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
if ((version & 0xFFFF) < Opcodes.V1_5
|| (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
out.putShort(newUTF8("Synthetic")).putInt(0);
}
}
if (innerClasses != null) {
out.putShort(newUTF8("InnerClasses"));
out.putInt(innerClasses.length + 2).putShort(innerClassesCount);
out.putByteArray(innerClasses.data, 0, innerClasses.length);
}
if (ClassReader.ANNOTATIONS && anns != null) {
out.putShort(newUTF8("RuntimeVisibleAnnotations"));
anns.put(out);
}
if (ClassReader.ANNOTATIONS && ianns != null) {
out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
ianns.put(out);
}
if (ClassReader.ANNOTATIONS && tanns != null) {
out.putShort(newUTF8("RuntimeVisibleTypeAnnotations"));
tanns.put(out);
}
if (ClassReader.ANNOTATIONS && itanns != null) {
out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations"));
itanns.put(out);
}
if (attrs != null) {
attrs.put(this, null, 0, -1, -1, out);
}
if (invalidFrames) {
anns = null;
ianns = null;
attrs = null;
innerClassesCount = 0;
innerClasses = null;
bootstrapMethodsCount = 0;
bootstrapMethods = null;
firstField = null;
lastField = null;
firstMethod = null;
lastMethod = null;
computeMaxs = false;
computeFrames = true;
invalidFrames = false;
new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES);
return toByteArray();
}
return out.data;
}
方法的后半部分就是給這個(gè)ByteVector開始填數(shù)據(jù)了陆蟆,按照ClassFile的格式依次填入數(shù)據(jù),和在ClassReader中讀取的順序一模一樣惋增,這樣生成的Class結(jié)構(gòu)就是符合虛擬機(jī)規(guī)范的,也能被虛擬機(jī)正常的加載改鲫。
那么這些數(shù)據(jù)是從哪里來的呢诈皿,聰明的讀者現(xiàn)在肯定猜到了,就是前面這些抽象的visit系列方法像棘,它們從原始Class文件中依次讀取了數(shù)據(jù)然后又作為參數(shù)傳了進(jìn)來稽亏,我們先看第一個(gè)方法
@Override
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]);
}
}
}
這個(gè)visit方法是轉(zhuǎn)發(fā)的第一個(gè)方法,其中的幾個(gè)參數(shù)分別表示了原Class文件中的編譯版本缕题,訪問標(biāo)志(是否是final截歉,static,abstract等等)烟零,類或接口名瘪松,泛型咸作,父類以及實(shí)現(xiàn)的接口,可以看到這個(gè)方法只是單純的賦了一下值宵睦,并沒有什么其他的操作记罚,這些值在最后生成新Class文件的時(shí)候會(huì)再次寫入到byte數(shù)組中。
再看一個(gè)有點(diǎn)復(fù)雜的壳嚎,ASM對(duì)于method的處理
@Override
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);
}
直接代理給了 MethodWriter
類進(jìn)行處理桐智,繼續(xù)跟進(jìn)去構(gòu)造函數(shù)
MethodWriter(final ClassWriter cw, final int access, final String name,
final String desc, final String signature,
final String[] exceptions, final boolean computeMaxs,
final boolean computeFrames) {
super(Opcodes.ASM5);
if (cw.firstMethod == null) {
cw.firstMethod = this;
} else {
cw.lastMethod.mv = this;
}
cw.lastMethod = this;
......
}
前面分析Class的文件結(jié)構(gòu)的時(shí)候,方法表是一個(gè)數(shù)組類型烟馅,一個(gè)類中存在多個(gè)方法是很正常的说庭,這個(gè)visitMethod同樣會(huì)被調(diào)用多次,這里重點(diǎn)關(guān)注這個(gè) firstMethod
和 cw.lastMethod.mv
郑趁,在ClassWriter類中其實(shí)并沒有明顯的數(shù)組類型來存放多方法結(jié)構(gòu)刊驴,這也是ASM對(duì)于method和field寫的比較模糊的一點(diǎn),每一次的 visitMethod
的調(diào)用都是 new 一個(gè) MethodWriter對(duì)象穿撮,第一次 cw.firstMethod == null
缺脉,于是給了 firstMethod
和 lastMethod
賦值當(dāng)前,既有頭又有尾悦穿,有些雙向鏈表的意思攻礼,但是這里在第二次進(jìn)來的時(shí)候走的是 cw.lastMethod.mv = this
,而 cw.lastMethod
指向的是剛才的 MethodWriter
對(duì)象栗柒,這就很清晰了礁扮,每一個(gè) MethodWriter
的 mv 字段保留著下一個(gè)的引用,實(shí)際上只是單向的鏈表瞬沦。
然后我們看一下在輸出新的Class文件的時(shí)候
mb = firstMethod;
while (mb != null) {
mb.put(out);
mb = (MethodWriter) mb.mv;
}
剛才我們構(gòu)造的 method 鏈表又重新的一個(gè)個(gè)的寫進(jìn)去了太伊,而我們每一次的visitMethod 都會(huì)加長(zhǎng)這個(gè)鏈表。
ASM就是通過這種方式來修改Class結(jié)構(gòu)的逛钻,當(dāng)我們想要加一個(gè)方法的時(shí)候僚焦,只需要多調(diào)用一次 visitMethod 方法,而當(dāng)我們想刪除其中一個(gè)方法的時(shí)候曙痘,只需要 return null芳悲,讓這個(gè)MethodWrite 對(duì)象不會(huì)被構(gòu)建即可。
ASM總結(jié)
ClassReader通過讀取整個(gè)class文件边坤,作為訪問者模式中的元素類名扛,而ClassWrite作為ClassVisitor的實(shí)現(xiàn)類,是一個(gè)特殊的具體訪問者茧痒,通過一個(gè)accept
方法將兩個(gè)連接起來肮韧,并在對(duì)應(yīng)的visitXXX系列方法中決定最終元素的屬性,而且元素的訪問操作是可以進(jìn)行鏈?zhǔn)睫D(zhuǎn)發(fā)的,這樣我們既可以擁有ClassWrite的class文件生成能力弄企,也能在自定義的visitor中實(shí)現(xiàn)我們想要對(duì)于class元素的處理超燃。整體來說,ASM是一款十分優(yōu)秀的字節(jié)碼處理的框架桩蓉,訪問者模式十分適合對(duì)于class這樣的格式基本不會(huì)改變的元素形式淋纲。
這一章主要是介紹ASM對(duì)于單個(gè)class文件的處理,對(duì)于一個(gè)完整的Java工程來說院究,我們想要處理的class文件可能是整個(gè)項(xiàng)目所編譯出來的全部洽瞬,后面會(huì)帶來一篇hook編譯過程來實(shí)現(xiàn)class處理的文章,兩篇結(jié)合起來就能實(shí)現(xiàn)許多的功能业汰,動(dòng)態(tài)代理伙窃,無侵入式埋點(diǎn),熱修復(fù)框架都將不是遙不可及样漆。