【转】 代理模式
原文鏈接:http://layznet.iteye.com/blog/1182924
一、代理概念 
為某個(gè)對(duì)象提供一個(gè)代理,以控制對(duì)這個(gè)對(duì)象的訪問。 代理類和委托類有共同的父類或父接口,這樣在任何使用委托類對(duì)象的地方都可以用代理對(duì)象替代。代理類負(fù)責(zé)請(qǐng)求的預(yù)處理、過濾、將請(qǐng)求分派給委托類處理、以及委托類執(zhí)行完請(qǐng)求后的后續(xù)處理。 
圖1:代理模式 
從圖中可以看出,代理接口(Subject)、代理類(ProxySubject)、委托類(RealSubject)形成一個(gè)“品”字結(jié)構(gòu)。 
根據(jù)代理類的生成時(shí)間不同可以將代理分為靜態(tài)代理和動(dòng)態(tài)代理兩種。 
下面以一個(gè)模擬需求說(shuō)明靜態(tài)代理和動(dòng)態(tài)代理:委托類要處理一項(xiàng)耗時(shí)較長(zhǎng)的任務(wù),客戶類需要打印出執(zhí)行任務(wù)消耗的時(shí)間。解決這個(gè)問題需要記錄任務(wù)執(zhí)行前時(shí)間和任務(wù)執(zhí)行后時(shí)間,兩個(gè)時(shí)間差就是任務(wù)執(zhí)行消耗的時(shí)間。 
二、靜態(tài)代理 
由程序員創(chuàng)建或工具生成代理類的源碼,再編譯代理類。所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和委托類的關(guān)系在運(yùn)行前就確定了。 
清單1:代理接口 
 
清單2:委托類,具體處理業(yè)務(wù)。 
 
清單3:靜態(tài)代理類 
 
清單4:生成靜態(tài)代理類工廠 
 
清單5:客戶類 
 
靜態(tài)代理類優(yōu)缺點(diǎn) 
優(yōu)點(diǎn):業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性。這是代理的共有優(yōu)點(diǎn)。 
缺點(diǎn): 
1)代理對(duì)象的一個(gè)接口只服務(wù)于一種類型的對(duì)象,如果要代理的方法很多,勢(shì)必要為每一種方法都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時(shí)就無(wú)法勝任了。 
2)如果接口增加一個(gè)方法,除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。 
三、動(dòng)態(tài)代理 
動(dòng)態(tài)代理類的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運(yùn)行時(shí)確定。 
1、先看看與動(dòng)態(tài)代理緊密關(guān)聯(lián)的Java API。 
1)java.lang.reflect.Proxy 
這是 Java 動(dòng)態(tài)代理機(jī)制生成的所有動(dòng)態(tài)代理類的父類,它提供了一組靜態(tài)方法來(lái)為一組接口動(dòng)態(tài)地生成代理類及其對(duì)象。 
清單6:Proxy類的靜態(tài)方法 
 
2)java.lang.reflect.InvocationHandler 
這是調(diào)用處理器接口,它自定義了一個(gè) invoke 方法,用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對(duì)委托類的代理訪問。每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都要指定一個(gè)對(duì)應(yīng)的調(diào)用處理器對(duì)象。 
清單7:InvocationHandler的核心方法 
 
3)java.lang.ClassLoader 
 這是類裝載器類,負(fù)責(zé)將類的字節(jié)碼裝載到 Java 虛擬機(jī)(JVM)中并為其定義類對(duì)象,然后該類才能被使用。Proxy 靜態(tài)方法生成動(dòng)態(tài)代理類同樣需要通過類裝載器來(lái)進(jìn)行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由 JVM 在運(yùn)行時(shí)動(dòng)態(tài)生成的而非預(yù)存在于任何一個(gè) .class 文件中。 
每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都需要指定一個(gè)類裝載器對(duì)象 
2、動(dòng)態(tài)代理實(shí)現(xiàn)步驟 
具體步驟是: 
a. 實(shí)現(xiàn)InvocationHandler接口創(chuàng)建自己的調(diào)用處理器 
b. 給Proxy類提供ClassLoader和代理接口類型數(shù)組創(chuàng)建動(dòng)態(tài)代理類 
c. 以調(diào)用處理器類型為參數(shù),利用反射機(jī)制得到動(dòng)態(tài)代理類的構(gòu)造函數(shù) 
d. 以調(diào)用處理器對(duì)象為參數(shù),利用動(dòng)態(tài)代理類的構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類對(duì)象 
清單8:分步驟實(shí)現(xiàn)動(dòng)態(tài)代理 
 
Proxy類的靜態(tài)方法newProxyInstance對(duì)上面具體步驟的后三步做了封裝,簡(jiǎn)化了動(dòng)態(tài)代理對(duì)象的獲取過程。 
清單9:簡(jiǎn)化后的動(dòng)態(tài)代理實(shí)現(xiàn) 
 
3、動(dòng)態(tài)代理實(shí)現(xiàn)示例 
清單10:創(chuàng)建自己的調(diào)用處理器 
 
清單11:生成動(dòng)態(tài)代理對(duì)象的工廠,工廠方法列出了如何生成動(dòng)態(tài)代理類對(duì)象的步驟。 
 
清單12:動(dòng)態(tài)代理客戶類 
 
4、動(dòng)態(tài)代理機(jī)制特點(diǎn)? 
首先是動(dòng)態(tài)生成的代理類本身的一些特點(diǎn)。1)包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因?yàn)榻涌诓荒鼙欢x為 protect 或 private,所以除 public 之外就是默認(rèn)的 package 訪問級(jí)別),那么它將被定義在該接口所在包(假設(shè)代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.ibm.developerworks),這樣設(shè)計(jì)的目的是為了最大程度的保證動(dòng)態(tài)代理類不會(huì)因?yàn)榘芾淼膯栴}而無(wú)法被成功定義并訪問;2)類修 飾符:該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;3)類名:格式是“$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 值增加,原因是如果對(duì)同一組接口(包括接口排列的順序相同)試圖重復(fù)創(chuàng)建動(dòng)態(tài)代理類,它會(huì)很聰明地返回先前已經(jīng)創(chuàng)建好的代理類的類對(duì)象,而不會(huì)再嘗試去創(chuàng) 建一個(gè)全新的代理類,這樣可以節(jié)省不必要的代碼重復(fù)生成,提高了代理類的創(chuàng)建效率。4)類繼承關(guān)系:該類的繼承關(guān)系如圖: 
圖2:動(dòng)態(tài)代理類的繼承關(guān)系 
由圖可見,Proxy 類是它的父類,這個(gè)規(guī)則適用于所有由 Proxy 創(chuàng)建的動(dòng)態(tài)代理類。而且該類還實(shí)現(xiàn)了其所代理的一組接口,這就是為什么它能夠被安全地類型轉(zhuǎn)換到其所代理的某接口的根本原因。 
接下來(lái)讓我們了解一下代理類實(shí)例的一些特點(diǎn)。每個(gè)實(shí)例都會(huì)關(guān)聯(lián)一個(gè)調(diào)用處理器對(duì)象,可以通過 Proxy 提供的靜態(tài)方法 getInvocationHandler 去獲得代理類實(shí)例的調(diào)用處理器對(duì)象。在代理類實(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ū)分度,所以為了保證代理類與委托類對(duì)外的一致性,這三個(gè)方法也應(yīng) 該被分派到委托類執(zhí)行。當(dāng)代理的一組接口有重復(fù)聲明的方法且該方法被調(diào)用時(shí),代理類總是從排在最前面的接口中獲取方法對(duì)象并分派給調(diào)用處理器,而無(wú)論代理 類實(shí)例是否正在以該接口(或繼承于該接口的某子接口)的形式被外部引用,因?yàn)樵诖眍悆?nèi)部無(wú)法區(qū)分其當(dāng)前的被引用類型。 
接著來(lái)了解一下被代理的一組接口有哪些特點(diǎn)。首先,要注意不能有重復(fù)的接口,以避免動(dòng)態(tài)代理類代碼生成時(shí)的編譯錯(cuò)誤。其次,這些接口對(duì)于類裝載器 必須可見,否則類裝載器將無(wú)法鏈接它們,將會(huì)導(dǎo)致類定義失敗。再次,需被代理的所有非 public 的接口必須在同一個(gè)包中,否則代理類生成也會(huì)失敗。最后,接口的數(shù)目不能超過 65535,這是 JVM 設(shè)定的限制。 
最后再來(lái)了解一下異常處理方面的特點(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)為我們?cè)O(shè)計(jì)好了解決方法:它將會(huì)拋出 UndeclaredThrowableException 異常。這個(gè)異常是一個(gè) RuntimeException 類型,所以不會(huì)引起編譯錯(cuò)誤。通過該異常的 getCause 方法,還可以獲得原來(lái)那個(gè)不受支持的異常對(duì)象,以便于錯(cuò)誤診斷。 
5、動(dòng)態(tài)代理的優(yōu)點(diǎn)和美中不足 
優(yōu)點(diǎn): 
動(dòng)態(tài)代理與靜態(tài)代理相比較,最大的好處是接口中聲明的所有方法都被轉(zhuǎn)移到調(diào)用處理器一個(gè)集中的方法中處理 (InvocationHandler.invoke)。這樣,在接口方法數(shù)量比較多的時(shí)候,我們可以進(jìn)行靈活處理,而不需要像靜態(tài)代理那樣每一個(gè)方法進(jìn) 行中轉(zhuǎn)。在本示例中看不出來(lái),因?yàn)閕nvoke方法體內(nèi)嵌入了具體的外圍業(yè)務(wù)(記錄任務(wù)處理前后時(shí)間并計(jì)算時(shí)間差),實(shí)際中可以類似Spring AOP那樣配置外圍業(yè)務(wù)。 
美中不足: 
誠(chéng)然,Proxy 已經(jīng)設(shè)計(jì)得非常優(yōu)美,但是還是有一點(diǎn)點(diǎn)小小的遺憾之處,那就是它始終無(wú)法擺脫僅支持 interface 代理的桎梏,因?yàn)樗脑O(shè)計(jì)注定了這個(gè)遺憾。回想一下那些動(dòng)態(tài)生成的代理類的繼承關(guān)系圖,它們已經(jīng)注定有一個(gè)共同的父類叫 Proxy。Java 的繼承機(jī)制注定了這些動(dòng)態(tài)代理類們無(wú)法實(shí)現(xiàn)對(duì) class 的動(dòng)態(tài)代理,原因是多繼承在 Java 中本質(zhì)上就行不通。 
有很多條理由,人們可以否定對(duì) class 代理的必要性,但是同樣有一些理由,相信支持 class 動(dòng)態(tài)代理會(huì)更美好。接口和類的劃分,本就不是很明顯,只是到了 Java 中才變得如此的細(xì)化。如果只從方法的聲明及是否被定義來(lái)考量,有一種兩者的混合體,它的名字叫抽象類。實(shí)現(xiàn)對(duì)抽象類的動(dòng)態(tài)代理,相信也有其內(nèi)在的價(jià)值。此 外,還有一些歷史遺留的類,它們將因?yàn)闆]有實(shí)現(xiàn)任何接口而從此與動(dòng)態(tài)代理永世無(wú)緣。如此種種,不得不說(shuō)是一個(gè)小小的遺憾。
轉(zhuǎn)載于:https://www.cnblogs.com/bingzhikun/p/4797679.html
總結(jié)
                            
                        - 上一篇: 关于office2010的mso问题和卸
 - 下一篇: Smooks转换设计