深入ASM原始碼之ClassReader、ClassVisitor、ClassWriter
阿新 • • 發佈:2019-01-26
概述
ASM是Java中比較流行的用來讀寫位元組碼的類庫,用來基於位元組碼層面對程式碼進行分析和轉換。在讀寫的過程中可以加入自定義的邏輯以增強或修改原來已編譯好的位元組碼,比如CGLIB用它來實現動態代理。ASM被設計用於在執行時對Java類進行生成和轉換,當然也包括離線處理。ASM短小精悍、且速度很快,從而避免在執行時動態生成位元組碼或轉換時對程式速度的影響,又因為它體積小巧,可以在很多記憶體受限的環境中使用。ASM的主要優勢包括如下幾個方面:
1. 它又一個很小,但設計良好並且模組化的API,且易於使用。
2. 它具有很好的文件,並且還有eclipse外掛。
3. 它支援最新的Java版本。
4. 它短小精悍、快速、健壯。
5. 它又一個很大的使用者社群,可以給新使用者提供支援。
6. 它的開源許可允許你幾乎以任何方式來使用它。
關於ASM的詳細文件可以參考:ASM 3.0:Java位元組碼引擎庫,寫的很詳細的一個文件。
ASM Core設計一覽
在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類:型別相關的常量定義以及一些基於其上的操作。
他們之間的類圖關係如下:
ClassReader實現
ClassReader是ASM中最核心的實現,它用於讀取並解析Class位元組碼。類位元組碼格式可以具體參考:《Java位元組碼格式詳解1》、《Java位元組碼格式詳解2》、《Java位元組碼格式詳解3》在構建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, sourceDebugvoid 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);
void visitEnd();
}
解析方法定義,它緊跟欄位定義之後,最前面的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 visitAnnotation(String desc, boolean visible);
AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible);
void visitAttribute(Attribute attr);
}
對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 visitTryCatchBlock(Label start, Label end, Label handler, String type);
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實現
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;
//A reusable key used to look for items in the {@link #items} hash table.
final Item key2;
//A reusable key used to look for items in the {@link #items} hash table.
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;
//This field stores the last element of this list.
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;
}