Java 動(dòng)態(tài)代理機制分析及擴展(1)
引言 Java 動(dòng)態(tài)代理機制的出現,使得 Java 開(kāi)發(fā)人員不用手工編寫(xiě)代理類(lèi),只要簡(jiǎn)單地指定一組接口及委托類(lèi)對象,便能動(dòng)態(tài)地獲得代理類(lèi)。代理類(lèi)會(huì )負責將所有的方法調用分派到委托對象上反射執行,在分派執行的過(guò)程中,開(kāi)發(fā)人員還可以按需調整委托類(lèi)對象及其功能,這是一套非常靈活有彈性的代理框架。通過(guò)閱讀本文,讀者將會(huì )對 Java 動(dòng)態(tài)代理機制有更加深入的理解。本文首先從 Java 動(dòng)態(tài)代理的運行機制和特點(diǎn)出發(fā),對其代碼進(jìn)行了分析,推演了動(dòng)態(tài)生成類(lèi)的內部實(shí)現。
回頁(yè)首
代理:設計模式
代理是一種常用的設計模式,其目的就是為其他對象提供一個(gè)代理以控制對某個(gè)對象的訪(fǎng)問(wèn)。代理類(lèi)負責為委托類(lèi)預處理消息,過(guò)濾消息并轉發(fā)消息,以及進(jìn)行消息被委托類(lèi)執行后的后續處理。
圖 1. 代理模式 為了保持行為的一致性,代理類(lèi)和委托類(lèi)通常會(huì )實(shí)現相同的接口,所以在訪(fǎng)問(wèn)者看來(lái)兩者沒(méi)有絲毫的區別。通過(guò)代理類(lèi)這中間一層,能有效控制對委托類(lèi)對象的直接訪(fǎng)問(wèn),也可以很好地隱藏和保護委托類(lèi)對象,同時(shí)也為實(shí)施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。Java 動(dòng)態(tài)代理機制以巧妙的方式近乎完美地實(shí)踐了代理模式的設計理念。
回頁(yè)首
相關(guān)的類(lèi)和接口
要了解 Java 動(dòng)態(tài)代理的機制,首先需要了解以下相關(guān)的類(lèi)或接口:
回頁(yè)首
代理機制及其特點(diǎn)
首先讓我們來(lái)了解一下如何使用 Java 動(dòng)態(tài)代理。具體有如下四步驟:
- 通過(guò)實(shí)現 InvocationHandler 接口創(chuàng )建自己的調用處理器;
- 通過(guò)為 Proxy 類(lèi)指定 ClassLoader 對象和一組 interface 來(lái)創(chuàng )建動(dòng)態(tài)代理類(lèi);
- 通過(guò)反射機制獲得動(dòng)態(tài)代理類(lèi)的構造函數,其唯一參數類(lèi)型是調用處理器接口類(lèi)型;
- 通過(guò)構造函數創(chuàng )建動(dòng)態(tài)代理類(lèi)實(shí)例,構造時(shí)調用處理器對象作為參數被傳入。
清單 3. 動(dòng)態(tài)代理對象創(chuàng )建過(guò)程 // InvocationHandlerImpl 實(shí)現了 InvocationHandler 接口,并能實(shí)現方法調用從代理類(lèi)到委托類(lèi)的分派轉發(fā) // 其內部通常包含指向委托類(lèi)實(shí)例的引用,用于真正執行分派轉發(fā)過(guò)來(lái)的方法調用 InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過(guò) Proxy 為包括 Interface 接口在內的一組接口動(dòng)態(tài)創(chuàng )建代理類(lèi)的類(lèi)對象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通過(guò)反射從生成的類(lèi)對象獲得構造函數對象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通過(guò)構造函數對象創(chuàng )建動(dòng)態(tài)代理類(lèi)實(shí)例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
|
實(shí)際使用過(guò)程更加簡(jiǎn)單,因為 Proxy 的靜態(tài)方法 newProxyInstance 已經(jīng)為我們封裝了步驟 2 到步驟 4 的過(guò)程,所以簡(jiǎn)化后的過(guò)程如下
清單 4. 簡(jiǎn)化的動(dòng)態(tài)代理對象創(chuàng )建過(guò)程 // InvocationHandlerImpl 實(shí)現了 InvocationHandler 接口,并能實(shí)現方法調用從代理類(lèi)到委托類(lèi)的分派轉發(fā) InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過(guò) Proxy 直接創(chuàng )建動(dòng)態(tài)代理類(lèi)實(shí)例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
|
接下來(lái)讓我們來(lái)了解一下 Java 動(dòng)態(tài)代理機制的一些特點(diǎn)。
首先是動(dòng)態(tài)生成的代理類(lèi)本身的一些特點(diǎn)。1)包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因為接口不能被定義為 protect 或 private,所以除 public 之外就是默認的 package 訪(fǎng)問(wèn)級別),那么它將被定義在該接口所在包(假設代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理類(lèi)所在的包就是 com.ibm.developerworks),這樣設計的目的是為了最大程度的保證動(dòng)態(tài)代理類(lèi)不會(huì )因為包管理的問(wèn)題而無(wú)法被成功定義并訪(fǎng)問(wèn);2)類(lèi)修飾符:該代理類(lèi)具有 final 和 public 修飾符,意味著(zhù)它可以被所有的類(lèi)訪(fǎng)問(wèn),但是不能被再度繼承;3)類(lèi)名:格式是“$ProxyN”,其中 N 是一個(gè)逐一遞增的阿拉伯數字,代表 Proxy 類(lèi)第 N 次生成的動(dòng)態(tài)代理類(lèi),值得注意的一點(diǎn)是,并不是每次調用 Proxy 的靜態(tài)方法創(chuàng )建動(dòng)態(tài)代理類(lèi)都會(huì )使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創(chuàng )建動(dòng)態(tài)代理類(lèi),它會(huì )很聰明地返回先前已經(jīng)創(chuàng )建好的代理類(lèi)的類(lèi)對象,而不會(huì )再?lài)L試去創(chuàng )建一個(gè)全新的代理類(lèi),這樣可以節省不必要的代碼重復生成,提高了代理類(lèi)的創(chuàng )建效率。4)類(lèi)繼承關(guān)系:該類(lèi)的繼承關(guān)系如圖:
圖 2. 動(dòng)態(tài)代理類(lèi)的繼承圖 由圖可見(jiàn),Proxy 類(lèi)是它的父類(lèi),這個(gè)規則適用于所有由 Proxy 創(chuàng )建的動(dòng)態(tài)代理類(lèi)。而且該類(lèi)還實(shí)現了其所代理的一組接口,這就是為什么它能夠被安全地類(lèi)型轉換到其所代理的某接口的根本原因。
接下來(lái)讓我們了解一下代理類(lèi)實(shí)例的一些特點(diǎn)。每個(gè)實(shí)例都會(huì )關(guān)聯(lián)一個(gè)調用處理器對象,可以通過(guò) Proxy 提供的靜態(tài)方法 getInvocationHandler 去獲得代理類(lèi)實(shí)例的調用處理器對象。在代理類(lèi)實(shí)例上調用其代理的接口中所聲明的方法時(shí),這些方法最終都會(huì )由調用處理器的 invoke 方法執行,此外,值得注意的是,代理類(lèi)的根類(lèi) java.lang.Object 中有三個(gè)方法也同樣會(huì )被分派到調用處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 類(lèi)型,能夠被代理類(lèi)覆蓋;二是因為這些方法往往呈現出一個(gè)類(lèi)的某種特征屬性,具有一定的區分度,所以為了保證代理類(lèi)與委托類(lèi)對外的一致性,這三個(gè)方法也應該被分派到委托類(lèi)執行。當代理的一組接口有重復聲明的方法且該方法被調用時(shí),代理類(lèi)總是從排在最前面的接口中獲取方法對象并分派給調用處理器,而無(wú)論代理類(lèi)實(shí)例是否正在以該接口(或繼承于該接口的某子接口)的形式被外部引用,因為在代理類(lèi)內部無(wú)法區分其當前的被引用類(lèi)型。
接著(zhù)來(lái)了解一下被代理的一組接口有哪些特點(diǎn)。首先,要注意不能有重復的接口,以避免動(dòng)態(tài)代理類(lèi)代碼生成時(shí)的編譯錯誤。其次,這些接口對于類(lèi)裝載器必須可見(jiàn),否則類(lèi)裝載器將無(wú)法鏈接它們,將會(huì )導致類(lèi)定義失敗。再次,需被代理的所有非 public 的接口必須在同一個(gè)包中,否則代理類(lèi)生成也會(huì )失敗。最后,接口的數目不能超過(guò) 65535,這是 JVM 設定的限制。
最后再來(lái)了解一下異常處理方面的特點(diǎn)。從調用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類(lèi)型的異常,因為所有的異常都繼承于 Throwable 接口,但事實(shí)是否如此呢?答案是否定的,原因是我們必須遵守一個(gè)繼承原則:即子類(lèi)覆蓋父類(lèi)或實(shí)現父接口的方法時(shí),拋出的異常必須在原方法支持的異常列表之內。所以雖然調用處理器理論上講能夠,但實(shí)際上往往受限制,除非父接口中的方法支持拋 Throwable 異常。那么如果在 invoke 方法中的確產(chǎn)生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動(dòng)態(tài)代理類(lèi)已經(jīng)為我們設計好了解決方法:它將會(huì )拋出 UndeclaredThrowableException 異常。這個(gè)異常是一個(gè) RuntimeException 類(lèi)型,所以不會(huì )引起編譯錯誤。通過(guò)該異常的 getCause 方法,還可以獲得原來(lái)那個(gè)不受支持的異常對象,以便于錯誤診斷。
回頁(yè)首
代碼是最好的老師
機制和特點(diǎn)都介紹過(guò)了,接下來(lái)讓我們通過(guò)源代碼來(lái)了解一下 Proxy 到底是如何實(shí)現的。
首先記住 Proxy 的幾個(gè)重要的靜態(tài)變量:
清單 5. Proxy 的重要靜態(tài)變量 // 映射表:用于維護類(lèi)裝載器對象到其對應的代理類(lèi)緩存 private static Map loaderToCache = new WeakHashMap();
// 標記:用于標記一個(gè)動(dòng)態(tài)代理類(lèi)正在被創(chuàng )建中 private static Object pendingGenerationMarker = new Object();
// 同步表:記錄已經(jīng)被創(chuàng )建的動(dòng)態(tài)代理類(lèi)類(lèi)型,主要被方法 isProxyClass 進(jìn)行相關(guān)的判斷 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
// 關(guān)聯(lián)的調用處理器引用 protected InvocationHandler h;
|
然后,來(lái)看一下 Proxy 的構造方法:
清單 6. Proxy 構造方法 // 由于 Proxy 內部從不直接調用構造函數,所以 private 類(lèi)型意味著(zhù)禁止任何調用 private Proxy() {}
// 由于 Proxy 內部從不直接調用構造函數,所以 protected 意味著(zhù)只有子類(lèi)可以調用 protected Proxy(InvocationHandler h) {this.h = h;}
|
接著(zhù),可以快速瀏覽一下 newProxyInstance 方法,因為其相當簡(jiǎn)單:
清單 7. Proxy 靜態(tài)方法 newProxyInstance public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 檢查 h 不為空,否則拋異常 if (h == null) { throw new NullPointerException(); }
// 獲得與制定類(lèi)裝載器和一組接口相關(guān)的代理類(lèi)類(lèi)型對象 Class cl = getProxyClass(loader, interfaces);
// 通過(guò)反射獲取構造函數對象并生成代理類(lèi)實(shí)例 try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
|
由此可見(jiàn),動(dòng)態(tài)代理真正的關(guān)鍵是在 getProxyClass 方法,該方法負責為一組接口動(dòng)態(tài)地生成代理類(lèi)類(lèi)型對象。在該方法內部,您將能看到 Proxy 內的各路英雄(靜態(tài)變量)悉數登場(chǎng)。有點(diǎn)迫不及待了么?那就讓我們一起走進(jìn) Proxy 最最神秘的殿堂去欣賞一番吧。該方法總共可以分為四個(gè)步驟:
- 對這組接口進(jìn)行一定程度的安全檢查,包括檢查接口類(lèi)對象是否對類(lèi)裝載器可見(jiàn)并且與類(lèi)裝載器所能識別的接口類(lèi)對象是完全相同的,還會(huì )檢查確保是 interface 類(lèi)型而不是 class 類(lèi)型。這個(gè)步驟通過(guò)一個(gè)循環(huán)來(lái)完成,檢查通過(guò)后將會(huì )得到一個(gè)包含所有接口名稱(chēng)的字符串數組,記為
String[] interfaceNames??傮w上這部分實(shí)現比較直觀(guān),所以略去大部分代碼,僅保留留如何判斷某類(lèi)或接口是否對特定類(lèi)裝載器可見(jiàn)的相關(guān)代碼。
清單 8. 通過(guò) Class.forName 方法判接口的可見(jiàn)性
try { // 指定接口名字、類(lèi)裝載器對象,同時(shí)制定 initializeBoolean 為 false 表示無(wú)須初始化類(lèi) // 如果方法返回正常這表示可見(jiàn),否則會(huì )拋出 ClassNotFoundException 異常表示不可見(jiàn) interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { }
|
- 從 loaderToCache 映射表中獲取以類(lèi)裝載器對象為關(guān)鍵字所對應的緩存表,如果不存在就創(chuàng )建一個(gè)新的緩存表并更新到 loaderToCache。緩存表是一個(gè) HashMap 實(shí)例,正常情況下它將存放鍵值對(接口名字列表,動(dòng)態(tài)生成的代理類(lèi)的類(lèi)對象引用)。當代理類(lèi)正在被創(chuàng )建時(shí)它會(huì )臨時(shí)保存(接口名字列表,pendingGenerationMarker)。標記 pendingGenerationMarke 的作用是通知后續的同類(lèi)請求(接口數組相同且組內接口排列順序也相同)代理類(lèi)正在被創(chuàng )建,請保持等待直至創(chuàng )建完成。
清單 9. 緩存表的使用
do { // 以接口名字列表作為關(guān)鍵字獲得對應 cache 值 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果已經(jīng)創(chuàng )建,直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { // 代理類(lèi)正在被創(chuàng )建,保持等待 try { cache.wait(); } catch (InterruptedException e) { } // 等待被喚醒,繼續循環(huán)并通過(guò)二次檢查以確保創(chuàng )建完成,否則重新等待 continue; } else { // 標記代理類(lèi)正在被創(chuàng )建 cache.put(key, pendingGenerationMarker); // break 跳出循環(huán)已進(jìn)入創(chuàng )建過(guò)程 break; } while (true);
|
- 動(dòng)態(tài)創(chuàng )建代理類(lèi)的類(lèi)對象。首先是確定代理類(lèi)所在的包,其原則如前所述,如果都為 public 接口,則包名為空字符串表示頂層包;如果所有非 public 接口都在同一個(gè)包,則包名與這些接口的包名相同;如果有多個(gè)非 public 接口且不同包,則拋異常終止代理類(lèi)的生成。確定了包后,就開(kāi)始生成代理類(lèi)的類(lèi)名,同樣如前所述按格式“$ProxyN”生成。類(lèi)名也確定了,接下來(lái)就是見(jiàn)證奇跡的發(fā)生 —— 動(dòng)態(tài)生成代理類(lèi):
清單 10. 動(dòng)態(tài)生成代理類(lèi)
// 動(dòng)態(tài)地生成代理類(lèi)的字節碼數組 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 動(dòng)態(tài)地定義新生成的代理類(lèi) proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }
// 把生成的代理類(lèi)的類(lèi)對象記錄進(jìn) proxyClasses 表 proxyClasses.put(proxyClass, null);
|
由此可見(jiàn),所有的代碼生成的工作都由神秘的 ProxyGenerator 所完成了,當你嘗試去探索這個(gè)類(lèi)時(shí),你所能獲得的信息僅僅是它位于并未公開(kāi)的 sun.misc 包,有若干常量、變量和方法以完成這個(gè)神奇的代碼生成的過(guò)程,但是 sun 并沒(méi)有提供源代碼以供研讀。至于動(dòng)態(tài)類(lèi)的定義,則由 Proxy 的 native 靜態(tài)方法 defineClass0 執行。
- 代碼生成過(guò)程進(jìn)入結尾部分,根據結果更新緩存表,如果成功則將代理類(lèi)的類(lèi)對象引用更新進(jìn)緩存表,否則清楚緩存表中對應關(guān)鍵值,最后喚醒所有可能的正在等待的線(xiàn)程。
走完了以上四個(gè)步驟后,至此,所有的代理類(lèi)生成細節都已介紹完畢,剩下的靜態(tài)方法如 getInvocationHandler 和 isProxyClass 就顯得如此的直觀(guān),只需通過(guò)查詢(xún)相關(guān)變量就可以完成,所以對其的代碼分析就省略了。
回頁(yè)首
代理類(lèi)實(shí)現推演
分析了 Proxy 類(lèi)的源代碼,相信在讀者的腦海中會(huì )對 Java 動(dòng)態(tài)代理機制形成一個(gè)更加清晰的理解,但是,當探索之旅在 sun.misc.ProxyGenerator 類(lèi)處嘎然而止,所有的神秘都匯聚于此時(shí),相信不少讀者也會(huì )對這個(gè) ProxyGenerator 類(lèi)產(chǎn)生有類(lèi)似的疑惑:它到底做了什么呢?它是如何生成動(dòng)態(tài)代理類(lèi)的代碼的呢?誠然,這里也無(wú)法給出確切的答案。還是讓我們帶著(zhù)這些疑惑,一起開(kāi)始探索之旅吧。
事物往往不像其看起來(lái)的復雜,需要的是我們能夠化繁為簡(jiǎn),這樣也許就能有更多撥云見(jiàn)日的機會(huì )。拋開(kāi)所有想象中的未知而復雜的神秘因素,如果讓我們用最簡(jiǎn)單的方法去實(shí)現一個(gè)代理類(lèi),唯一的要求是同樣結合調用處理器實(shí)施方法的分派轉發(fā),您的第一反應將是什么呢?“聽(tīng)起來(lái)似乎并不是很復雜”。的確,掐指算算所涉及的工作無(wú)非包括幾個(gè)反射調用,以及對原始類(lèi)型數據的裝箱或拆箱過(guò)程,其他的似乎都已經(jīng)水到渠成。非常地好,讓我們整理一下思緒,一起來(lái)完成一次完整的推演過(guò)程吧。
清單 11. 代理類(lèi)中方法調用的分派轉發(fā)推演實(shí)現 // 假設需代理接口 Simulator public interface Simulator { short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB; }
// 假設代理類(lèi)為 SimulatorProxy, 其類(lèi)聲明將如下 final public class SimulatorProxy implements Simulator { // 調用處理器對象的引用 protected InvocationHandler handler; // 以調用處理器為參數的構造函數 public SimulatorProxy(InvocationHandler handler){ this.handler = handler; } // 實(shí)現接口方法 simulate public short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB {
// 第一步是獲取 simulate 方法的 Method 對象 java.lang.reflect.Method method = null; try{ method = Simulator.class.getMethod( "simulate", new Class[] {int.class, long.class, String.class} ); } catch(Exception e) { // 異常處理 1(略) } // 第二步是調用 handler 的 invoke 方法分派轉發(fā)方法調用 Object r = null; try { r = handler.invoke(this, method, // 對于原始類(lèi)型參數需要進(jìn)行裝箱操作 new Object[] {new Integer(arg1), new Long(arg2), arg3}); }catch(Throwable e) { // 異常處理 2(略) } // 第三步是返回結果(返回類(lèi)型是原始類(lèi)型則需要進(jìn)行拆箱操作) return ((Short)r).shortValue(); } } |
模擬推演為了突出通用邏輯所以更多地關(guān)注正常流程,而淡化了錯誤處理,但在實(shí)際中錯誤處理同樣非常重要。從以上的推演中我們可以得出一個(gè)非常通用的結構化流程:第一步從代理接口獲取被調用的方法對象,第二步分派方法到調用處理器執行,第三步返回結果。在這之中,所有的信息都是可以已知的,比如接口名、方法名、參數類(lèi)型、返回類(lèi)型以及所需的裝箱和拆箱操作,那么既然我們手工編寫(xiě)是如此,那又有什么理由不相信 ProxyGenerator 不會(huì )做類(lèi)似的實(shí)現呢?至少這是一種比較可能的實(shí)現。
接下來(lái)讓我們把注意力重新回到先前被淡化的錯誤處理上來(lái)。在異常處理 1 處,由于我們有理由確保所有的信息如接口名、方法名和參數類(lèi)型都準確無(wú)誤,所以這部分異常發(fā)生的概率基本為零,所以基本可以忽略。而異常處理 2 處,我們需要思考得更多一些?;叵胍幌?,接口方法可能聲明支持一個(gè)異常列表,而調用處理器 invoke 方法又可能拋出與接口方法不支持的異常,再回想一下先前提及的 Java 動(dòng)態(tài)代理的關(guān)于異常處理的特點(diǎn),對于不支持的異常,必須拋 UndeclaredThrowableException 運行時(shí)異常。所以通過(guò)再次推演,我們可以得出一個(gè)更加清晰的異常處理 2 的情況:
清單 12. 細化的異常處理 2 Object r = null;
try { r = handler.invoke(this, method, new Object[] {new Integer(arg1), new Long(arg2), arg3});
} catch( ExceptionA e) {
// 接口方法支持 ExceptionA,可以?huà)伋?br> throw e;
} catch( ExceptionB e ) { // 接口方法支持 ExceptionB,可以?huà)伋?br> throw e;
} catch(Throwable e) { // 其他不支持的異常,一律拋 UndeclaredThrowableException throw new UndeclaredThrowableException(e); }
|
這樣我們就完成了對動(dòng)態(tài)代理類(lèi)的推演實(shí)現。推演實(shí)現遵循了一個(gè)相對固定的模式,可以適用于任意定義的任何接口,而且代碼生成所需的信息都是可知的,那么有理由相信即使是機器自動(dòng)編寫(xiě)的代碼也有可能延續這樣的風(fēng)格,至少可以保證這是可行的。
回頁(yè)首
美中不足
誠然,Proxy 已經(jīng)設計得非常優(yōu)美,但是還是有一點(diǎn)點(diǎn)小小的遺憾之處,那就是它始終無(wú)法擺脫僅支持 interface 代理的桎梏,因為它的設計注定了這個(gè)遺憾?;叵胍幌履切﹦?dòng)態(tài)生成的代理類(lèi)的繼承關(guān)系圖,它們已經(jīng)注定有一個(gè)共同的父類(lèi)叫 Proxy。Java 的繼承機制注定了這些動(dòng)態(tài)代理類(lèi)們無(wú)法實(shí)現對 class 的動(dòng)態(tài)代理,原因是多繼承在 Java 中本質(zhì)上就行不通。
有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支持 class 動(dòng)態(tài)代理會(huì )更美好。接口和類(lèi)的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的聲明及是否被定義來(lái)考量,有一種兩者的混合體,它的名字叫抽象類(lèi)。實(shí)現對抽象類(lèi)的動(dòng)態(tài)代理,相信也有其內在的價(jià)值。此外,還有一些歷史遺留的類(lèi),它們將因為沒(méi)有實(shí)現任何接口而從此與動(dòng)態(tài)代理永世無(wú)緣。如此種種,不得不說(shuō)是一個(gè)小小的遺憾。
但是,不完美并不等于不偉大,偉大是一種本質(zhì),Java 動(dòng)態(tài)代理就是佐例。
參考資料
作者簡(jiǎn)介
王忠平,軟件工程師,目前在 IBM 上海中國系統技術(shù)實(shí)驗室任職。
何平,軟件工程師,目前在 IBM 上海中國系統技術(shù)實(shí)驗室任職。
======================================================
Java 動(dòng)態(tài)代理機制分析及擴展(2)
本文希望將 Java 動(dòng)態(tài)代理機制從接口擴展到類(lèi),使得類(lèi)能夠享有與接口類(lèi)似的動(dòng)態(tài)代理支持。
設計及特點(diǎn)
新擴展的類(lèi)名為 ProxyEx,將直接繼承于 java.lang.reflect.Proxy,也聲明了與原 Proxy 類(lèi)中同名的 public 靜態(tài)方法,目的是保持與原代理機制在使用方法上的完全一致。
圖 1. ProxyEx 類(lèi)繼承圖 與原代理機制最大的區別在于,動(dòng)態(tài)生成的代理類(lèi)將不再從 Proxy 類(lèi)繼承,改而繼承需被代理的類(lèi)。由于 Java 的單繼承原則,擴展代理機制所支持的類(lèi)數目不得多于一個(gè),但它可以聲明實(shí)現若干接口。包管理的機制與原來(lái)相似,不支持一個(gè)以上的類(lèi)和接口同時(shí)為非 public;如果僅有一個(gè)非 public 的類(lèi)或接口,假設其包為 PackageA,則動(dòng)態(tài)生成的代理類(lèi)將位于包 PackageA;否則將位于被代理的類(lèi)所在的包。生成的代理類(lèi)也被賦予 final 和 public 訪(fǎng)問(wèn)屬性,且其命名規則類(lèi)似地為“父類(lèi)名 +ProxyN”(N 也是遞增的阿拉伯數字)。最后,在異常處理方面則與原來(lái)保持完全一致。
圖 2. 動(dòng)態(tài)生成的代理類(lèi)的繼承圖
回頁(yè)首
模板
通過(guò)對 Java 動(dòng)態(tài)代理機制的推演,我們已經(jīng)獲得了一個(gè)通用的方法模板??梢灶A期的是,通過(guò)模板來(lái)定制和引導代理類(lèi)的代碼生成,是比較可行的方法。我們將主要使用兩個(gè)模板:類(lèi)模板和方法模板。
清單 1. 類(lèi)模板 package &Package; final public class &Name &Extends &Implements { private java.lang.reflect.InvocationHandler handler = null; &Constructors &Methods }
|
類(lèi)模板定制了代理類(lèi)的代碼框架。其中帶“&”前綴的標簽位被用來(lái)引導相應的代碼替換。在此預留了包(&Package)、類(lèi)名(&ClassName)、類(lèi)繼承(&Extends)、接口實(shí)現(&Implements)、構造函數集(&Constructors)及方法集(&Methods)的標簽位。類(lèi)模板還同時(shí)聲明了一個(gè)私有型的調用處理器對象作為類(lèi)成員。
清單 2. 方法模板 &Modifiers &ReturnType &MethodName(&Parameters) &Throwables { java.lang.reflect.Method method = null; try { method = &Class.getMethod( \"& MethodName\", &ParameterTypes ); } catch(Exception e){ } Object r = null; try{ r = handler.invoke( this, method, &ParameterValues ); }&Exceptions &Return }
|
方法模板定制了代理類(lèi)方法集合中各個(gè)方法的代碼框架,同樣的帶“&”前綴的標簽位被用來(lái)引導相應的代碼替換。在此預留了修飾符(&Modifiers)、返回類(lèi)型(&ReturnType)、方法名(&MethodName)、參數列表(Parameters)、異常列表(&Throwables)、方法的聲明類(lèi)(&Class)、參數類(lèi)型列表(&ParameterTypes)、調用處理器的參數值列表(&ParameterValues),異常處理(&Exceptions)及返回值(&Return)的標簽位。
回頁(yè)首
代碼生成
有了類(lèi)模板和方法模板,代碼生成過(guò)程就變得有章可依?;具^(guò)程可分為三步:1)生成代理類(lèi)的方法集合;2)生成代理類(lèi)的構造函數;3)最后生成整個(gè)代理類(lèi)。
生成代理類(lèi)的方法集
第一步,通過(guò)反射獲得被代理類(lèi)的所有 public 或 protected 且非 static 的 Method 對象列表,這些方法將被涵蓋的原因是它們是可以被其他類(lèi)所訪(fǎng)問(wèn)的。
第二步,遍歷 Method 對象列表,對每個(gè) Method 對象,進(jìn)行相應的代碼生成工作。
清單 3. 對標簽位進(jìn)行代碼替換生成方法代碼 String declTemplate = "&Modifiers &ReturnType &MethodName(&Parameters) &Throwables"; String bodyTemplate = "&Declaration &Body"; // 方法聲明 String declare = declTemplate.replaceAll("&Modifiers", getMethodModifiers( method )) .replaceAll("&ReturnType", getMethodReturnType( method )) .replaceAll("&MethodName", method.getName()) .replaceAll("&Parameters", getMethodParameters( method )) .replaceAll("&Throwables", getMethodThrowables( method ));
// 方法聲明以及實(shí)現 String body = bodyTemplate.replaceAll("&Declaration", declare ) .replaceAll("&Body", getMethodEntity( method ));
|
這里涉及了一些 ProxyEx 類(lèi)的私有的輔助函數如 getMethodModifiers 和 getMethodReturnType 等等,它們都是通過(guò)反射獲取所需的信息,然后動(dòng)態(tài)地生成各部分代碼。函數 getMethodEntity 是比較重要的輔助函數,它又調用了其他的輔助函數來(lái)生成代碼并替換標簽位。
清單 4. ProxyEx 的靜態(tài)方法 getMethodEntity() private static String getMethodEntity( Method method ) { String template = "\n{" + "\n java.lang.reflect.Method method = null;" + "\n try{" + "\n method = &Class.getMethod( \"&MethodName\", &ParameterTypes );" + "\n }" + "\n catch(Exception e){" + "\n }" + "\n Object r = null;" + "\n try{" + "\n r = handler.invoke( this, method, &ParameterValues );" + "\n }&Exceptions" + "\n &Return" + "\n}"; String result = template.replaceAll("&MethodName", method.getName() ) .replaceAll("&Class", method.getDeclaringClass().getName() + ".class") .replaceAll("&ParameterTypes", getMethodParameterTypesHelper(method)) .replaceAll("&ParameterValues", getMethodParameterValuesHelper(method) ) .replaceAll("&Exceptions", getMethodParameterThrowablesHelper(method)) .replaceAll("&Return", getMethodReturnHelper( method ) ); return result; }
|
當為 Class 類(lèi)型對象生成該類(lèi)型對應的字符代碼時(shí),可能涉及數組類(lèi)型,反推過(guò)程會(huì )需要按遞歸方法生成代碼,這部分工作由 getTypeHelper 方法提供
清單 5. ProxyEx 的靜態(tài)方法 getTypeHelper() private static String getTypeHelper(Class type) { if( type.isArray() ) { Class c = type.getComponentType(); return getTypeHelper(c) + "[]"; } else { return type.getName(); } }
|
第三步,將所生成的方法保存進(jìn)一個(gè) map 表,該表記錄的是鍵值對(方法聲明,方法實(shí)現)。由于類(lèi)的多態(tài)性,父類(lèi)的方法可能被子類(lèi)所覆蓋,這時(shí)以上通過(guò)遍歷所得的方法列表中就會(huì )出現重復的方法對象,維護該表可以很自然地達到避免方法重復生成的目的,這就維護該表的原因所在。
生成代理類(lèi)的構造函數
相信讀者依然清晰記得代理類(lèi)是通過(guò)其構造函數反射生成的,而構造時(shí)傳入的唯一參數就是調用處理器對象。為了保持與原代理機制的一致性,新的代理類(lèi)的構造函數也同樣只有一個(gè)調用處理器對象作為參數。模板簡(jiǎn)單如下
清單 6. 構造函數模板 public &Constructor(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; }
|
需要特別提一下的是 super 方法的參數值列表 &Parameters 的生成,我們借鑒了 Mock 思想,側重于追求對象構造的成功,而并未過(guò)多地努力分析并尋求最準確最有意義的賦值。對此,相信讀者會(huì )多少產(chǎn)生一些疑慮,但稍后我們會(huì )提及改進(jìn)的方法,請先繼續閱讀。
生成整個(gè)代理類(lèi)
通過(guò)以上步驟,構造函數和所有需被代理的方法的代碼已經(jīng)生成,接下來(lái)就是生成整個(gè)代理類(lèi)的時(shí)候了。這個(gè)過(guò)程也很直觀(guān),通過(guò)獲取相關(guān)信息并對類(lèi)模板中各個(gè)標簽位進(jìn)行替換,便可以輕松的完成整個(gè)代理類(lèi)的代碼生成。
回頁(yè)首
被遺忘的角落:類(lèi)變量
等等,似乎遺忘了什么?從調用者的角度出發(fā),我們希望代理類(lèi)能夠作為被代理類(lèi)的如實(shí)代表呈現在用戶(hù)面前,包括其內部狀態(tài),而這些狀態(tài)通常是由類(lèi)變量所體現出來(lái)的,于是就涉及到類(lèi)變量的代理問(wèn)題。
要解決這個(gè)問(wèn)題,首先需要思考何時(shí)兩者的類(lèi)變量可能出現不一致?回答了這個(gè)問(wèn)題,也就找到了解決思路?;仡櫞眍?lèi)的構造函數,我們以粗糙的方式構造了代理類(lèi)實(shí)例。它們可能一開(kāi)始就已經(jīng)不一致了。還有每次方法調用也可能導致被兩者的類(lèi)變量的不一致。如何解決?直觀(guān)的想法是:1)構造時(shí)需設法進(jìn)行同步;2)方法調用之前和之后也需設法進(jìn)行同步。這樣,我們就能夠有效避免代理類(lèi)和被代理類(lèi)的類(lèi)變量不一致的問(wèn)題的出現了。
但是,如何獲得被代理類(lèi)的實(shí)例呢?從當前的的設計中已經(jīng)沒(méi)有辦法做到。既然如此,那就繼續我們的擴展之旅。只不過(guò)這次擴展的對象是調用處理器接口,我們將在擴展后的接口里加入獲取被代理類(lèi)對象的方法,且擴展調用處理器接口將以 static 和 public 的形式被定義在 ProxyEx 類(lèi)中。
清單 7. ProxyEx 類(lèi)內的靜態(tài)接口 InvocationHandlerEx public static interface InvocationHandlerEx extends InvocationHandler { // 返回指定 stubClass 參數所對應的被代理類(lèi)實(shí)體對象 Object getStub(Class stubClass); }
|
新的調用處理器接口具備了獲取被代理類(lèi)對象的能力,從而為實(shí)現類(lèi)變量的同步打開(kāi)了通道。接下來(lái)還需要的就是執行類(lèi)變量同步的 sync 方法,每個(gè)動(dòng)態(tài)生成的代理類(lèi)中都會(huì )被悄悄地加入這個(gè)私有方法以供調用。每次方法被分派轉發(fā)到調用處理器執行之前和之后,sync 方法都會(huì )被調用,從而保證類(lèi)變量的雙向實(shí)時(shí)更新。相應的,方法模板也需要更新以支持該新特性。
清單 8. 更新后的方法模板(部分) Object r = null; try{ // 代理類(lèi)到被代理類(lèi)方向的變量同步 sync(&Class, true); r = handler.invoke( this, method, &ParameterValues ); // 被代理類(lèi)到代理類(lèi)方向的變量同步 sync(&Class, false); }&Exceptions
&Return
|
sync 方法還會(huì )在構造函數尾部被調用,從而將被代理類(lèi)對象的變量信息同步到代理類(lèi)對象,實(shí)現類(lèi)似于拷貝構造的等價(jià)效果。相應的,構造函數模板也需要更新以支持該新特性。
清單 9. 更新后的構造函數模板 public &Name(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; // 被代理類(lèi)到代理類(lèi)方向的變量同步 sync(null, false); }
|
接下來(lái)介紹 sync 方法的實(shí)現,其思想就是首先獲取被代理類(lèi)的所有 Field 對象的列表,并通過(guò)擴展的調用處理器獲得方法的聲明類(lèi)說(shuō)對應的 stub 對象,然后遍歷 Field 對象列表并對各個(gè)變量進(jìn)行拷貝同步。
清單 10. 聲明在動(dòng)態(tài)生成的代理類(lèi)內部的 snyc 函數 private synchronized void sync(java.lang.Class clazz, boolean toStub) { // 判斷是否為擴展調用處理器 if( handler instanceof InvocationHandlerEx ) { java.lang.Class superClass = this.getClass().getSuperclass(); java.lang.Class stubClass = ( clazz != null ? clazz : superClass ); // 通過(guò)擴展調用處理器獲得stub對象 Object stub = ((InvocationHandlerEx)handler).getStub(stubClass); if( stub != null ) { // 獲得所有需同步的類(lèi)成員列表,遍歷并同步 java.lang.reflect.Field[] fields = getFields(superClass); for(int i=0; fields!=null&&i<fields.length; i++) { try { fields[i].setAccessible(true); // 執行代理類(lèi)和被代理類(lèi)的變量同步 if(toStub) { fields[i].set(stub, fields[i].get(this)); } else { fields[i].set(this, fields[i].get(stub)); } } catch(Throwable e) { } } } } }
|
這里涉及到一個(gè)用于獲取類(lèi)的所有 Field 對象列表的靜態(tài)輔助方法 getFields。為了提高頻繁查詢(xún)時(shí)的性能,配合該靜態(tài)方法的是一個(gè)靜態(tài)的 fieldsMap 對象,用于記錄已查詢(xún)過(guò)的類(lèi)其所包含的 Field 對象列表,使得再次查詢(xún)時(shí)能迅速返回其對應列表。相應的,類(lèi)模板也需進(jìn)行更新。
清單 11. 增加了靜態(tài) fieldsMap 變量后的類(lèi)模板 package &Package; final public class &Name &Extends &Implements { private static java.util.HashMap fieldsMap = new java.util.HashMap(); private java.lang.reflect.InvocationHandler handler = null; &Constructors &Methods }
|
清單 12. 聲明在動(dòng)態(tài)生成的代理類(lèi)內部的靜態(tài)方法 getFields private static java.lang.reflect.Field[] getFields(java.lang.Class c) { if( fieldsMap.containsKey(c) ) { return (java.lang.reflect.Field[])fieldsMap.get(c); } java.lang.reflect.Field[] fields = null; if( c == java.lang.Object.class ) { fields = c.getDeclaredFields(); } else { java.lang.reflect.Field[] fields0 = getFields(c.getSuperclass()); java.lang.reflect.Field[] fields1 = c.getDeclaredFields(); fields = new java.lang.reflect.Field[fields0.length + fields1.length]; System.arraycopy(fields0, 0, fields, 0, fields0.length); System.arraycopy(fields1, 0, fields, fields0.length, fields1.length); } fieldsMap.put(c, fields); return fields; }
|
回頁(yè)首
動(dòng)態(tài)編譯及裝載
代碼生成以后,需要經(jīng)過(guò)編譯生成 JVM 所能識別的字節碼,而字節碼還需要通過(guò)類(lèi)裝載器載入 JVM 才能最終被真正使用,接下來(lái)我們將闡述如何動(dòng)態(tài)編譯及裝載。
首先是動(dòng)態(tài)編譯。這部分由 ProxyEx 類(lèi)的 getProxyClassCodeSource 函數完成。該函數分三步進(jìn)行:第一步保存源代碼到 .java 文件;第二步編譯該 .java 文件;第三步從輸出的 .class 文件讀取字節碼。
清單 13. ProxyEx 的靜態(tài)方法 getProxyClassCodeSource private static byte[] getProxyClassCodeSource( String pkg, String className, String declare ) throws Exception { // 將類(lèi)的源代碼保存進(jìn)一個(gè)名為類(lèi)名加“.java”的本地文件 File source = new File(className + ".java"); FileOutputStream fos = new FileOutputStream( source ); fos.write( declare.getBytes() ); fos.close(); // 調用com.sun.tools.javac.Main類(lèi)的靜態(tài)方法compile進(jìn)行動(dòng)態(tài)編譯 int status = com.sun.tools.javac.Main.compile( new String[] { "-d", ".", source.getName() } );
if( status != 0 ) { source.delete(); throw new Exception("Compiler exit on " + status); } // 編譯得到的字節碼將被輸出到與包結構相同的一個(gè)本地目錄,文件名為類(lèi)名加”.class” String output = "."; int curIndex = -1; int lastIndex = 0; while( (curIndex=pkg.indexOf('.', lastIndex)) != -1 ) { output = output + File.separator + pkg.substring( lastIndex, curIndex ); lastIndex = curIndex + 1; } output = output + File.separator + pkg.substring( lastIndex ); output = output + File.separator + className + ".class"; // 從輸出文件中讀取字節碼,并存入字節數組 File target = new File(output); FileInputStream f = new FileInputStream( target ); byte[] codeSource = new byte[(int)target.length()]; f.read( codeSource ); f.close(); // 刪除臨時(shí)文件 source.delete(); target.delete(); return codeSource; }
|
得到代理類(lèi)的字節碼,接下來(lái)就可以動(dòng)態(tài)裝載該類(lèi)了。這部分由 ProxyEx 類(lèi)的 defineClassHelper 函數完成。該函數分兩步進(jìn)行:第一步通過(guò)反射獲取父類(lèi) Proxy 的靜態(tài)私有方法 defineClass0;第二步傳入字節碼數組及其他相關(guān)信息并反射調用該方法以完成類(lèi)的動(dòng)態(tài)裝載。
清單 14. ProxyEx 的靜態(tài)方法 defineClassHelper private static Class defineClassHelper( String pkg, String cName, byte[] codeSource ) throws Exception { Method defineClass = Proxy.class.getDeclaredMethod( "defineClass0", new Class[] { ClassLoader.class, String.class, byte[].class, int.class, int.class } );
defineClass.setAccessible(true); return (Class)defineClass.invoke( Proxy.class, new Object[] { ProxyEx.class.getClassLoader(), pkg.length()==0 ? cName : pkg+"."+cName, codeSource, new Integer(0), new Integer(codeSource.length) } ); }
|
回頁(yè)首
性能改進(jìn)
原動(dòng)態(tài)代理機制中對接口數組有一些有趣的特點(diǎn),其中之一就是接口的順序差異會(huì )在一定程度上導致生成新的代理類(lèi),即使其實(shí)并無(wú)必要。其中的原因就是因為緩存表是以接口名稱(chēng)列表作為關(guān)鍵字,所以不同的順序就意味著(zhù)不同的關(guān)鍵字,如果對應的關(guān)鍵字不存在,就會(huì )生成新但是作用重復的代理類(lèi)。在 ProxyEx 類(lèi)中,我們通過(guò)主動(dòng)排序避免了類(lèi)似的問(wèn)題,提高動(dòng)態(tài)生成代理類(lèi)的效率。而且,如果發(fā)現數組中都是接口類(lèi)型,則直接調用父類(lèi) Proxy 的靜態(tài)方法 getProxyClass 生成代理類(lèi),否則才通過(guò)擴展動(dòng)態(tài)代理機制生成代理類(lèi),這樣也一定程度上改進(jìn)了性能。
回頁(yè)首
兼容性問(wèn)題
接下來(lái)需要考慮的是與原代理機制的兼容性問(wèn)題。曾記否,Proxy 中還有兩個(gè)靜態(tài)方法:isProxyClass 和 getInvocationHandler,分別被用于判斷 Class 對象是否是動(dòng)態(tài)代理類(lèi)和從 Object 對象獲取對應的調用處理器(如果可能的話(huà))。
清單 15. Proxy 的靜態(tài)方法 isProxyClass 和 getInvocationHandler static boolean isProxyClass(Class cl) static InvocationHandler getInvocationHandler(Object proxy)
|
現在的兼容性問(wèn)題,主要涉及到 ProxyEx 類(lèi)與父類(lèi) Proxy 在關(guān)于動(dòng)態(tài)生成的代理類(lèi)的信息方面所面臨的如何保持同步的問(wèn)題。曾介紹過(guò),在 Proxy 類(lèi)中有個(gè)私有的 Map 對象 proxyClasses 專(zhuān)門(mén)負責保存所有動(dòng)態(tài)生成的代理類(lèi)類(lèi)型。Proxy 類(lèi)的靜態(tài)函數 isProxyClass 就是通過(guò)查詢(xún)該表以確定某 Class 對象是否為動(dòng)態(tài)代理類(lèi),我們需要做的就是把由 ProxyEx 生成的代理類(lèi)類(lèi)型也保存入該表。這部分工作由 ProxyEx 類(lèi)的靜態(tài)方法 addProxyClass 輔助完成。
清單 16. ProxyEx 的靜態(tài)方法 addProxyClass private static void addProxyClass( Class proxy ) throws IllegalArgumentException { try { // 通過(guò)反射獲取父類(lèi)的私有 proxyClasses 變量并更新 Field proxyClasses = Proxy.class.getDeclaredField("proxyClasses"); proxyClasses.setAccessible(true); ((Map)proxyClasses.get(Proxy.class)).put( proxy, null ); } catch(Exception e) { throw new IllegalArgumentException(e.toString()); } }
|
相對而言,原來(lái) Proxy 類(lèi)的靜態(tài)方法 getInvocationHandler 實(shí)現相當簡(jiǎn)單,先判斷是否為代理類(lèi),若是則直接類(lèi)型轉換到 Proxy 并返回其調用處理器成員,而擴展后的代理類(lèi)并不非從 Proxy 類(lèi)繼承,所以在獲取調用處理器對象的方法上需要一些調整。這部分由 ProxyEx 類(lèi)的同名靜態(tài)方法 getInvocationHandler 完成。
清單 17. ProxyEx 的靜態(tài)方法 getInvocationHandler public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException { // 如果Proxy實(shí)例,直接調父類(lèi)的方法 if( proxy instanceof Proxy ) { return Proxy.getInvocationHandler( proxy ); } // 如果不是代理類(lèi),拋異常 if( !Proxy.isProxyClass( proxy.getClass() )) { throw new IllegalArgumentException("Not a proxy instance"); } try { // 通過(guò)反射獲取擴展代理類(lèi)的調用處理器對象 Field invoker = proxy.getClass().getDeclaredField("handler"); invoker.setAccessible(true); return (InvocationHandler)invoker.get(proxy); } catch(Exception e) { throw new IllegalArgumentException("Suspect not a proxy instance"); } }
|
回頁(yè)首
坦言:也有局限
受限于 Java 的類(lèi)繼承機制,擴展的動(dòng)態(tài)代理機制也有其局限,它不能支持:
- 聲明為 final 的類(lèi);
- 聲明為 final 的函數;
- 構造函數均為 private 類(lèi)型的類(lèi);
回頁(yè)首
實(shí)例演示
闡述了這么多,相信讀者一定很想看一下擴展動(dòng)態(tài)代理機制是如何工作的。本文最后將以 2010 世博門(mén)票售票代理為模型進(jìn)行演示。
首先,我們定義了一個(gè)售票員抽象類(lèi) TicketSeller。
清單 18. TicketSeller public abstract class TicketSeller { protected String theme; protected TicketSeller(String theme) { this.theme = theme; } public String getTicketTheme() { return this.theme; } public void setTicketTheme(String theme) { this.theme = theme; } public abstract int getTicketPrice(); public abstract int buy(int ticketNumber, int money) throws Exception; }
|
其次,我們會(huì )實(shí)現一個(gè) 2010 世博門(mén)票售票代理類(lèi) Expo2010TicketSeller。
清單 19. Expo2010TicketSeller public class Expo2010TicketSeller extends TicketSeller { protected int price; protected int numTicketForSale; public Expo2010TicketSeller() { super("World Expo 2010"); this.price = 180; this.numTicketForSale = 200; } public int getTicketPrice() { return price; } public int buy(int ticketNumber, int money) throws Exception { if( ticketNumber > numTicketForSale ) { throw new Exception("There is no enough ticket available for sale, only " + numTicketForSale + " ticket(s) left"); } int charge = money - ticketNumber * price; if( charge < 0 ) { throw new Exception("Money is not enough. Still needs " + (-charge) + " RMB."); } numTicketForSale -= ticketNumber; return charge; } }
|
接著(zhù),我們將通過(guò)購票者類(lèi) TicketBuyer 來(lái)模擬購票以演示擴展動(dòng)態(tài)代理機制。
清單 20. TicketBuyer public class TicketBuyer { public static void main(String[] args) { // 創(chuàng )建真正的TickerSeller對象,作為stub實(shí)體 final TicketSeller stub = new Expo2010TicketSeller();
// 創(chuàng )建擴展調用處理器對象 InvocationHandler handler = new InvocationHandlerEx() { public Object getStub(Class stubClass) { // 僅對可接受的Class類(lèi)型返回stub實(shí)體 if( stubClass.isAssignableFrom(stub.getClass()) ) { return stub; } return null; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o; try { System.out.println(" >>> Enter method: " + method.getName() ); o = method.invoke(stub, args); } catch(InvocationTargetException e) { throw e.getCause(); } finally { System.out.println(" <<< Exit method: " + method.getName() ); } return o; } }; // 通過(guò)ProxyEx構造動(dòng)態(tài)代理 TicketSeller seller = (TicketSeller)ProxyEx.newProxyInstance( TicketBuyer.class.getClassLoader(), new Class[] {TicketSeller.class}, handler); // 顯示代理類(lèi)的類(lèi)型 System.out.println("Ticket Seller Class: " + seller.getClass() + "\n"); // 直接訪(fǎng)問(wèn)theme變量,驗證代理類(lèi)變量在對象構造時(shí)同步的有效性 System.out.println("Ticket Theme: " + seller.theme + "\n"); // 函數訪(fǎng)問(wèn)price信息 System.out.println("Query Ticket Price..."); System.out.println("Ticket Price: " + seller.getTicketPrice() + " RMB\n"); // 模擬票務(wù)交易 buyTicket(seller, 1, 200); buyTicket(seller, 1, 160); buyTicket(seller, 250, 30000); // 直接更新theme變量 System.out.println("Updating Ticket Theme...\n"); seller.theme = "World Expo 2010 in Shanghai"; // 函數訪(fǎng)問(wèn)theme信息,驗證擴展動(dòng)態(tài)代理機制對變量同步的有效性 System.out.println("Query Updated Ticket Theme..."); System.out.println("Updated Ticket Theme: " + seller.getTicketTheme() + "\n"); } // 購票函數 protected static void buyTicket(TicketSeller seller, int ticketNumber, int money) { try { System.out.println("Transaction: Order " + ticketNumber + " ticket(s) with " + money + " RMB"); int charge = seller.buy(ticketNumber, money); System.out.println("Transaction: Succeed - Charge is " + charge + " RMB\n"); } catch (Exception e) { System.out.println("Transaction: Fail - " + e.getMessage() + "\n"); } } }
|
最后,見(jiàn)演示程序的執行結果。
清單 21. 執行輸出 Ticket Seller Class: class com.demo.proxy.test.TicketSellerProxy0
Ticket Theme: World Expo 2010
Query Ticket Price... >>> Enter method: getTicketPrice <<< Exit method: getTicketPrice Ticket Price: 180 RMB
Transaction: Order 1 ticket(s) with 200 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Succeed - Charge is 20 RMB
Transaction: Order 1 ticket(s) with 160 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Fail - Money is not enough. Still needs 20 RMB.
Transaction: Order 250 ticket(s) with 30000 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Fail - There is no enough ticket available for sale, only 199 ticket(s) left
Updating Ticket Theme...
Query Updated Ticket Theme... >>> Enter method: getTicketTheme <<< Exit method: getTicketTheme Updated Ticket Theme: World Expo 2010 in Shanghai
|
參考資料
作者簡(jiǎn)介
王忠平,軟件工程師,目前在 IBM 上海中國系統技術(shù)實(shí)驗室任職。
何平,軟件工程師,目前在 IBM 上海中國系統技術(shù)實(shí)驗室任職。
(###)