最近進(jìn)行組內(nèi)分享時選擇了這個Java字節(jié)碼處理這個主題,特此記錄下來。眾所周知,Java是一門運行在虛擬機(jī)上的語言,在創(chuàng)建之初就是為了"write once ,run anywhere "的目的,為了解決不同架構(gòu)處理器區(qū)別,通過虛擬機(jī)來屏蔽不同的各個操作系統(tǒng)之間的區(qū)別,其虛擬機(jī)上運行的平臺中立的二進(jìn)制文件正是class字節(jié)碼文件,當(dāng)然JIT,AOT之類的技術(shù)是后來為了讓Java運行更快加入的,不過這里我們只是對Java的字節(jié)碼文件的處理。
ASM是什么呢,ASM是一個Java字節(jié)碼層次的處理框架。它可以直接對class文件進(jìn)行增刪改的操作,Java中許多的框架的實現(xiàn)正是基于ASM,比如AOP的實現(xiàn),Java自身的動態(tài)代理只能支持接口的形式,而使用ASM就能很方便的擴(kuò)展到類的代理??梢哉fASM就是一把利劍,是深入Java必須學(xué)習(xí)的一個點。
本章主要通過以下幾點來解析ASM
- ASM的基礎(chǔ)使用
- ASM的設(shè)計模式
- Class的文件格式
- ASM的源碼解析
- ASM總結(jié)
ASM的基礎(chǔ)使用
對于ASM的使用最推薦的莫過于官方的 《ASM Guide》,介紹的十分詳細(xì)。這里先拋磚引玉給出一個比較簡單的例子,引出ASM中最重要的三個類。假如我們有一個需求是給一個class文件添加一個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);
}
}
這段代碼是給一個Class文件多加一個字段的操作,在ASM的封裝下,這樣的操作實現(xiàn)非常簡單,下面我們就將學(xué)習(xí)到ASM是如何做到這一切的。
ASM的設(shè)計模式
從前面的ClassVisitor以及accept方法就能看出這是明顯的visitor(訪問者)模式,visitor模式在Java的設(shè)計模式中是一種比較冷門的設(shè)計模式,冷門并不是因為這個模式的缺陷,而是相較于一些風(fēng)格比較明顯的比如單例,工廠,觀察者模式,訪問者模式的實際應(yīng)用場景是比較少的,后面會介紹到這個模式實際存在的一個不小的問題。
訪問者模式主要包含被訪問的元素以及訪問者兩部分,元素一般是不同的類型,不同的訪問者對于這些元素的操作一般也是不同的,訪問者模式使得用戶可以在不修改現(xiàn)有系統(tǒng)的情況下擴(kuò)展系統(tǒng)的功能,為這些不同類型的元素增加新的操作。
訪問者的模式講解比較好的文章可以看這篇,操作復(fù)雜對象結(jié)構(gòu)——訪問者模式
一般訪問者模式的元素會接受一個訪問者的參數(shù),在元素內(nèi)部這個訪問者會直接訪問這個元素,說的比較繞,用代碼來解釋
public interface Employee {
public void accept(Department handler);
}
首先對于元素類我們提供一個接口,內(nèi)部的方法就是accept,用來接收一個訪問者類
public abstract class Department {
public abstract void visit(FulltimeEmployee employee);
public abstract void visit(ParttimeEmployee employee);
}
這個抽象的訪問者類提供了兩個訪問具體元素的抽象方法,然后我們具體的實現(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("臨時工 : " + 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);
}
}
這個的元素實現(xiàn)類中的accept方法中傳進(jìn)來的是抽象的訪問者類,然后將自身轉(zhuǎn)發(fā)出去,達(dá)到外界訪問這個元素的目的。
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);
}
}
看上面這個例子,通過這種轉(zhuǎn)發(fā)的方式,我們就能在不修改元素的條件下,添加對于元素訪問的操作,只需要讓元素類accept一個新的訪問者就行。這樣看起來我們通過accept的轉(zhuǎn)發(fā)行為讓元素類和操作類進(jìn)行了解耦,所以這種模式這種模式對于添加新的訪問者的操作是符合“開閉原則”的,但是如果我們一旦要增加新的元素時,就會導(dǎo)致所有的訪問者類都需要增加相應(yīng)的訪問方法,這是明顯違反設(shè)計模式,所以訪問者模式并不適合元素類頻繁變動的場景,這也是訪問者模式自身最大的缺陷。
對于Java class文件來說,其中的元素從設(shè)計到現(xiàn)在,基本沒有變化,僅僅只添加了一些細(xì)節(jié),比如泛型的引入,其實Java就是為了保證低版本的兼容性,才導(dǎo)致了一些不合理的存在,比如為了引入泛型,但是又不想破壞兼容性,才有了泛型擦除這樣的坑爹操作,相比C#這樣的語言,Java的泛型可以說只是偽泛型,但是Java的優(yōu)勢就是兼容性好,對于訪問者模式也就是元素類不會隨意改動,這個缺陷也就不存在了。
Class的文件格式
前面說到Class文件結(jié)構(gòu),想要理解ASM的內(nèi)部運行原理,首先需要了解Class文件,這樣才能知道ASM如何對于Class文件進(jìn)行操作,Class文件作為虛擬機(jī)所執(zhí)行的平臺中立文件,內(nèi)部結(jié)構(gòu)設(shè)計的十分的清晰,每一個Class文件都對應(yīng)著唯一一個類或接口的定義信息。每個Class文件都由以8位為單位的字節(jié)流組成,下面是一個Class文件中所包括的項,在Class文件中,各項按照嚴(yán)格順序連續(xù)存放,中間沒有任何填充或者對齊作為各項間的分隔符號。
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)中,上面各項的含義如下:
magic: 作為一個魔數(shù),確定這個文件是否是一個能被虛擬機(jī)接受的class文件,值固定為0xCAFEBABE。
minor_version,major_version:分別表示class文件的副,主版本號,不同版本的虛擬機(jī)實現(xiàn)支持的Class文件版本號不同。
constant_pool_count:常量池計數(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)志,表示這個類或者接口的訪問權(quán)限及屬性,包括有ACC_PUBLIC,ACC_FINAL,ACC_SUPER等等。
this_class:類索引,指向常量池表中項的一個索引。
super_class:父類索引,這個值必須為0或者是對常量池中項的一個有效索引值,如果為0,表示這個class只能是Object類,只有它是唯一沒有父類的類。
interfaces_count:接口計算器,表示當(dāng)前類或者接口的直接父接口數(shù)量。
interfaces[]:接口表,里面的每個成員的值必須是一個對常量池表中項的一個有效索引值。
fields_count:字段計算器,表示當(dāng)前class文件中fields表的成員個數(shù),每個成員都是一個field_info。
fields:字段表,每個成員都是一個完整的fields_info結(jié)構(gòu),表示當(dāng)前類或接口中某個字段的完整描述,不包括父類或父接口的部分。
methods_count:方法計數(shù)器,表示當(dāng)前class文件methos表的成員個數(shù)。
methods:方法表,每個成員都是一個完整的method_info結(jié)構(gòu),可以表示類或接口中定義的所有方法,包括實例方法,類方法,以及類或接口初始化方法。
attributes_count:屬性表,其中是每一個attribute_info,包含以下這些屬性,InnerClasses,EnclosingMethod,Synthetic,Signature,Annonation等。
ASM的源碼解析
前面我們分析了ASM的場景剛好適合訪問者模式,同時也是這么做的,這里我們就從accept方法開始分析ASM,限于篇幅,這里省略了accept方法中部分的visitor調(diào)用,不過原理都是一樣的,可以自行對照源碼參考
//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)來了一個參數(shù)ClassVisitor,然后接著往下看,這里我們省略了解析class文件的過程,整個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();
整個class文件在accept這個方法中,相當(dāng)于以庖丁解牛的方式,肢解成了上一小節(jié)中ClassFile中的每一項,而這個classVisitor實際上是一個抽象類。
public abstract class ClassVisitor
有抽象類,自然會有實現(xiàn)類,也就是前面的ClassWriter。
public class ClassWriter extends ClassVisitor
在前面我們添加字段的示例之中,在最后處理完class文件后,通過toByteArray方法生成了新的class文件,所以這里有兩個疑問,第一,這個toByteArray 在這里擔(dān)當(dāng)了什么角色,為什么能夠生成新的Class文件,第二,在參數(shù)轉(zhuǎn)發(fā)出來之后,我們是如何通過visit系列方法改變整個Class文件的。帶著這兩個問題,我們繼續(xù)往下看。
首先看一下 toByteArray 這個方法的前半部分
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);
同樣限于篇幅,這里省略了中間的一部分計算size的部分,作者在里面給出了注釋,計算Class字節(jié)的真實大小,這個字節(jié)怎么計算呢,這里首先給了一個24,想必是前面ClassFile中的魔數(shù)以及版本號之類的字節(jié)數(shù)大小,然后在后面分別添加了字段,方法,屬性等等的大小,通過這個最終的size,構(gòu)造了一個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;
}
方法的后半部分就是給這個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)來,我們先看第一個方法
@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]);
}
}
}
這個visit方法是轉(zhuǎn)發(fā)的第一個方法,其中的幾個參數(shù)分別表示了原Class文件中的編譯版本,訪問標(biāo)志(是否是final,static,abstract等等),類或接口名,泛型,父類以及實現(xiàn)的接口,可以看到這個方法只是單純的賦了一下值,并沒有什么其他的操作,這些值在最后生成新Class文件的時候會再次寫入到byte數(shù)組中。
再看一個有點復(fù)雜的,ASM對于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ù)組類型,一個類中存在多個方法是很正常的,這個visitMethod同樣會被調(diào)用多次,這里重點關(guān)注這個 firstMethod 和 cw.lastMethod.mv,在ClassWriter類中其實并沒有明顯的數(shù)組類型來存放多方法結(jié)構(gòu),這也是ASM對于method和field寫的比較模糊的一點,每一次的 visitMethod 的調(diào)用都是 new 一個 MethodWriter對象,第一次 cw.firstMethod == null,于是給了 firstMethod 和 lastMethod 賦值當(dāng)前,既有頭又有尾,有些雙向鏈表的意思,但是這里在第二次進(jìn)來的時候走的是 cw.lastMethod.mv = this ,而 cw.lastMethod 指向的是剛才的 MethodWriter 對象,這就很清晰了,每一個 MethodWriter 的 mv 字段保留著下一個的引用,實際上只是單向的鏈表。
然后我們看一下在輸出新的Class文件的時候
mb = firstMethod;
while (mb != null) {
mb.put(out);
mb = (MethodWriter) mb.mv;
}
剛才我們構(gòu)造的 method 鏈表又重新的一個個的寫進(jìn)去了,而我們每一次的visitMethod 都會加長這個鏈表。
ASM就是通過這種方式來修改Class結(jié)構(gòu)的,當(dāng)我們想要加一個方法的時候,只需要多調(diào)用一次 visitMethod 方法,而當(dāng)我們想刪除其中一個方法的時候,只需要 return null,讓這個MethodWrite 對象不會被構(gòu)建即可。
ASM總結(jié)
ClassReader通過讀取整個class文件,作為訪問者模式中的元素類,而ClassWrite作為ClassVisitor的實現(xiàn)類,是一個特殊的具體訪問者,通過一個accept方法將兩個連接起來,并在對應(yīng)的visitXXX系列方法中決定最終元素的屬性,而且元素的訪問操作是可以進(jìn)行鏈?zhǔn)睫D(zhuǎn)發(fā)的,這樣我們既可以擁有ClassWrite的class文件生成能力,也能在自定義的visitor中實現(xiàn)我們想要對于class元素的處理。整體來說,ASM是一款十分優(yōu)秀的字節(jié)碼處理的框架,訪問者模式十分適合對于class這樣的格式基本不會改變的元素形式。
這一章主要是介紹ASM對于單個class文件的處理,對于一個完整的Java工程來說,我們想要處理的class文件可能是整個項目所編譯出來的全部,后面會帶來一篇hook編譯過程來實現(xiàn)class處理的文章,兩篇結(jié)合起來就能實現(xiàn)許多的功能,動態(tài)代理,無侵入式埋點,熱修復(fù)框架都將不是遙不可及。