Java字節(jié)碼處理框架ASM設(shè)計思想解析

最近進(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)注這個 firstMethodcw.lastMethod.mv,在ClassWriter類中其實并沒有明顯的數(shù)組類型來存放多方法結(jié)構(gòu),這也是ASM對于method和field寫的比較模糊的一點,每一次的 visitMethod 的調(diào)用都是 new 一個 MethodWriter對象,第一次 cw.firstMethod == null,于是給了 firstMethodlastMethod 賦值當(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ù)框架都將不是遙不可及。

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,823評論 18 399
  • 原文鏈接http://www.cnblogs.com/liuhaorain/p/3747470.html 本文中很...
    cmlong_閱讀 677評論 2 0
  • 隔壁二班的語文課在預(yù)備鈴聲響時,就會按時地響起一片掌聲,我都羨慕嫉妒了。 那是陳校長的課——偶爾經(jīng)過,陳校長那如鳴...
    秋笏笑月閱讀 329評論 0 2
  • 遙望星河守蒼穹,銀漢迢迢不可期。 盈盈難尋終不遇,始是夜半無語時。
    巴圖魯閱讀 304評論 0 1

友情鏈接更多精彩內(nèi)容