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

為什么選擇 ASM?

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

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

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

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

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

首先,Proxy 編程是面向接口的。下面我們會看到,Proxy 并不負(fù)責(zé)實(shí)例化對象,和 Decorator 模式一樣,要把 Account 定義成一個接口,然后在 AccountImpl 里實(shí)現(xiàn) Account 接口,接著實(shí)現(xiàn)一個 InvocationHandler Account 方法被調(diào)用的時候,虛擬機(jī)都會實(shí)際調(diào)用這個 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);         } }                


后,在應(yīng)用程序中指定 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 生成的是實(shí)現(xiàn) Account 接口的對象而不是 AccountImpl 的子類。這對于軟件架構(gòu)設(shè)計,尤其對于既有軟件系統(tǒng)是有一定掣肘的。
    Proxy 畢竟是通過反射實(shí)現(xiàn)的,必須在效率上付出代價:有實(shí)驗(yàn)數(shù)據(jù)表明,調(diào)用反射比一般的函數(shù)開銷至少要大 10 倍。而且,從程序?qū)崿F(xiàn)上可以看出,對 proxy class 的所有方法調(diào)用都要通過使用反射的 invoke 方法。因此,對于性能關(guān)鍵的應(yīng)用,使用 proxy class 是需要精心考慮的,以避免反射成為整個應(yīng)用的瓶頸。

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

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

ASM 已經(jīng)被廣泛應(yīng)用于一系列 Java 項(xiàng)目: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 編譯器產(chǎn)生的 .class 文件。這些文件具有嚴(yán)格定義的格式。為了更好的理解 ASM,首先對 Java 類文件格式作一點(diǎn)簡單的介紹。Java 源文件經(jīng)過 javac 編譯器編譯之后,將會生成對應(yīng)的二進(jìn)制文件(如下圖所示)。每個合法的 Java 類文件都具備精確的定義,而正是這種精確的定義,才使得 Java 虛擬機(jī)得以正確讀取和解釋所有的 Java 類文件。

圖 2. ASM – Javac 流程

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

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

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

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