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

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

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末为障,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子放祟,更是在濱河造成了極大的恐慌鳍怨,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跪妥,死亡現(xiàn)場(chǎng)離奇詭異鞋喇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)眉撵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門侦香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纽疟,你說我怎么就攤上這事罐韩。” “怎么了污朽?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵散吵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蟆肆,道長(zhǎng)矾睦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任颓芭,我火速辦了婚禮,結(jié)果婚禮上柬赐,老公的妹妹穿的比我還像新娘亡问。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布州藕。 她就那樣靜靜地躺著束世,像睡著了一般。 火紅的嫁衣襯著肌膚如雪床玻。 梳的紋絲不亂的頭發(fā)上毁涉,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音锈死,去河邊找鬼贫堰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛待牵,可吹牛的內(nèi)容都是我干的其屏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缨该,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼偎行!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贰拿,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蛤袒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后膨更,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妙真,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年询一,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隐孽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡健蕊,死狀恐怖菱阵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缩功,我是刑警寧澤晴及,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站嫡锌,受9級(jí)特大地震影響虑稼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜势木,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一蛛倦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧啦桌,春花似錦溯壶、人聲如沸及皂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)验烧。三九已至,卻和暖如春又跛,著一層夾襖步出監(jiān)牢的瞬間碍拆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工慨蓝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留感混,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓菌仁,卻偏偏與公主長(zhǎng)得像浩习,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子济丘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

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