參考:ASM 4 user guide
第一部分 core API
第二章 類
2.1.1概觀
編譯后的類包括:
l 一個描述部分:包括修飾語(比如public或PRivate)、名字、父類、接口或者注釋區域。
l 類中每個域聲明的部分。
l 類中每個方法以及構造函數聲明的部分。也包含了方法編譯后的代碼,它是一系列java字節碼指令的形式。
編譯后的類結構如下:
2.1.2內部名(internal name)
類或者接口使用內部名,內部名就是類的全限定名,即帶斜杠的全稱。
例如,String的internal name為 java/lang/String.
2.1.3類型描述符
內部名只被用在類或者接口名字。其他的使用類型描述符。
String is Ljava/lang/String;
類的描述符以L開頭,以分號結尾。
數組類型以方括號開頭。
2.1.4方法描述
方法描述符以圓括號開始,圓括號中是每個參數的類型(每個參數類型連著寫,之間沒有空格或者逗號之類),圓括號后面是方法的返回值類型。 方法描述符不包括方法的名字和參數名字。
2.2 接口和組件
2.2.1 描述
ASM API對于產生和轉換編譯后的類是基于ClassVistor抽象類的。visitField返回一個FieldVistor,這個規則在FieldVistor中也是遞歸的。
ClassVistor中方法調用的順序:
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
visit必須首先調用,接著最多一個visitSource,接著最多一個visitOuterClass, 接著任意數量個visitAnnotation和visitAttribute,接著任意數量個visitInnerClass、visitField和visitMethod,最后調用visitEnd。
ASM提供了三種基于ClassVisitor API的核心組件來產生和轉換類,
l ClassReader類解析一個給定的編譯好的類的字節數組,調用ClassVisitor實例中的visitXXX方法,這個ClassVistor實例是作為accept的參數傳入的。它可以被看做是事件的生產者。
l ClassWriter類是ClassVistor的子類,它將編譯后的類直接以二進制的形式構建。它輸出一個包括編譯好的類的字節數組(可以通過toByteArray方法獲得)。它被看做是事件的消費者。
l ClassVistor類代理了所有的來自其他ClassVistor實例的方法調用。它被看做是事件的過濾者。
2.2.2 解析類
解析一個現有的類需要的組件只是ClassReader。例子,我們需要打印一個類的內容,首先我們寫一個ClassVistor的子類來打印它所訪問的類的信息。
ClassPrintercp=newClassPrinter();ClassReadercr=newClassReader("java.lang.Runnable");cr.accept(cp,0);
由于ClassPrinter是ClassVistor的子類,前面提到了,ClassReader的accept方法需要傳入一個ClassVistor,所以講將cp作為accept的參數。
運行結果是:
java/lang/Runnableextendsjava/lang/Object{run()V}
注意,有許多方法構建ClassReader實例。可以被訪問的類可以是用名字標注的(如上例),或者通過字節數組值或者作為一個InputStream。可以通過ClassLoader的getResourceAsStream方法來獲得一個input stream。如:
cl.getResourceAsStream(classname.replace(’.’,’/’) + ".class");
2.2.3 產生類
為了產生一個類,唯一需要的組件就是ClassWriter組件。
可以借助ClassLoader(說明中有兩種方法)動態加載一個產生的類。
2.2.4 轉換類
這里講述將ClassReader和ClassWriter組件一起用,結果就是被Class reader解析的類會被class writer重建。
byte[] b1 = ...;ClassWriter cw = new ClassWriter(0);// cv forwards all events to cwClassVisitor cv = new ClassVisitor(ASM4, cw) { };ClassReader cr = new ClassReader(b1);cr.accept(cv, 0);byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
優化:
使用上面的方法,僅僅修改了jdk版本,絕大多數沒有改變。b2是通過抓取b1的方式的方式構建的。更高效的做法是直接拷貝不需要改變的部分,這樣,這一部分就不需進行解析和產生響應的事件了。ASM可以自動進行這樣的優化。
2.2.5 刪除類成員
不將函數調用進行轉發,相當于相應的類元素被移除了。
但是這樣的策略對fields和methods是無法奏效的,因為visitField和visitMethod方法必須有一個返回值,所以此時返回一個null即可。
注意:想要指明一個方法需要同時支出函數名和描述符。因為可能會有很多方法具有相同的名字但是具有不同的參數。
2.2.6添加類成員
和不轉發方法調用不同的是,轉發的更多就相當于添加了一些類元素。
Note: in fact theonly truly correct solution is to add new members by making additional calls inthe visitEnd method. Indeed a class must not contain duplicate members, and theonly way to be sure that a new member is unique is to compare it with all the existingmembers, which can only be done once they have all been visited, i.e. in thevisitEnd method. This is rather constraining. Using generated names that areunlikely to be used by a programmer, such as _counter$ or _4B7F_ is sufficient inpractice to avoid duplicate members without having to add them in visitEnd.Note that, as discussed in the first chapter, the tree API does not have thislimitation: it is possible to add new members at any time inside atransformation with this API.
示例:添加一個變量的做法,在visitField中判斷要添加的變量是否已經存在,若不存在,則在visitEnd方法中添加這個變量。
新聞熱點
疑難解答