天天看點

深入ASM源碼之ClassReader、ClassVisitor、ClassWriter

asm是java中比較流行的用來讀寫位元組碼的類庫,用來基于位元組碼層面對代碼進行分析和轉換。在讀寫的過程中可以加入自定義的邏輯以增強或修改原來已編譯好的位元組碼,比如cglib用它來實作動态代理。asm被設計用于在運作時對java類進行生成和轉換,當然也包括離線處理。asm短小精悍、且速度很快,進而避免在運作時動态生成位元組碼或轉換時對程式速度的影響,又因為它體積小巧,可以在很多記憶體受限的環境中使用。

asm的主要優勢包括如下幾個方面:

1. 它又一個很小,但設計良好并且子產品化的api,且易于使用。

2. 它具有很好的文檔,并且還有eclipse插件。

3. 它支援最新的java版本。

4. 它短小精悍、快速、健壯。

5. 它又一個很大的使用者社群,可以給新使用者提供支援。

6. 它的開源許可允許你幾乎以任何方式來使用它。

在asm的核心實作中,它主要有以下幾個類、接口(在org.objectweb.asm包中):

classreader類:位元組碼的讀取與分析引擎。它采用類似sax的事件讀取機制,每當有事件發生時,調用注冊的classvisitor、annotationvisitor、fieldvisitor、methodvisitor做相應的處理。

classvisitor接口:定義在讀取class位元組碼時會觸發的事件,如類頭解析完成、注解解析、字段解析、方法解析等。

annotationvisitor接口:定義在解析注解時會觸發的事件,如解析到一個基本值類型的注解、enum值類型的注解、array值類型的注解、注解值類型的注解等。

fieldvisitor接口:定義在解析字段時觸發的事件,如解析到字段上的注解、解析到字段相關的屬性等。

methodvisitor接口:定義在解析方法時觸發的事件,如方法上的注解、屬性、代碼等。

classwriter類:它實作了classvisitor接口,用于拼接位元組碼。

annotationwriter類:它實作了annotationvisitor接口,用于拼接注解相關位元組碼。

fieldwriter類:它實作了fieldvisitor接口,用于拼接字段相關位元組碼。

methodwriter類:它實作了methodvisitor接口,用于拼接方法相關位元組碼。

signaturereader類:對類定義、字段定義、方法定義、本地變量定義的簽名的解析。signature因範型引入,用于存儲範型定義時的中繼資料(因為這些中繼資料在運作時會被擦除)。

signaturevisitor接口:定義在解析signature時會觸發的事件,如正常的type參數、類或接口的邊界等。

signaturewriter類:它實作了signaturevisitor接口,用于拼接範型相關位元組碼。

attribute類:位元組碼中屬性的類抽象。

bytevector類:位元組碼二進制存儲的容器。

opcodes接口:位元組碼指令的一些常量定義。

type類:類型相關的常量定義以及一些基于其上的操作。

他們之間的類圖關系如下:

深入ASM源碼之ClassReader、ClassVisitor、ClassWriter

在建構classreader執行個體時,它首先儲存位元組碼二進制數組b,然後建立items數組,數組的長度在位元組碼數組的第8、9個位元組指定(最前面4個位元組是魔數cafebabe,之後2個位元組是次版本号,再後2個位元組是主版本号),每個item表示常量池項在位元組碼數組的偏移量加1(常量池中每個項由1個位元組的type和緊跟的位元組數組表示,常量池項有12種類型,其中constant_fieldref_info、constant_methodref_info、constant_interfacemethodref_info、constant_nameandtype_info包括其類型位元組占用5個位元組,另外4個位元組每2個位元組為字段、方法等所在的類、其名稱、描述符在目前常量池中constant_utf8_info類型的引用;constant_integer_info、constant_float_info包括其類型位元組占用5個位元組,另外四個位元組為其對應的值;constant_class_info、constant_string_info包括其類型位元組占用3個位元組,另外兩個位元組為在目前常量池constant_utf8_info項的索引;constant_utf8_info類型第1個位元組表示類型,第2、3個位元組為該項所表示的字元串的長度);constant_double_info、constant_long_info加類型位元組為9個字;maxstringlength表示最長的utf8類型的常量池項的值,用于決定在解析constant_utf8_info類型項時最大需要的字元數組;header表示常量池之後的位元組碼的第一個位元組。

在調用classreader的accept方法時,它解析位元組碼中常量池之後的所有元素。緊接着常量池的2個位元組是該類的access标簽:acc_public、acc_final等;之後2個位元組為目前類名在常量池constant_utf8_info類型的索引;之後2個位元組為其父類名在常量池constant_utf8_info類型的索引(索引值0表示父類為null,即直接繼承自object類);再之後為其實作的接口數長度和對應各個接口名在常量池中constant_utf8_info類型的索引值;暫時先跳過field和method定義資訊,解析類的attribute表,它用兩個位元組表達attribute數組的長度,每個attribute項中最前面2個位元組是attribute名稱:sourcefile(讀取sourcefile值)、innerclasses(暫時紀錄起始索引)、enclosingmethod(紀錄目前匿名類、本地類包含者類名以及包含者的方法名和描述符)、signature(類的簽名資訊,用于範型)、runtimevisibleannotations(暫時紀錄起始索引)、deprecated(表識屬性)、synthetic(辨別屬性)、sourcedebugextension(為調試器提供的自定義擴充資訊,讀取成一個字元串)、runtimeinvisibleannotations(暫時紀錄起始索引),對其他不識别的屬性,紀錄成attribute鍊,如果attribute名稱符合在accept中attribute數組中指定的attribute名,則替換傳入的attribute數組對應的項;根據解析出來的資訊調用以下visit方法:

void visit(int version, int access, string name, string signature, string supername, string[] interfaces);

// sourcefile, sourcedebug

void visitsource(string source, string debug);

// enclosingmethod attribute: enclosingowner, enclosingname, enclosingdesc. 

// note: only when the class has enclosingmethod attribute, meaning the class is a local class or an anonymous class

void visitouterclass(string owner, string name, string desc);

依次解析runtimevisibleannotations和runtimeinvisibleannotations屬性,首先解析定義的annotation的描述符以及運作時可見flag,傳回使用者自定義的annotationvisitor:

annotationvisitor visitannotation(string desc, boolean visible);

對每個定義的annotation,解析其鍵值對,并根據不同的annotation字段值調用annotationvisitor中的方法,在所有解析結束後,調用annotationvisitor.visitend方法:

public interface annotationvisitor {

    // 對基本類型的數組,依然采用該方法,visitarray隻是在非基本類型時調用。

    void visit(string name, object value);

    void visitenum(string name, string desc, string value);

    annotationvisitor visitannotation(string name, string desc);

    annotationvisitor visitarray(string name);

    void visitend();

}

之前解析出的attribute連結清單(非标準的attribute定義),對每個attribute執行個體,調用classvisitor中的visitattribute方法:

void visitattribute(attribute attr);

attribute類包含type字段和一個位元組數組:

public class attribute {

    public final string type;

    byte[] value;

    attribute next;

對每個innerclasses屬性,解析并調用classvisitor的visitinnerclass方法(該屬性事實上儲存了所有其直接内部類以及它本身到最頂層類的路徑):

void visitinnerclass(string name, string outername, string innername, int access);

解析字段,它緊跟接口數組定義之後,最前面的2個位元組為字段數組的長度,對每個字段,前面2個位元組為通路flag定義,再後2個位元組為name索引,以及2個位元組的描述符索引,然後解析其attribute資訊:constantvalue、signature、deprecated、synthetic、runtimevisibleannotations、runtimeinvisibleannotations以及非标準定義的attribute鍊,而後調用classvisitor的visitfield方法,傳回fieldvisitor執行個體:

// 其中value為靜态字段的初始化值(對非靜态字段,它的初始化必須由構造函數實作),如果沒有初始化值,該值為null。

fieldvisitor visitfield(int access, string name, string desc, string signature, object value);

對傳回的fieldvisitor依次對其annotation以及非标準attribute解析,調用其visit方法,并在完成後調用它的visitend方法:

public interface fieldvisitor {

    annotationvisitor visitannotation(string desc, boolean visible);

    void visitattribute(attribute attr);

解析方法定義,它緊跟字段定義之後,最前面的2個位元組為方法數組長度,對每個方法,前面2個位元組為通路flag定義,再後2個位元組為name索引,以及2個位元組的方法描述符索引,然後解析其attribute資訊:code、exceptions、signature、deprecated、runtimevisibleannotations、annotationdefault、synthetic、runtimeinvisibleannotations、runtimevisibleparameterannotations、runtimeinvisibleparameterannotations以及非标準定義的attribute鍊,如果存在exceptions屬性,解析其異常類數組,之後調用classvisitor的visitmethod方法,傳回methodvisitor執行個體:

methodvisitor visitmethod(int access, string name, string desc, string signature, string[] exceptions);

annotationdefault為對annotation定義時指定預設值的解析;然後依次解析runtimevisibleannotations、runtimeinvisibleannotations、runtimevisibleparameterannotations、runtimeinvisibleparameterannotations等屬性,調用相關annotationvisitor的visit方法;對非标準定義的attribute鍊,依次調用methodvisitor的visitattribute方法:

public interface methodvisitor {

    annotationvisitor visitannotationdefault();

    annotationvisitor visitparameterannotation(int parameter, string desc, boolean visible);

對code屬性解析,讀取2個位元組的最深棧大小、最大local變量數、code占用位元組數,調用methodvisitor的visitcode()方法表示開始解析code屬性,對每條指令,建立一個label執行個體并構成label數組,解析code屬性中的異常表,對每個異常項,調用visittrycatchblock方法:

void visittrycatchblock(label start, label end, label handler, string type);

label包含以下資訊:

/**

 * a label represents a position in the bytecode of a method. labels are used

 * for jump, goto, and switch instructions, and for try catch blocks.

 * 

 * @author eric bruneton

 */

public class label {

    public object info;

    int status;

    int line;

    int position;

    private int referencecount;

    private int[] srcandrefpositions;

    int inputstacktop;

    int outputstackmax;

    frame frame;

    label successor;

    edge successors;

    label next;

解析code屬性中的内部屬性資訊:localvariabletable、localvariabletypetable、linenumbertable、stackmaptable、stackmap以及非标準定義的attribute鍊,對每個label調用其visitlinenumber方法以及對每個frame調用visitframe方法,并且對相應的指令調用相應的方法:

void visitframe(int type, int nlocal, object[] local, int nstack, object[] stack);

// visits a zero operand instruction.

void visitinsn(int opcode);

// visits an instruction with a single int operand.

void visitintinsn(int opcode, int operand);

// visits a local variable instruction. a local variable instruction is an instruction that loads or stores the value of a local variable.

void visitvarinsn(int opcode, int var);

// visits a type instruction. a type instruction is an instruction that takes the internal name of a class as parameter.

void visittypeinsn(int opcode, string type);

// visits a field instruction. a field instruction is an instruction that loads or stores the value of a field of an object.

void visitfieldinsn(int opcode, string owner, string name, string desc);

// visits a method instruction. a method instruction is an instruction that invokes a method.

void visitmethodinsn(int opcode, string owner, string name, string desc);

// visits a jump instruction. a jump instruction is an instruction that may jump to another instruction.

void visitjumpinsn(int opcode, label label);

// visits a label. a label designates the instruction that will be visited just after it.

void visitlabel(label label);

// visits a ldc instruction.

void visitldcinsn(object cst);

// visits an iinc instruction.

void visitiincinsn(int var, int increment);

// visits a tableswitch instruction.

void visittableswitchinsn(int min, int max, label dflt, label[] labels);

// visits a lookupswitch instruction.

void visitlookupswitchinsn(label dflt, int[] keys, label[] labels);

// visits a multianewarray instruction.

void visitmultianewarrayinsn(string desc, int dims);

// visits a try catch block.

void visitlocalvariable(string name, string desc, string signature, label start, label end, int index);

// visits a line number declaration.

void visitlinenumber(int line, label start);

// visits the maximum stack size and the maximum number of local variables of the method.

void visitmaxs(int maxstack, int maxlocals);

最後調用classvisitor的visitend方法:

void visitend();

classwriter繼承自classvisitor接口,可以使用它調用其相應的visit方法動态的構造一個位元組碼類。它包含以下字段資訊:

public class classwriter implements classvisitor {

    //the class reader from which this class writer was constructed, if any.

    classreader cr;

    //minor and major version numbers of the class to be generated.

    int version;

    //index of the next item to be added in the constant pool.

    int index;

    //the constant pool of this class.

    final bytevector pool;

    //the constant pool's hash table data.

    item[] items;

    //the threshold of the constant pool's hash table.

    int threshold;

    //a reusable key used to look for items in the {@link #items} hash table.

    final item key;

    final item key2;

    final item key3;

    //a type table used to temporarily store internal names that will not necessarily be stored in the constant pool. 

    item[] typetable;

    //number of elements in the {@link #typetable} array.

    private short typecount;

    //the access flags of this class.

    private int access;

    //the constant pool item that contains the internal name of this class.

    private int name;

    //the internal name of this class.

    string thisname;

    //the constant pool item that contains the signature of this class.

    private int signature;

    //the constant pool item that contains the internal name of the super class of this class.

    private int supername;

    // number of interfaces implemented or extended by this class or interface.

    private int interfacecount;

    //the interfaces implemented or extended by this class or interface. 

    private int[] interfaces;

    //the index of the constant pool item that contains the name of the source file from which this class was compiled.

    private int sourcefile;

    //the sourcedebug attribute of this class.

    private bytevector sourcedebug;

    //the constant pool item that contains the name of the enclosing class of this class.

    private int enclosingmethodowner;

    //the constant pool item that contains the name and descriptor of the enclosing method of this class.

    private int enclosingmethod;

    //the runtime visible annotations of this class.

    private annotationwriter anns;

    //the runtime invisible annotations of this class.

    private annotationwriter ianns;

    //the non standard attributes of this class.

    private attribute attrs;

    //the number of entries in the innerclasses attribute.

    private int innerclassescount;

    //the innerclasses attribute.

    private bytevector innerclasses;

    //the fields of this class. these fields are stored in a linked list of {@link fieldwriter} objects, linked to each other by their {@link fieldwriter#next} field. this field stores the first element of this list.

    fieldwriter firstfield;

    //this field stores the last element of this list.

    fieldwriter lastfield;

    //the methods of this class. these methods are stored in a linked list of {@link methodwriter} objects, linked to each other by their {@link methodwriter#next} field. this field stores the first element of this list.

    methodwriter firstmethod;

    methodwriter lastmethod;

    //true if the maximum stack size and number of local variables must be automatically computed.

    private final boolean computemaxs;

    //true if the stack map frames must be recomputed from scratch.

    private final boolean computeframes;

   //true if the stack map tables of this class are invalid. 

    boolean invalidframes;

繼續閱讀