位元組碼相對java的意義類似彙編相對c的意義,底層了解的越多越深入,程式就越神奇,一切想法皆有可能實作。學習了下位元組碼架構asm,總結分享下:
api概述。
一、asm庫提供了兩類api接口模型來産生或者修改類位元組碼:
(1)核心api: 基于事件,每個事件代表類的一個元素,如頭事件、方法事件、字段事件等。特點是更快耗費更少的記憶體。
(2)樹型api: 基于對象樹狀結構,字段方法等都可以看做對象樹的一部分。使用相對簡單,但耗費記憶體。
二、api包結構大緻如下:
(1)事件、解析器、生産器類api在包路徑org.objectweb.asm及org.objectweb.asm.signature内。
(2)開發和調試中可以用到的一些核心api在包路徑org.objectweb.asm.util内。
(3)基于對象樹的api在org.objectweb.asm.tree内,同時包括了和事件類api互相轉換的工具。
(4)org.objectweb.asm.tree.analysis為對象樹類api提供了類分析架構。
核心類ap i- class
一、類結構
(1)虛拟機内部定義的類結構圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuEDMwU2Zh1Wavw1clxWam5SbzF2LcFmdhp2LcNXZnFGcvwVbvNmLmJXZwl3chVmL3d3dvw1LcpDc0RHaiojIsJye.png)
(2)類名稱在虛拟機内部标示有所不同,如java.lang.string内部标示為java/lang/string
(3)編譯後的class的字段類型如下:
(4)方法描述内部辨別如下:
二、類相關接口api:
(1)classreader可以看作事件的産生器,能夠讀取解析類的二進制位元組數組,邊解析邊把類的字段或者方法等資訊以事件傳遞給classvisitor的相關visitxxx方法。常見代碼:
// classprinter是classvisitor的執行個體,在内部的visitxxx方法内部定義自己的業務邏輯,如列印輸出
classprinter cp = new classprinter();
classreader cr = new classreader("java.lang.runnable");
cr.accept(cp, 0);
(2)classwriter可以看做時間的一個消費器,是classvisitor的一個子類實作,可以直接建構出類的二進制數組辨別形式,截取下例子:
classwriter cw = new classwriter(0);
// public interface comparable extends mesurable {
cw.visit(v1_5, acc_public + acc_abstract + acc_interface,
"pkg/comparable", null, "java/lang/object",
new string[] { "pkg/mesurable" });
// int less = -1;
cw.visitfield(acc_public + acc_final + acc_static, "less", "i",
null, new integer(-1)).visitend();
// int equal = 0;
cw.visitfield(acc_public + acc_final + acc_static, "equal", "i",
null, new integer(0)).visitend();
// int greater = 1;
cw.visitfield(acc_public + acc_final + acc_static, "greater", "i",
null, new integer(1)).visitend();
// int compareto(object o);
cw.visitmethod(acc_public + acc_abstract, "compareto",
"(ljava/lang/object;)i", null, null).visitend();
cw.visitend();
byte[] b = cw.tobytearray();
(3)classvisitor可以将接收到的所有監聽到事件的方法調用傳遞給另外一個classvisitor,可以看做一個事件過濾器,針對classvisitor添加自己的業務邏輯就可以實作神奇的位元組碼修改:
// 轉換前的位元組碼
byte[] b1 = ...;
classreader cr = new classreader(b);
classwriter cw = new classwriter(cr, 0);
// 在這裡面定義你想要的業務,一切皆有可能
classvisitor cv = new changeversionadapter(cw);
cr.accept(cv, 0);
// 轉換後的位元組碼
byte[] b2 = cw.tobytearray();
(4)classvisitor内可以實作删除類成員(如visitmethod傳回null删除方法)、添加類成員(visitend添加visitfield),classvisitor也可以是個調用鍊。
三、開發工具api:
(1)type提供了内部類型和java類型的轉換,如前面提到的string轉換内部類名可以這樣實作:
// 僅用于類或者接口,java/lang/string
string internalname = type.gettype(string.class).getinternalname();
string轉換為内部描述可以這樣:
// 轉換内部描述:ljava/lang/string;
string internaldesc = type.gettype(string.class).getdescriptor();
string intdesc = type.int_type.getdescriptor();
方法參數和傳回值示例:
// 傳回type.int_type
type.getargumenttypes("(i)v");
// type.void_type
type.getreturntype("(i)v")
(2)traceclassvisitor可以文本形式列印出轉換後的位元組碼到底是什麼樣子的:
// printwriter會輸出轉換後的位元組碼
traceclassvisitor cv = new traceclassvisitor(cw, printwriter);
cv.visit(...);
...
cv.visitend();
byte b[] = cw.tobytearray();
(3)轉換後的位元組碼是否格式合法可以用checkclassadapter校驗:
traceclassvisitor tcv = new traceclassvisitor(cw, printwriter);
// 發現問題會抛出異常
checkclassadapter cv = new checkclassadapter(tcv);
(4)如果有的類實在複雜到你很難寫出位元組碼來生成它,asm提供自動把複雜類列印出位元組碼的工具asmifier,通過指令行方式輸出,太強大了:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.asmifier \
java.lang.runnable
核心類ap i- method
一、方法結構
(1)方法執行模型:java代碼線上程内執行,每個線程有自己響應的棧stack,每個棧由棧幀frame構成,每個frame代表一個方法調用。每個frame包括本地變量和操作棧,本地變量用序号通路,變量使用slot存儲,除了long和double需要2個slot外其他變量都有一個slot存儲。一個frame構成如下:
(2)位元組碼指令由操作碼和參數構成,可以分為兩類:一類是将值在本地變量和操作數棧來回複制移動的,如iload\lload\fload\dload\aload\istore\lstore\fstore等。另一類是操作操作數棧上value的,包括一大批的指令,操作棧stack的、定義常量的、算術計算的、類型轉換的、操作類、操作字段、操作方法、操作數組、跳轉、傳回。
二、方法接口api群組件
(1)methodvisitor可以被classvisitor的visitmethod方法傳回,用于方法的生成和轉化。methodvisitor的内部方法嚴格按照如下順序調用:
多個methodvisitor時,每個都是一個獨立的轉換方法執行個體,是以多個methodvisitor之間可以交替執行。類似類api,methodvisitor使用到classreader和classwriter。classwriter參數不同意義不同:
// asm不進行任何自動計算,需要我們自己計算棧幀、本地變量、操作數棧的大小
new classwriter(0);
(2)一個生産getf()方法的指令如下:
mv.visitcode();
mv.visitvarinsn(aload, 0);
mv.visitfieldinsn(getfield, "pkg/bean", "f", "i");
mv.visitinsn(ireturn);
mv.visitmaxs(1, 1);
mv.visitend();
(3)methodvisitor和classvisitor通常配合使用來轉換方法,可以用于删除方法指令、執行狀态無關的轉換、執行狀态相關的轉換(一般需要定義狀态屬性到methodvisitor内記憶)及其他更複雜的轉換。
三、方法開發工具api
(1)基本的type、traceclassvisitor、checkclassadapter、asmifier同樣适用。
(2)analyzeradapter記錄了visitframe調用後棧幀映射的資訊,必須要和classreader#expand_frames配合使用。每個visitx指令都被代理到調用鍊,并通過本地變量locals和棧stack模拟出隊棧幀映射的影響,這樣後面的visitor可以得到棧幀映射的目前狀态。
(3)localvariablessorter可以對方法内的變量自動編号,對于需要修改位元組碼添加變量的場景非常适用。還可以喝analyzeradapter配合構造成過濾鍊配合使用。
(4)需要在方法開始和結束為止添加指令的場景适合使用adviceadapter,而且使用與構造方法。