您的位置:軟件測試 > 開源軟件測試 > 開源單元測試工具 >
AOP的利器:ASM 3.0介紹
作者:網絡轉載 發(fā)布時間:[ 2013/2/22 13:41:25 ] 推薦標簽:

為什么選擇 ASM?

直接的改造 Java 類的方法莫過于直接改寫 class 文件。Java 規(guī)范詳細說明了class 文件的格式,直接編輯字節(jié)碼確實可以改變 Java 類的行為。直到,還有一些 Java 高手們使用原始的工具,如 UltraEdit 這樣的編輯器對 class 文件動手術。是的,這是直接的方法,但是要求使用者對 Java class 文件的格式了熟于心:小心地推算出想改造的函數相對文件首部的偏移量,同時重新計算 class 文件的校驗碼以通過 Java 虛擬機的安全機制。

Java 5 中提供的 Instrument 包也可以提供類似的功能:啟動時往 Java 虛擬機中掛上一個用戶定義的 hook 程序,可以在裝入特定類的時候改變特定類的字節(jié)碼,從而改變該類的行為。但是其缺點也是明顯的:

    Instrument 包是在整個虛擬機上掛了一個鉤子程序,每次裝入一個新類的時候,都必須執(zhí)行一遍這段程序,即使這個類不需要改變。
    直接改變字節(jié)碼事實上類似于直接改寫 class 文件,無論是調用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer),還是 Instrument.redefineClasses(ClassDefinition[] definitions),都必須提供新 Java 類的字節(jié)碼。也是說,同直接改寫 class 文件一樣,使用 Instrument 也必須了解想改造的方法相對類首部的偏移量,才能在適當的位置上插入新的代碼。

盡管 Instrument 可以改造類,但事實上,Instrument 更適用于監(jiān)控和控制虛擬機的行為。

一種比較理想且流行的方法是使用 java.lang.ref.proxy。我們仍舊使用上面的例子,給 Account 類加上 checkSecurity 功能:

首先,Proxy 編程是面向接口的。下面我們會看到,Proxy 并不負責實例化對象,和 Decorator 模式一樣,要把 Account 定義成一個接口,然后在 AccountImpl 里實現 Account 接口,接著實現一個 InvocationHandler Account 方法被調用的時候,虛擬機都會實際調用這個 InvocationHandler 的 invoke 方法:

 class SecurityProxyInvocationHandler implements InvocationHandler {         private Object proxyedObject;         public SecurityProxyInvocationHandler(Object o) {                 proxyedObject = o;         }                          public Object invoke(Object object, Method method, Object[] arguments)                 throws Throwable {                                       if (object instanceof Account && method.getName().equals("opertaion")) {                         SecurityChecker.checkSecurity();                 }                 return method.invoke(proxyedObject, arguments);         } }                


后,在應用程序中指定 InvocationHandler 生成代理對象:

 public static void main(String[] args) {         Account account = (Account) Proxy.newProxyInstance(                 Account.class.getClassLoader(),                 new Class[] { Account.class },                 new SecurityProxyInvocationHandler(new AccountImpl())         );         account.function(); }         


其不足之處在于:

    Proxy 是面向接口的,所有使用 Proxy 的對象都必須定義一個接口,而且用這些對象的代碼也必須是對接口編程的:Proxy 生成的對象是接口一致的而不是對象一致的:例子中 Proxy.newProxyInstance 生成的是實現 Account 接口的對象而不是 AccountImpl 的子類。這對于軟件架構設計,尤其對于既有軟件系統(tǒng)是有一定掣肘的。
    Proxy 畢竟是通過反射實現的,必須在效率上付出代價:有實驗數據表明,調用反射比一般的函數開銷至少要大 10 倍。而且,從程序實現上可以看出,對 proxy class 的所有方法調用都要通過使用反射的 invoke 方法。因此,對于性能關鍵的應用,使用 proxy class 是需要精心考慮的,以避免反射成為整個應用的瓶頸。

ASM 能夠通過改造既有類,直接生成需要的代碼。增強的代碼是硬編碼在新生成的類文件內部的,沒有反射帶來性能上的付出。同時,ASM 與 Proxy 編程不同,不需要為增強代碼而新定義一個接口,生成的代碼可以覆蓋原來的類,或者是原始類的子類。它是一個普通的 Java 類而不是 proxy 類,甚至可以在應用程序的類框架中擁有自己的位置,派生自己的子類。

相比于其他流行的 Java 字節(jié)碼操縱工具,ASM 更小更快。ASM 具有類似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分別有 350k 和 150k。同時,同樣類轉換的負載,如果 ASM 是 60% 的話,BCEL 需要 700%,而 SERP 需要 1 或者更多。

ASM 已經被廣泛應用于一系列 Java 項目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。Hibernate 和 Spring 也通過 cglib,另一個更高層一些的自動代碼生成工具使用了 ASM。

Java 類文件概述

所謂 Java 類文件,是通常用 javac 編譯器產生的 .class 文件。這些文件具有嚴格定義的格式。為了更好的理解 ASM,首先對 Java 類文件格式作一點簡單的介紹。Java 源文件經過 javac 編譯器編譯之后,將會生成對應的二進制文件(如下圖所示)。每個合法的 Java 類文件都具備精確的定義,而正是這種精確的定義,才使得 Java 虛擬機得以正確讀取和解釋所有的 Java 類文件。

圖 2. ASM – Javac 流程

Java 類文件是 8 位字節(jié)的二進制流。數據項按順序存儲在 class 文件中,相鄰的項之間沒有間隔,這使得 class 文件變得緊湊,減少存儲空間。在 Java 類文件中包含了許多大小不同的項,由于每一項的結構都有嚴格規(guī)定,這使得 class 文件能夠從頭到尾被順利地解析。下面讓我們來看一下 Java 類文件的內部結構,以便對此有個大致的認識。

例如,一個簡單的 Hello World 程序:

 public class HelloWorld {         public static void main(String[] args) {                 System.out.println("Hello world");         } }

上一頁123下一頁
軟件測試工具 | 聯系我們 | 投訴建議 | 誠聘英才 | 申請使用列表 | 網站地圖
滬ICP備07036474 2003-2017 版權所有 上海澤眾軟件科技有限公司 Shanghai ZeZhong Software Co.,Ltd