框架結構
ASM字節碼處理框架是用java開發的而且使用基于訪問者模式生成字節碼及驅動類到字節碼的轉換。這答應開發人員避免直接處理方法字節碼中的類常量池及偏移,因此為開發人員隱藏了字節碼的復雜性并且相對于其他類似工具如BCEL, SERP, or Javassist提供了更好的性能。
ASM分為幾個包更方便靈活地構建。包結構圖如圖1。
Figure 1. Arrangement of ASM packages
·Core包提供了讀/寫/轉換字節碼的API而且是其他包的基礎。這個包已經足夠生成Java字節碼而且能夠實現大部分的字節碼轉換。
·Tree包提供了Java字節碼的內存內表示。
·Analysis包為存儲在來自Tree包結構中的Java方法字節碼提供了基礎的數據流分析和類型檢查算法。
·Commons包(ASM2.0增加)提供了幾個通用的字節碼轉換和簡化字節碼生成的適配器。
·Util包包含幾個助手類和簡單的字節碼較驗器來方便開發和測試。
·xml包提供了與XML文件相互轉換的字節碼結構適配器,及兼容SAX而且答應使用XSLT來定義字節碼轉換方式的適配器。
后面幾節會給出ASM框架中Core包的介紹。為了更好地理解這個包的組織結構,你最好有一些在JVM規范中定義的字節碼結構的基礎了解。下面是較高級別的類文件格式圖([*]標識重復的結構)
[1]-------------------------------------------+
Header and Constant Stack
+--------------------------------------------+
[*] Class Attributes
[2]------------+------------------------------+
[*] Fields Field Name, Descriptor, etc
+------------------------------+
[*] Field Attributes
[3]------------+------------------------------+
[*] Methods Method Name, Descriptor, etc
+------------------------------
Method max stack and locals
------------------------------
[*] Method Code table
------------------------------
[*] Method Exception table
------------------------------
[*] Method Code Attributes
+------------------------------
[*] Method Attributes
+-------------+------------------------------+
需要注重的一些地方:
·所有使用在類結構中的描述符,字符串和其他常量都存儲在類文件開始的常量堆棧中,來自其他結構的引用是基于堆棧的序號。
·每一個類必須包含頭部(包括類名,父類,接口等)和常量堆棧。而其他元素如字段列表/方法列表/屬性列表都是可選的。
·每一個方法段包含相同的頭信息和最大最小局部變量數的信息,這些是用來校驗字節碼的。對非抽象和非原生方法,還包含一個方法指令表,一個異常表及代碼屬性。此外,還可能有其他的方法屬性。
·類的每一個屬性,成員/方法/方法代碼都有自己的名字,具體細節可參考JVM規范的類文件格式部分。這些屬性代表字節碼的各種信息,如源文件名/內部類/標識(用來存儲泛型)/行號/局部變量表和注解。JVM規范也答應定義自定義的屬性來包含更多的信息但標準實現的VM不會識別。注:Java5注解實際上已經廢棄了那些自定義屬性,因為注解在主義上答應你表達更多的東西。
·方法代碼表包含JVM的指令列表。一些指令(就像異常/行號/局部變量表)使用代碼表中的偏移值并且所有這些偏移的值可能需要在指令從方法代碼表中增刪時相應調整。
如你所見,字節碼轉換并不輕易。但是,ASM框架減少了潛在的結構復雜性并且提供簡化的API答應所有字節碼信息的訪問和復雜的轉換。
基于事件的字節碼處理
Core包使用推方案(類似訪問者模式,在SAX API就使用了這種模式處理XML)來遍歷復雜的字節碼結構。ASM定義了幾個接口,如ClassVisitor,FieldVisitor,MethodVisitor和AnnotationVisitor。AnnotationVisitor是一個非凡的接口答應你表達層次的注解結構。下面的幾幅圖顯示這些接口是如何相互交互及配合使用實現字節碼轉換和從字節碼獲取信息。
Core包邏輯上可憐分為兩大部分:
1、字節碼生產者,如ClassReader或者按正確順序調用了上面的訪問者類的方法的自定義類。
2、字節碼消費者,如輸出器(ClassWriter, FieldWriter, MethodWriter, and AnnotationWriter),適配器(ClassAdapter and MethodAdapter)或者其他實現了訪問者接口的類。
圖2給出了通用生產者/消費者交互過程的時序圖。
Figure 2. Sequence diagram for PRodUCer-consumer interaction
在這個交互過程中,客戶端應用首先創建了ClassReader并調用accept()方法(以ClassVisitor實例作為參數)。然后ClassReader解析類并對每一個字節碼斷發送“visit”事務給ClassVisitor。對循環的上下文,如成員/方法/注解,ClassVisitor可以創建繼續撲克相應接口(FieldVisitor, MethodVisitor, or AnnotationVisitor)的子訪問者并返回給生產者。假如生產者接收到一個空值,他簡單地忽略類的那部分(如在由訪問者驅動的“延遲加載”特性時就不需要解析相應的字節碼部分);否則相應的子上下文事件就傳遞給子訪問者實例。當子上下文結束時,生產者調用visitEnd()方法然后移到下一部分。
字節碼消費者可以通過手工傳遞事件給下一個鏈中的訪問者或者使用來自傳遞所有訪問方法給內部的訪問者的ClassAdapter/ MethodAdapter的訪問者通過“響應鏈”模式連接起來。這些代理者一方面字節碼的消費者方面另一方面也作為字節碼的生產者。他們在實現特定的字節碼轉換時可以修改原始的代理方式:
1、訪問調用代理可以在刪除類成員/方法/方法指令時被忽略。
2、訪問調用參數可以在重命名類/方法/類型時被修改。
3、新訪問調用可以在引入新成員/方法/注入新代碼到現存代碼時被增加。
ClassWriter訪問者可以終結整個處理鏈,他也是最終字節碼的生成者。例如:
ClassWriter cw = new ClassWriter(computeMax);
ClassVisitor cc = new CheckClassAdapter(cw);
ClassVisitor tv =
new TraceClassVisitor(cc, new PrintWriter(System.out));
ClassVisitor cv = new TransformingClassAdapter(tv);
ClassReader cr = new ClassReader(bytecode);
cr.accept(cv, skipDebug);
byte[] newBytecode = cw.toByteArray();
TraceSignatureVisitor v =
new TraceSignatureVisitor(access);
SignatureReader r = new SignatureReader(sign);
r.accept(v);
String genericDecl = v.getDeclaration();
String genericReturn = v.getReturnType();
String genericExceptions = v.getExceptions();
String methodDecl = genericReturn + " " +
methodName + genericDecl;
if(genericExceptions!=null) {
methodDecl += " throws " + genericExceptions;
}
public class DependencyVisitor implements
AnnotationVisitor, SignatureVisitor,
ClassVisitor, FieldVisitor, MethodVisitor {
...
private String getGroupKey(String name) {為了獲取依靠關系,訪問者接口如ClassVisitor, AnnotationVisitor, FieldVisitor, and MethodVisitor應該選擇性地集成方法的參數。幾個常見的樣例如下:
int n = name.lastIndexOf('/');
if(n>-1) name = name.substring(0, n);
packages.add(name);
return name;
}
private void addName(String name) {
if(name==null) return;
String p = getGroupKey(name);
if(current.containsKey(p)) {
current.put(p, current.get(p)+1);
} else {
current.put(p, 1);
}
}
private void addDesc(String desc) {
addType(Type.getType(desc));
}
private void addType(Type t) {
switch(t.getSort()) {
case Type.ARRAY:
addType(t.getElementType());
break;
case Type.OBJECT:
addName(t.getClassName().replace('.','/'));
break;
}
}
private void addMethodDesc(String desc) {
addType(Type.getReturnType(desc));
Type[] types = Type.getArgumentTypes(desc);
for(int i = 0; i < types.length; i++) {
addType(types[ i]);
}
}
private void addSignature(String sign) {
if(sign!=null) {
new SignatureReader(sign).accept(this);
}
}
private void addTypeSignature(String sign) {
if(sign!=null) {
new SignatureReader(sign).acceptType(this);
}
}
public void visit(int version, int access,
String name, String signature,
String superName, String[] interfaces) {
String p = getGroupKey(name);
current = groups.get(p);
if(current==null) {
current = new HashMap();
groups.put(p, current);
}
if(signature==null) {
addName(superName);
addNames(interfaces);
} else {
addSignature(signature);
}
}
public FieldVisitor visitField(int access,
String name, String desc,
String signature, Object value) {
if(signature==null) {
addDesc(desc);
} else {
addTypeSignature(signature);
}
if(value instanceof Type) {
addType((Type) value);
}
return this;
}
public MethodVisitor visitMethod(int access,
String name, String desc,
String signature, String[] exceptions) {
if(signature==null) {
addMethodDesc(desc);
} else {
addSignature(signature);
}
addNames(exceptions);
return this;
}
public AnnotationVisitor visitAnnotation(
String desc, boolean visible) {
addDesc(desc);
return this;
}
public AnnotationVisitor
visitParameterAnnotation(int parameter,
String desc, boolean visible) {
addDesc(desc);
return this;
}
/**
* Visits a type instruction
* NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
*/
public void visitTypeInsn(int opcode,
String desc) {
if(desc.charAt(0)=='[') {
addDesc(desc);
} else {
addName(desc);
}
}
/**
* Visits a field instruction
* GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
*/
public void visitFieldInsn(int opcode,
String owner, String name, String desc) {
addName(owner);
addDesc(desc);
}
/**
* Visits a method instruction INVOKEVIRTUAL,
* INVOKESPECIAL, INVOKESTATIC or
* INVOKEINTERFACE.
*/
public void visitMethodInsn(int opcode,
String owner, String name, String desc) {
addName(owner);
addMethodDesc(desc);
}
/**
* Visits a LDC instruction.
*/
public void visitLdcInsn(Object cst) {
if(cst instanceof Type) {
addType((Type) cst);
}
}
/**
* Visits a MULTIANEWARRAY instruction.
*/
public void visitMultiANewArrayInsn(
String desc, int dims) {
addDesc(desc);
}
/**
* Visits a try catch block.
*/
public void visitTryCatchBlock(Label start,
Label end, Label handler, String type) {
addName(type);
}
DependencyVisitor v = new DependencyVisitor();
ZipFile f = new ZipFile(jarName);
Enumeration extends ZipEntry> en = f.entries();
while(en.hasMoreElements()) {
ZipEntry e = en.nextElement();
String name = e.getName();
if(name.endsWith(".class")) {
ClassReader cr =
new ClassReader(f.getInputStream(e));
cr.accept(v, false);
}
}
CodeVisitor visitMethod(int access, String name,
String desc, String[] exceptions,
Attribute attrs);
This has been split into several methods in ASM 2.0:
在2.0中已經分為多個方法:
MethodVisitor visitMethod(int access,
String name, String desc, String signature,
String[] exceptions)
AnnotationVisitor visitAnnotation(String desc,
boolean visible)
void visitAttribute(Attribute attr)
新聞熱點
疑難解答