一文讀懂Java動(dòng)態(tài)代理

引言
最早的代理模式,我們大致可以聯(lián)想到三國時(shí)期,孟德君挾天子以令諸侯是代理模式,是權(quán)利代理;現(xiàn)今生活中類似房產(chǎn)中介、票務(wù)中介是代理模式,是業(yè)務(wù)代理;還有翻墻瀏覽網(wǎng)頁是代理模式,是VPN代理;回到我們編程世界里呢,以前你用的遠(yuǎn)程方法調(diào)用(RMI)是代理、企業(yè)JavaBeans(EJB)是代理,現(xiàn)在流行的眾多RPC框架(如dubbo)也是代理,包括我們的Java動(dòng)態(tài)代理,他們都是對象代理。
Java動(dòng)態(tài)代理
Java 動(dòng)態(tài)代理機(jī)制的出現(xiàn),使得 Java 開發(fā)人員不用手工編寫代理類,只要簡單地指定一組接口及委托類對象,便能動(dòng)態(tài)地獲得代理類。代理類會(huì)負(fù)責(zé)將所有的方法調(diào)用分派到委托對象上反射執(zhí)行,在分派執(zhí)行的過程中,開發(fā)人員還可以按需調(diào)整委托類對象及其功能,這是一套非常靈活有彈性的代理框架。
代理是一種常見的設(shè)計(jì)模式,其目的就是為 ”調(diào)用方“ 提供一個(gè)代理類以控制對 ”被調(diào)用方“ 的訪問。代理類負(fù)責(zé)為委托類預(yù)處理消息,過濾消息并轉(zhuǎn)發(fā)消息,以及進(jìn)行消息被委托類執(zhí)行后的后續(xù)處理。

可以發(fā)現(xiàn),代理類與委托類,實(shí)現(xiàn)了同一個(gè)接口,所以對于客戶端請求來說沒有絲毫的區(qū)別,這也是Java面向接口編程的特點(diǎn)。代理模式使用代理對象(代理類)完成用戶請求,有效的屏蔽了用戶對真實(shí)對象(委托類)的訪問,也可以很好地隱藏和保護(hù)委托類對象。同時(shí)也為添加不同控制策略爭取了空間,從而在設(shè)計(jì)上獲得了更大的靈活性。Java 動(dòng)態(tài)代理機(jī)制以巧妙的方式近乎完美地實(shí)踐了代理模式的設(shè)計(jì)理念。
正如現(xiàn)實(shí)世界的代理人被授權(quán)執(zhí)行當(dāng)事人的一些事宜,無需當(dāng)事人出面,從第三方的角度看,似乎當(dāng)事人并不存在,因?yàn)樗缓痛砣送ㄐ拧6聦?shí)上代理人是要有當(dāng)事人的授權(quán),并且在核心問題上還需要請示當(dāng)事人,同時(shí)代理人除了轉(zhuǎn)發(fā)完成當(dāng)事人的授權(quán)事宜之外,還可以在執(zhí)行授權(quán)事宜前后增加額外的一些事務(wù)。
代理對象 = 目標(biāo)對象 + 增強(qiáng)事務(wù)
相關(guān)類和接口

要了解 Java 動(dòng)態(tài)代理的機(jī)制,首先需要了解兩個(gè)相關(guān)的類或接口:
?java.lang.reflect.Proxy:這是 Java 動(dòng)態(tài)代理機(jī)制的主類,它提供了一組靜態(tài)方法來為一組接口動(dòng)態(tài)地生成代理類及其對象。
清單 1. Proxy 的靜態(tài)方法
// 方法 1: 該方法用于獲取指定代理對象所關(guān)聯(lián)的調(diào)用處理器static InvocationHandlergetInvocationHandler(Object proxy)// 方法 2:該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動(dòng)態(tài)代理類的類對象static Class getProxyClass(ClassLoader loader,Class[] interfaces)// 方法 3:該方法用于判斷指定類對象是否是一個(gè)動(dòng)態(tài)代理類static boolean isProxyClass(Class cl)// 方法 4:該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動(dòng)態(tài)代理類實(shí)例static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
?java.lang.reflect.InvocationHandler:這是調(diào)用處理器接口,它自定義了一個(gè) invoke 方法,用于集中處理在動(dòng)態(tài)代理類對象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對委托類的代理訪問。
清單 2. InvocationHandler 的核心方法
// 該方法負(fù)責(zé)集中處理動(dòng)態(tài)代理類上的所有方法調(diào)用。// 第一個(gè)參數(shù)既是代理類實(shí)例,// 第二個(gè)參數(shù)是被調(diào)用的方法對象// 第三個(gè)方法是調(diào)用參數(shù)。// 調(diào)用處理器根據(jù)這三個(gè)參數(shù)// 進(jìn)行預(yù)處理或分派到委托類實(shí)例上反射執(zhí)行Object invoke(Object proxy, Method method, Object[] args)
每次生成動(dòng)態(tài)代理類對象時(shí)都需要指定一個(gè)實(shí)現(xiàn)了該接口的調(diào)用處理器對象(參見 Proxy 靜態(tài)方法 4 的第三個(gè)參數(shù))。
?java.lang.ClassLoader:這是類裝載器類,負(fù)責(zé)將類的字節(jié)碼裝載到 Java 虛擬機(jī)(JVM)中并為其定義類對象,然后該類才能被使用。Proxy 靜態(tài)方法生成動(dòng)態(tài)代理類同樣需要通過類裝載器來進(jìn)行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由 JVM 在?運(yùn)行時(shí)動(dòng)態(tài)生成?的而非預(yù)存在于任何一個(gè) .class 文件中。
每次生成動(dòng)態(tài)代理類對象時(shí)都需要指定一個(gè)類裝載器對象(參見 Proxy 靜態(tài)方法 4 的第一個(gè)參數(shù))
代理機(jī)制與特點(diǎn)
首先讓我們來了解一下如何使用 Java 動(dòng)態(tài)代理。具體有如下四步驟:

1.通過實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器;2.通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創(chuàng)建動(dòng)態(tài)代理類;3.通過反射機(jī)制獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù),其唯一參數(shù)類型是調(diào)用處理器接口類型;4.通過構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類實(shí)例,構(gòu)造時(shí)調(diào)用處理器對象作為參數(shù)被傳入。
清單 3. InvocationHandler 的核心方法
// InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,// 并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā)// 其內(nèi)部通常包含指向委托類實(shí)例的引用,// 用于真正執(zhí)行分派轉(zhuǎn)發(fā)過來的方法調(diào)用InvocationHandler handler = new InvocationHandlerImpl(..);// 通過 Proxy 為包括 Interface 接口在內(nèi)的一組接口// 動(dòng)態(tài)創(chuàng)建代理類的類對象Class clazz = Proxy.getProxyClass(classLoader,new Class[] { Interface.class, ... });// 通過反射從生成的類對象獲得構(gòu)造函數(shù)對象Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });// 通過構(gòu)造函數(shù)對象創(chuàng)建動(dòng)態(tài)代理類實(shí)例Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
實(shí)際使用過程更加簡單,因?yàn)?Proxy 的靜態(tài)方法 newProxyInstance 已經(jīng)為我們封裝了步驟 2 到步驟 4 的過程,所以簡化后的過程如下
清單 4. 簡化的動(dòng)態(tài)代理對象創(chuàng)建過程(三合一)
// InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,// 并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā)InvocationHandler handler = new InvocationHandlerImpl(..);// 通過 Proxy 直接創(chuàng)建動(dòng)態(tài)代理類實(shí)例Interface proxy = (Interface)Proxy.newProxyInstance(classLoader,new Class[] { Interface.class },handler );
接下來讓我們來了解一下 Java 動(dòng)態(tài)代理機(jī)制的一些特點(diǎn)。
首先是動(dòng)態(tài)生成的代理類本身的一些特點(diǎn)。
?包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因?yàn)榻涌诓荒鼙欢x為 protect 或 private,所以除 public 之外就是默認(rèn)的 package 訪問級別),那么它將被定義在該接口所在包(假設(shè)代理了 com.panshenlian.proxy 包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.panshenlian.proxy ),這樣設(shè)計(jì)的目的是為了最大程度的保證動(dòng)態(tài)代理類不會(huì)因?yàn)榘芾淼膯栴}而無法被成功定義并訪問;?類修飾符:該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;?類名:格式是”$ProxyN”,其中 N 是一個(gè)逐一遞增的阿拉伯?dāng)?shù)字,代表 Proxy 類第 N 次生成的動(dòng)態(tài)代理類,值得注意的一點(diǎn)是,并不是每次調(diào)用 Proxy 的靜態(tài)方法創(chuàng)建動(dòng)態(tài)代理類都會(huì)使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復(fù)創(chuàng)建動(dòng)態(tài)代理類,它會(huì)很聰明地返回先前已經(jīng)創(chuàng)建好的代理類的類對象,而不會(huì)再嘗試去創(chuàng)建一個(gè)全新的代理類,這樣可以節(jié)省不必要的代碼重復(fù)生成,提高了代理類的創(chuàng)建效率。?類繼承關(guān)系:該類的繼承關(guān)系如圖:

由圖可見,Proxy 類是它的父類,這個(gè)規(guī)則適用于所有由 Proxy 創(chuàng)建的動(dòng)態(tài)代理類。而且該類還實(shí)現(xiàn)了其所代理的一組接口,這就是為什么它能夠被安全地類型轉(zhuǎn)換到其所代理的某接口的根本原因。
接下來讓我們了解一下代理類實(shí)例的一些特點(diǎn)。每個(gè)實(shí)例都會(huì)關(guān)聯(lián)一個(gè)調(diào)用處理器對象,可以通過 Proxy 提供的靜態(tài)方法 getInvocationHandler 去獲得代理類實(shí)例的調(diào)用處理器對象。在代理類實(shí)例上調(diào)用其代理的接口中所聲明的方法時(shí),這些方法最終都會(huì)由調(diào)用處理器的 invoke 方法執(zhí)行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個(gè)方法也同樣會(huì)被分派到調(diào)用處理器的 invoke 方法執(zhí)行,它們是 hashCode,equals 和 toString,可能的原因有:
?一是因?yàn)檫@些方法為 public 且非 final 類型,能夠被代理類覆蓋;?二是因?yàn)檫@些方法往往呈現(xiàn)出一個(gè)類的某種特征屬性,具有一定的區(qū)分度,所以為了保證代理類與委托類對外的一致性,這三個(gè)方法也應(yīng)該被分派到委托類執(zhí)行。當(dāng)代理的一組接口有重復(fù)聲明的方法且該方法被調(diào)用時(shí),代理類總是從排在最前面的接口中獲取方法對象并分派給調(diào)用處理器,而無論代理類實(shí)例是否正在以該接口(或繼承于該接口的某子接口)的形式被外部引用,因?yàn)樵诖眍悆?nèi)部無法區(qū)分其當(dāng)前的被引用類型。
接著來了解一下被代理的一組接口有哪些特點(diǎn)。
首先,要注意不能有重復(fù)的接口,以避免動(dòng)態(tài)代理類代碼生成時(shí)的編譯錯(cuò)誤。其次,這些接口對于類裝載器必須可見,否則類裝載器將無法鏈接它們,將會(huì)導(dǎo)致類定義失敗。再次,需被代理的所有非 public 的接口必須在同一個(gè)包中,否則代理類生成也會(huì)失敗。最后,接口的數(shù)目不能超過 65535,這是 JVM 設(shè)定的限制。
/*** Proxy.java* Generate a proxy class. Must call the checkProxyAccess method* to perform permission checks before calling this.*/private static Class> getProxyClass0(ClassLoader loader,Class>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactoryreturn proxyClassCache.get(loader, interfaces);}
最后再來了解一下異常處理方面的特點(diǎn)。從調(diào)用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因?yàn)樗械漠惓6祭^承于 Throwable 接口,但事實(shí)是否如此呢?答案是否定的,原因是我們必須遵守一個(gè)繼承原則:即子類覆蓋父類或?qū)崿F(xiàn)父接口的方法時(shí),拋出的異常必須在原方法支持的異常列表之內(nèi)。
所以雖然調(diào)用處理器理論上講能夠,但實(shí)際上往往受限制,除非父接口中的方法支持拋 Throwable 異常。那么如果在 invoke 方法中的確產(chǎn)生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動(dòng)態(tài)代理類已經(jīng)為我們設(shè)計(jì)好了解決方法:它將會(huì)拋出 UndeclaredThrowableException 異常。這個(gè)異常是一個(gè) RuntimeException 類型,所以不會(huì)引起編譯錯(cuò)誤。通過該異常的 getCause 方法,還可以獲得原來那個(gè)不受支持的異常對象,以便于錯(cuò)誤診斷。
代碼演示
機(jī)制和特點(diǎn)都介紹過了,接下來讓我們通過源代碼來了解一下 Proxy 到底是如何實(shí)現(xiàn)的。
首先記住 Proxy 的幾個(gè)重要的靜態(tài)變量:
清單 5. Proxy 的重要靜態(tài)變量
// 映射表:用于維護(hù)類裝載器對象到其對應(yīng)的代理類緩存private static Map loaderToCache = new WeakHashMap();// 標(biāo)記:用于標(biāo)記一個(gè)動(dòng)態(tài)代理類正在被創(chuàng)建中private static Object pendingGenerationMarker = new Object();// 同步表:記錄已經(jīng)被創(chuàng)建的動(dòng)態(tài)代理類類型,主要被方法 isProxyClass 進(jìn)行相關(guān)的判斷private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());// 關(guān)聯(lián)的調(diào)用處理器引用protected InvocationHandler h;
然后,來看一下 Proxy 的構(gòu)造方法:
清單 6. Proxy 構(gòu)造方法
// 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),// 所以 private 類型意味著禁止任何調(diào)用private Proxy() {}// 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),// 所以 protected 意味著只有子類可以調(diào)用protected Proxy(InvocationHandler h) {this.h = h;}
接著,可以快速瀏覽一下 newProxyInstance 方法,因?yàn)槠湎喈?dāng)簡單:
清單 7. Proxy 靜態(tài)方法 newProxyInstance
/*** 將方法調(diào)用分派到指定調(diào)用處理器* 并返回指定接口的代理類實(shí)例*/public static Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)throws IllegalArgumentException {// 檢查 h 不為空,否則拋異常if (h == null) {throw new NullPointerException();}// 獲得與制定類裝載器和一組接口相關(guān)的代理類類型對象Class cl = getProxyClass(loader, interfaces);// 通過反射獲取構(gòu)造函數(shù)對象并生成代理類實(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());}}
由此可見,動(dòng)態(tài)代理真正的關(guān)鍵是在 getProxyClass 方法,該方法負(fù)責(zé)為一組接口動(dòng)態(tài)地生成代理類類型對象。在該方法內(nèi)部,您將能看到 Proxy 內(nèi)的各路英雄(靜態(tài)變量)悉數(shù)登場。有點(diǎn)迫不及待了么?那就讓我們一起走進(jìn) Proxy 最最神秘的殿堂去欣賞一番吧。該方法總共可以分為四個(gè)步驟:
第 1 步,對這組接口進(jìn)行一定程度的安全檢查,包括檢查接口類對象是否對類裝載器可見并且與類裝載器所能識別的接口類對象是完全相同的,還會(huì)檢查確保是 interface 類型而不是 class 類型。這個(gè)步驟通過一個(gè)循環(huán)來完成,檢查通過后將會(huì)得到一個(gè)包含所有接口名稱的字符串?dāng)?shù)組,記為?String[] interfaceNames?。總體上這部分實(shí)現(xiàn)比較直觀,所以略去大部分代碼,僅保留如何判斷某類或接口是否對特定類裝載器可見的相關(guān)代碼。
清單 8. 通過 Class.forName 方法判接口的可見性
try {// 指定接口名字、類裝載器對象,// 同時(shí)制定 initializeBoolean// 為 false 表示無須初始化類//// 如果方法返回正常這表示可見,// 否則會(huì)拋出 ClassNotFoundException// 異常表示不可見interfaceClass =Class.forName(interfaceName, false, loader);} catch (ClassNotFoundException e) {}
第 2 步,從 loaderToCache 映射表中獲取以類裝載器對象為關(guān)鍵字所對應(yīng)的緩存表,如果不存在就創(chuàng)建一個(gè)新的緩存表并更新到 loaderToCache。緩存表是一個(gè) HashMap 實(shí)例,正常情況下它將存放鍵值對(接口名字列表,動(dòng)態(tài)生成的代理類的類對象引用)。當(dāng)代理類正在被創(chuàng)建時(shí)它會(huì)臨時(shí)保存(接口名字列表,pendingGenerationMarker)。標(biāo)記 pendingGenerationMarke 的作用是通知后續(xù)的同類請求(接口數(shù)組相同且組內(nèi)接口排列順序也相同)代理類正在被創(chuàng)建,請保持等待直至創(chuàng)建完成。
清單 9. 緩存表的使用
do {// 以接口名字列表作為關(guān)鍵字獲得對應(yīng) 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) {// 代理類正在被創(chuàng)建,保持等待try {cache.wait();} catch (InterruptedException e) {}// 等待被喚醒后,// 再次循環(huán)檢查,// 以確保創(chuàng)建完成,// 否則重新等待continue;} else {// 標(biāo)記代理類正在被創(chuàng)建cache.put(key, pendingGenerationMarker);// break 跳出循環(huán)已進(jìn)入創(chuàng)建過程break;} while (true);
第 3 步,動(dòng)態(tài)創(chuàng)建代理類的類對象。首先是確定代理類所在的包,其原則如前所述,如果都為 public 接口,則包名為空字符串表示頂層包;如果所有非 public 接口都在同一個(gè)包,則包名與這些接口的包名相同;如果有多個(gè)非 public 接口且不同包,則拋異常終止代理類的生成。確定了包后,就開始生成代理類的類名,同樣如前所述按格式”?$ProxyN?”生成。類名也確定了,接下來就是見證奇跡的發(fā)生 —— 動(dòng)態(tài)生成代理類:
清單 10. 動(dòng)態(tài)生成代理類
// 動(dòng)態(tài)地生成代理類的字節(jié)碼數(shù)組byte[] proxyClassFile =ProxyGenerator.generateProxyClass(proxyName,interfaces);try {// 動(dòng)態(tài)地定義新生成的代理類proxyClass =defineClass0(loader,proxyName,proxyClassFile, 0,proxyClassFile.length);} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}// 把生成的代理類的類對象// 記錄進(jìn) proxyClasses 表proxyClasses.put(proxyClass, null);
由此可見,所有的代碼生成的工作都由神秘的?ProxyGenerator?所完成了,當(dāng)你嘗試去探索這個(gè)類時(shí),你所能獲得的信息僅僅是它位于并未公開的 sun.misc 包,有若干常量、變量和方法以完成這個(gè)神奇的代碼生成的過程,但是 sun 并沒有提供源代碼以供研讀。至于動(dòng)態(tài)類的定義,則由 Proxy 的 native 靜態(tài)方法 defineClass0 執(zhí)行。
第 4 步,代碼生成過程進(jìn)入結(jié)尾部分,根據(jù)結(jié)果更新緩存表,如果成功則將代理類的類對象引用更新進(jìn)緩存表,否則清除緩存表中對應(yīng)關(guān)鍵值,最后喚醒所有可能的正在等待的線程。
走完了以上四個(gè)步驟后,至此,所有的代理類生成細(xì)節(jié)都已介紹完畢,剩下的靜態(tài)方法如 getInvocationHandler 和 isProxyClass 就顯得如此的直觀,只需通過查詢相關(guān)變量就可以完成,所以對其的代碼分析就省略了。
代理類實(shí)現(xiàn)推演
分析了 Proxy 類的源代碼,相信在讀者的腦海中會(huì)對 Java 動(dòng)態(tài)代理機(jī)制形成一個(gè)更加清晰的理解,但是,當(dāng)探索之旅在 sun.misc.ProxyGenerator 類處嘎然而止,所有的神秘都匯聚于此時(shí),相信不少讀者也會(huì)對這個(gè) ProxyGenerator 類產(chǎn)生有類似的疑惑:它到底做了什么呢?它是如何生成動(dòng)態(tài)代理類的代碼的呢?誠然,這里也無法給出確切的答案。還是讓我們帶著這些疑惑,一起開始探索之旅吧。

事物往往不像其看起來的復(fù)雜,需要的是我們能夠化繁為簡,這樣也許就能有更多撥云見日的機(jī)會(huì)。拋開所有想象中的未知而復(fù)雜的神秘因素,如果讓我們用最簡單的方法去實(shí)現(xiàn)一個(gè)代理類,唯一的要求是同樣結(jié)合調(diào)用處理器實(shí)施方法的分派轉(zhuǎn)發(fā),您的第一反應(yīng)將是什么呢?”聽起來似乎并不是很復(fù)雜”。的確,掐指算算所涉及的工作無非包括幾個(gè)反射調(diào)用,以及對原始類型數(shù)據(jù)的裝箱或拆箱過程,其他的似乎都已經(jīng)水到渠成。非常地好,讓我們整理一下思緒,一起來完成一次完整的推演過程吧。
清單 11. 代理類中方法調(diào)用的分派轉(zhuǎn)發(fā)推演實(shí)現(xiàn)
// 假設(shè)需代理接口 Simulatorpublic interface Simulator {short simulate(int arg1, long arg2, String arg3)throws ExceptionA, ExceptionB;}// 假設(shè)代理類為 SimulatorProxy, 其類聲明將如下final public class SimulatorProxy implements Simulator {// 調(diào)用處理器對象的引用protected InvocationHandler handler;// 以調(diào)用處理器為參數(shù)的構(gòu)造函數(shù)public SimulatorProxy(InvocationHandler handler){this.handler = handler;}// 實(shí)現(xiàn)接口方法 simulatepublic 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(略)}// 第二步是調(diào)用 handler 的 invoke 方法分派轉(zhuǎn)發(fā)方法調(diào)用Object r = null;try {r = handler.invoke(this,method,// 對于原始類型參數(shù)需要進(jìn)行裝箱操作new Object[] {new Integer(arg1), new Long(arg2), arg3});}catch(Throwable e) {// 異常處理 2(略)}// 第三步是返回結(jié)果(返回類型是原始類型則需要進(jìn)行拆箱操作)return ((Short)r).shortValue();}}
模擬推演為了突出通用邏輯所以更多地關(guān)注正常流程,而淡化了錯(cuò)誤處理,但在實(shí)際中錯(cuò)誤處理同樣非常重要。從以上的推演中我們可以得出一個(gè)非常通用的結(jié)構(gòu)化流程:
?第一步從代理接口獲取被調(diào)用的方法對象;?第二步分派方法到調(diào)用處理器執(zhí)行;?第三步返回結(jié)果。
在這之中,所有的信息都是可以已知的,比如接口名、方法名、參數(shù)類型、返回類型以及所需的裝箱和拆箱操作,那么既然我們手工編寫是如此,那又有什么理由不相信 ProxyGenerator 不會(huì)做類似的實(shí)現(xiàn)呢?至少這是一種比較可能的實(shí)現(xiàn)。
接下來讓我們把注意力重新回到先前被淡化的錯(cuò)誤處理上來。在異常處理 1 處,由于我們有理由確保所有的信息如接口名、方法名和參數(shù)類型都準(zhǔn)確無誤,所以這部分異常發(fā)生的概率基本為零,所以基本可以忽略。而異常處理 2 處,我們需要思考得更多一些。回想一下,接口方法可能聲明支持一個(gè)異常列表,而調(diào)用處理器 invoke 方法又可能拋出與接口方法不支持的異常,再回想一下先前提及的 Java 動(dòng)態(tài)代理的關(guān)于異常處理的特點(diǎn),對于不支持的異常,必須拋 UndeclaredThrowableException 運(yùn)行時(shí)異常。所以通過再次推演,我們可以得出一個(gè)更加清晰的異常處理 2 的情況:
清單 12. 細(xì)化的異常處理 2
Object r = null;try {r = handler.invoke(this,method,new Object[] {new Integer(arg1),new Long(arg2),arg3});} catch( ExceptionA e) {// 接口方法支持 ExceptionA,可以拋出throw e;} catch( ExceptionB e ) {// 接口方法支持 ExceptionB,可以拋出throw e;} catch(Throwable e) {// 其他不支持的異常,// 一律拋 UndeclaredThrowableExceptionthrow new UndeclaredThrowableException(e);}
這樣我們就完成了對動(dòng)態(tài)代理類的推演實(shí)現(xiàn)。推演實(shí)現(xiàn)遵循了一個(gè)相對固定的模式,可以適用于任意定義的任何接口,而且代碼生成所需的信息都是可知的,那么有理由相信即使是機(jī)器自動(dòng)編寫的代碼也有可能延續(xù)這樣的風(fēng)格,至少可以保證這是可行的。
美中不足
誠然,Proxy 已經(jīng)設(shè)計(jì)得非常優(yōu)美,但是還是有一點(diǎn)點(diǎn)小小的遺憾之處,那就是它始終無法擺脫僅支持 interface 代理的桎梏,因?yàn)樗脑O(shè)計(jì)注定了這個(gè)遺憾。回想一下那些動(dòng)態(tài)生成的代理類的繼承關(guān)系圖,它們已經(jīng)注定有一個(gè)共同的父類叫 Proxy。Java 的繼承機(jī)制注定了這些動(dòng)態(tài)代理類們無法實(shí)現(xiàn)對 class 的動(dòng)態(tài)代理,原因是多繼承在 Java 中本質(zhì)上就行不通。
有很多條理由,人們可以否定對 class 代理的必要性,但是同樣有一些理由,相信支持 class 動(dòng)態(tài)代理會(huì)更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細(xì)化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實(shí)現(xiàn)對抽象類的動(dòng)態(tài)代理,相信也有其內(nèi)在的價(jià)值。此外,還有一些歷史遺留的類,它們將因?yàn)闆]有實(shí)現(xiàn)任何接口而從此與動(dòng)態(tài)代理永世無緣。如此種種,不得不說是一個(gè)小小的遺憾。
但是,不完美并不等于不偉大,偉大是一種本質(zhì),Java 動(dòng)態(tài)代理就是佐例。
-- 來自IBM Developer 王忠平, 何平
總結(jié)
代理模式是先人們針對一類特定問題總結(jié)出的經(jīng)驗(yàn)結(jié)晶,并在各個(gè)領(lǐng)域中得以靈活應(yīng)用。特別是在編程領(lǐng)域,不同語言根據(jù)自身的設(shè)計(jì)規(guī)范和特點(diǎn)融會(huì)貫通,最終都能夠落實(shí)到具體的解決方案中,譬如Spring AOP在性能事務(wù)上的增強(qiáng)提升,或者是攔截器實(shí)現(xiàn)在日志和權(quán)限層面的控制過濾等等,都能做到對原接口事務(wù)的無侵入,同時(shí)還能靈活管控,大范圍實(shí)施,達(dá)到我們的預(yù)期,這就是代理模式,巧妙之處。
感謝參考文獻(xiàn)與工具支持
[1]Java 動(dòng)態(tài)代理機(jī)制分析及擴(kuò)展:https://developer.ibm.com/zh/articles/j-lo-proxy1/
[2]代理模式原理及實(shí)例講解:https://developer.ibm.com/zh/articles/j-lo-proxy-pattern/
[3]Dynamic Proxy Classes:https://java.sun.com/j2se/1.4.2/docs/guide/reflection/proxy.html
[4]動(dòng)態(tài)代理機(jī)制:https://www.ibm.com/developerworks/cn/java/j-jtp08305.html
[5]圖片素材來源:https://www.pexels.com/
[6]流程圖設(shè)計(jì)來源:https://www.processon.com/
