代理模式(轉(zhuǎn)載自https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html)

東漢末年码秉,大將軍何進引董卓入京逮矛,想借西北王的軍隊對抗閹黨,無奈自己先被閹黨做掉转砖,而后造成巨變须鼎,導(dǎo)致諸侯并起,最終形成三國鼎立局面府蔗。漢獻帝即位后晋控,初平三年(公元 192 年),治中從事毛玠向曹操建議“奉天子以令不臣”姓赤,曹操采納了他的建議赡译,迎接漢獻帝來到許昌。漢獻帝劉協(xié)在許都沒有實際的權(quán)利不铆,曹操不斷地誅除公卿大臣蝌焚,不斷地集軍政大權(quán)于一身。建安元年八月誓斥,曹操進駐洛陽只洒,立刻趁張楊、楊奉兵眾在外劳坑,趕跑了韓暹,接著做了三件事:殺侍中臺崇距芬、尚書馮碩等,謂“討有罪”框仔;封董承、伏完等离斩,謂“賞有功”银舱;追賜射聲校尉沮俊,謂“矜死節(jié)”纵朋。然后在第九天趁他人尚未來得及反應(yīng)的情況下茄袖,遷帝都許操软,使皇帝擺脫其他勢力的控制。此后宪祥,他還加緊步伐剪除異己,提高自己的權(quán)勢蝗羊。他首先向最有影響力的三公發(fā)難,罷免太尉楊彪翔悠、司空張喜野芒;其次誅殺議郎趙彥;再次是發(fā)兵征討楊奉狞悲,解除近兵之憂摇锋;最后是一方面以天子名義譴責(zé)袁紹,打擊其氣焰荸恕,另一方面將大將軍讓予袁紹,穩(wěn)定大敵剑刑。這就是歷史上著名的“挾天子以令諸侯”双肤。漢獻帝與曹操的關(guān)系,是歷史上兩位偉大的政治家的聯(lián)手七芭,穩(wěn)定了東漢政權(quán)蔑赘,最終平穩(wěn)交接給曹魏政權(quán)预明,也間接映射了我們本文要講解的“代理模式”耙箍。

代理模式

代理模式使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問≡睦遥現(xiàn)實世界的代理人被授權(quán)執(zhí)行當(dāng)事人的一些事宜汁针,無需當(dāng)事人出面,從第三方的角度看辉词,似乎當(dāng)事人并不存在猾骡,因為他只和代理人通信。而事實上代理人是要有當(dāng)事人的授權(quán)隘蝎,并且在核心問題上還需要請示當(dāng)事人襟企。

在軟件設(shè)計中,使用代理模式的意圖也很多曼振,比如因為安全原因需要屏蔽客戶端直接訪問真實對象蔚龙,或者在遠程調(diào)用中需要使用代理類處理遠程方法調(diào)用的技術(shù)細節(jié) (如 RMI),也可能為了提升系統(tǒng)性能甲雅,對真實對象進行封裝坑填,從而達到延遲加載的目的。

代理模式角色分為 4 種:

  1. 主題接口:定義代理類和真實主題的公共對外方法妖枚,也是代理類代理真實主題的方法苍在;

  2. 真實主題:真正實現(xiàn)業(yè)務(wù)邏輯的類荠商;

  3. 代理類:用來代理和封裝真實主題莱没;

  4. Main:客戶端酷鸦,使用代理類和主題接口完成一些工作。

延遲加載

以一個簡單的示例來闡述使用代理模式實現(xiàn)延遲加載的方法及其意義。假設(shè)某客戶端軟件有根據(jù)用戶請求去數(shù)據(jù)庫查詢數(shù)據(jù)的功能眠寿。在查詢數(shù)據(jù)前盯拱,需要獲得數(shù)據(jù)庫連接,軟件開啟時初始化系統(tǒng)的所有類宁舰,此時嘗試獲得數(shù)據(jù)庫連接奢浑。當(dāng)系統(tǒng)有大量的類似操作存在時 (比如 XML 解析等),所有這些初始化操作的疊加會使得系統(tǒng)的啟動速度變得非常緩慢壤蚜。為此徊哑,使用代理模式的代理類封裝對數(shù)據(jù)庫查詢中的初始化操作,當(dāng)系統(tǒng)啟動時著蟹,初始化這個代理類梢莽,而非真實的數(shù)據(jù)庫查詢類,而代理類什么都沒有做炕横。因此葡粒,它的構(gòu)造是相當(dāng)迅速的膜钓。

在系統(tǒng)啟動時颂斜,將消耗資源最多的方法都使用代理模式分離拾枣,可以加快系統(tǒng)的啟動速度,減少用戶的等待時間司蔬。而在用戶真正做查詢操作時再由代理類單獨去加載真實的數(shù)據(jù)庫查詢類姨蝴,完成用戶的請求。這個過程就是使用代理模式實現(xiàn)了延遲加載授帕。

延遲加載的核心思想是:如果當(dāng)前并沒有使用這個組件浮梢,則不需要真正地初始化它,使用一個代理對象替代它的原有的位置芥映,只要在真正需要的時候才對它進行加載远豺。使用代理模式的延遲加載是非常有意義的,首先霎苗,它可以在時間軸上分散系統(tǒng)壓力榛做,尤其在系統(tǒng)啟動時检眯,不必完成所有的初始化工作,從而加速啟動時間刽严;其次避凝,對很多真實主題而言眨补,在軟件啟動直到被關(guān)閉的整個過程中倒脓,可能根本不會被調(diào)用崎弃,初始化這些數(shù)據(jù)無疑是一種資源浪費。例如使用代理類封裝數(shù)據(jù)庫查詢類后线婚,系統(tǒng)的啟動過程這個例子盆均。若系統(tǒng)不使用代理模式,則在啟動時就要初始化 DBQuery 對象,而使用代理模式后虹脯,啟動時只需要初始化一個輕量級的對象 DBQueryProxy循集。

下面代碼 IDBQuery 是主題接口,定義代理類和真實類需要對外提供的服務(wù)疆柔,定義了實現(xiàn)數(shù)據(jù)庫查詢的公共方法 request() 函數(shù)镶柱。DBQuery 是真實主題,負責(zé)實際的業(yè)務(wù)操作鞋屈,DBQueryProxy 是 DBQuery 的代理類故觅。

清單 1. 延遲加載代理

public interface IDBQuery {

String request();

}

public class DBQuery implements IDBQuery{

public DBQuery(){

try{

Thread.sleep(1000);//假設(shè)數(shù)據(jù)庫連接等耗時操作

}catch(InterruptedException ex){

ex.printStackTrace();

}

}

@Override

public String request() {

// TODO Auto-generated method stub

return "request string";

}

}

public class DBQueryProxy implements IDBQuery{

private DBQuery real = null;

@Override

public String request() {

// TODO Auto-generated method stub

//在真正需要的時候才能創(chuàng)建真實對象输吏,創(chuàng)建過程可能很慢

if(real==null){

real = new DBQuery();

}//在多線程環(huán)境下,這里返回一個虛假類拄氯,類似于 Future 模式

return real.request();

}

}

public class Main {

public static void main(String[] args){

IDBQuery q = new DBQueryProxy(); //使用代里

q.request(); //在真正使用時才創(chuàng)建真實對象

}

}

動態(tài)代理

動態(tài)代理是指在運行時動態(tài)生成代理類。即熙含,代理類的字節(jié)碼將在運行時生成并載入當(dāng)前代理的 ClassLoader艇纺。與靜態(tài)處理類相比黔衡,動態(tài)類有諸多好處。首先盟劫,不需要為真實主題寫一個形式上完全一樣的封裝類侣签,假如主題接口中的方法很多,為每一個接口寫一個代理方法也很麻煩蹦肴。如果接口有變動猴娩,則真實主題和代理類都要修改,不利于系統(tǒng)維護矛双;其次蟆豫,使用一些動態(tài)代理的生成方法甚至可以在運行時制定代理類的執(zhí)行邏輯十减,從而大大提升系統(tǒng)的靈活性。

動態(tài)代理類使用字節(jié)碼動態(tài)生成加載技術(shù)嫉称,在運行時生成加載類织阅。生成動態(tài)代理類的方法很多,如闹炉,JDK 自帶的動態(tài)處理、CGLIB羡棵、Javassist 或者 ASM 庫嗅钻。JDK 的動態(tài)代理使用簡單,它內(nèi)置在 JDK 中秃流,因此不需要引入第三方 Jar 包柳弄,但相對功能比較弱碧注。CGLIB 和 Javassist 都是高級的字節(jié)碼生成庫,總體性能比 JDK 自帶的動態(tài)代理好轩端,而且功能十分強大碉纺。ASM 是低級的字節(jié)碼生成工具刻撒,使用 ASM 已經(jīng)近乎于在使用 Java bytecode 編程声怔,對開發(fā)人員要求最高,當(dāng)然悠汽,也是性能最好的一種動態(tài)代理生成工具芥驳。但 ASM 的使用很繁瑣兆旬,而且性能也沒有數(shù)量級的提升,與 CGLIB 等高級字節(jié)碼生成工具相比宿饱,ASM 程序的維護性較差,如果不是在對性能有苛刻要求的場合强饮,還是推薦 CGLIB 或者 Javassist为黎。

以清單 1 所示代碼中的 DBQueryProxy 為例,使用動態(tài)代理生成動態(tài)類柠座,替換上例中的 DBQueryProxy片橡。首先捧书,使用 JDK 的動態(tài)代理生成代理對象。JDK 的動態(tài)代理需要實現(xiàn)一個處理方法調(diào)用的 Handler爆哑,用于實現(xiàn)代理方法的內(nèi)部邏輯舆吮。

清單 2. 動態(tài)代理
`import java.lang.reflect.InvocationHandler;`

`import java.lang.reflect.Method;`

`public class DBQueryHandler implements InvocationHandler{`

`IDBQuery realQuery = null;//定義主題接口`

`@Override`

`public Object invoke(Object proxy, Method method, Object[] args)`

`throws Throwable {`

`// TODO Auto-generated method stub`

`//如果第一次調(diào)用色冀,生成真實主題`

`if(realQuery == null){`

`realQuery = new DBQuery();`

`}`

`//返回真實主題完成實際的操作`

`return realQuery.request();`

`}`

`}`

以上代碼實現(xiàn)了一個 Handler,可以看到屯换,它的內(nèi)部邏輯和 DBQueryProxy 是類似的与学。在調(diào)用真實主題的方法前索守,先嘗試生成真實主題對象。接著杨赤,需要使用這個 Handler 生成動態(tài)代理對象。代碼如清單 3 所示渺尘。

清單 3. 生成動態(tài)代理對象

`import java.lang.reflect.InvocationHandler;`

`import java.lang.reflect.Method;`

`import java.lang.reflect.Proxy;`

`public class DBQueryHandler implements InvocationHandler{`

`IDBQuery realQuery = null;//定義主題接口`

`@Override`

`public Object invoke(Object proxy, Method method, Object[] args)`

`throws Throwable {`

`// TODO Auto-generated method stub`

`//如果第一次調(diào)用说敏,生成真實主題`

`if(realQuery == null){`

`realQuery = new DBQuery();`

`}`

`//返回真實主題完成實際的操作`

`return realQuery.request();`

`}`

`public static IDBQuery createProxy(){`

`IDBQuery proxy = (IDBQuery)Proxy.newProxyInstance(`

`ClassLoader.getSystemClassLoader(), new Class[]{IDBQuery.class}, new DBQueryHandler()`

`);`

`return proxy;`

`}`

`}`

以上代碼生成了一個實現(xiàn)了 IDBQuery 接口的代理類盔沫,代理類的內(nèi)部邏輯由 DBQueryHandler 決定架诞。生成代理類后,由 newProxyInstance() 方法返回該代理類的一個實例谴忧。至此沾谓,一個完整的動態(tài)代理完成了。

在 Java 中昏兆,動態(tài)代理類的生成主要涉及對 ClassLoader 的使用妇穴。以 CGLIB 為例腾它,使用 CGLIB 生成動態(tài)代理,首先需要生成 Enhancer 類實例继蜡,并指定用于處理代理業(yè)務(wù)的回調(diào)類逛腿。在 Enhancer.create() 方法中仅颇,會使用 DefaultGeneratorStrategy.Generate() 方法生成動態(tài)代理類的字節(jié)碼忘瓦,并保存在 byte 數(shù)組中。接著使用 ReflectUtils.defineClass() 方法境蜕,通過反射,調(diào)用 ClassLoader.defineClass() 方法售滤,將字節(jié)碼裝載到 ClassLoader 中完箩,完成類的加載拉队。最后使用 ReflectUtils.newInstance() 方法,通過反射秩彤,生成動態(tài)類的實例事哭,并返回該實例慷蠕。基本流程是根據(jù)指定的回調(diào)類生成 Class 字節(jié)碼—通過 defineClass() 將字節(jié)碼定義為類—使用反射機制生成該類的實例澎现。從清單 4 到清單 7 所示是使用 CGLIB 動態(tài)反射生成類的完整過程每辟。

清單 4. 定義接口
`public interface BookProxy {`

`public void addBook();`

`}`
清單 5. 定義實現(xiàn)類
`//該類并沒有申明 BookProxy 接口`

`public class BookProxyImpl {`

`public void addBook() {`

`System.out.println("增加圖書的普通方法...");`

`}`

`}`
清單 6. 定義反射類及重載方法

`import java.lang.reflect.Method;`

`import net.sf.cglib.proxy.Enhancer;`

`import net.sf.cglib.proxy.MethodInterceptor;`

`import net.sf.cglib.proxy.MethodProxy;`

`public class BookProxyLib implements MethodInterceptor {`

`private Object target;`

`/**`

`* 創(chuàng)建代理對象`

`*`

`* @param target`

`* @return`

`*/`

`public Object getInstance(Object target) {`

`this.target = target;`

`Enhancer enhancer = new Enhancer();`

`enhancer.setSuperclass(this.target.getClass());`

`// 回調(diào)方法`

`enhancer.setCallback(this);`

`// 創(chuàng)建代理對象`

`return enhancer.create();`

`}`

`@Override`

`// 回調(diào)方法`

`public Object intercept(Object obj, Method method, Object[] args,`

`MethodProxy proxy) throws Throwable {`

`System.out.println("事物開始");`

`proxy.invokeSuper(obj, args);`

`System.out.println("事物結(jié)束");`

`return null;`

`}`

`}`

清單 7. 運行程序

`public class TestCglib {`

`public static void main(String[] args) {`

`BookProxyLib cglib=new BookProxyLib();`

`BookProxyImpl bookCglib=(BookProxyImpl)cglib.getInstance(new BookProxyImpl());`

`bookCglib.addBook();`

`}`

`}`

清單 8. 運行輸出

`事物開始`

`增加圖書的普通方法...`

`事物結(jié)束`

代理模式的應(yīng)用場合

代理模式有多種應(yīng)用場合妹蔽,如下所述:

  1. 遠程代理挠将,也就是為一個對象在不同的地址空間提供局部代表舔稀,這樣可以隱藏一個對象存在于不同地址空間的事實。比如說 WebService产园,當(dāng)我們在應(yīng)用程序的項目中加入一個 Web 引用,引用一個 WebService粘勒,此時會在項目中聲稱一個 WebReference 的文件夾和一些文件屎即,這個就是起代理作用的剑勾,這樣可以讓那個客戶端程序調(diào)用代理解決遠程訪問的問題;

  2. 虛擬代理暂刘,是根據(jù)需要創(chuàng)建開銷很大的對象捂刺,通過它來存放實例化需要很長時間的真實對象族展。這樣就可以達到性能的最優(yōu)化,比如打開一個網(wǎng)頁贵涵,這個網(wǎng)頁里面包含了大量的文字和圖片恰画,但我們可以很快看到文字拴还,但是圖片卻是一張一張地下載后才能看到,那些未打開的圖片框端盆,就是通過虛擬代里來替換了真實的圖片焕妙,此時代理存儲了真實圖片的路徑和尺寸孝偎;

  3. 安全代理衣盾,用來控制真實對象訪問時的權(quán)限。一般用于對象應(yīng)該有不同的訪問權(quán)限的時候阻塑;

  4. 指針引用果复,是指當(dāng)調(diào)用真實的對象時,代理處理另外一些事走搁。比如計算真實對象的引用次數(shù)私植,這樣當(dāng)該對象沒有引用時车酣,可以自動釋放它湖员,或當(dāng)?shù)谝淮我靡粋€持久對象時,將它裝入內(nèi)存窄坦,或是在訪問一個實際對象前凳寺,檢查是否已經(jīng)釋放它读第,以確保其他對象不能改變它曙博。這些都是通過代理在訪問一個對象時附加一些內(nèi)務(wù)處理;

  5. 延遲加載怜瞒,用代理模式實現(xiàn)延遲加載的一個經(jīng)典應(yīng)用就在 Hibernate 框架里面父泳。當(dāng) Hibernate 加載實體 bean 時,并不會一次性將數(shù)據(jù)庫所有的數(shù)據(jù)都裝載吴汪。默認情況下惠窄,它會采取延遲加載的機制,以提高系統(tǒng)的性能漾橙。Hibernate 中的延遲加載主要分為屬性的延遲加載和關(guān)聯(lián)表的延時加載兩類杆融。實現(xiàn)原理是使用代理攔截原有的 getter 方法,在真正使用對象數(shù)據(jù)時才去數(shù)據(jù)庫或者其他第三方組件加載實際的數(shù)據(jù)霜运,從而提升系統(tǒng)性能蒋腮。

結(jié)束語

設(shè)計模式是前人工作的總結(jié)和提煉。通常藕各,被人們廣泛流傳的設(shè)計模式都是對某一特定問題的成熟的解決方案池摧。如果能合理地使用設(shè)計模式,不僅能使系統(tǒng)更容易地被他人理解激况,同時也能使系統(tǒng)擁有更加合理的結(jié)構(gòu)作彤。本文對代理模式的 4 種角色、延遲加載乌逐、動態(tài)代理等做了一些介紹竭讳,希望能夠幫助讀者對代理模式有進一步的了解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浙踢,一起剝皮案震驚了整個濱河市装蓬,隨后出現(xiàn)的幾起案子妈嘹,更是在濱河造成了極大的恐慌忿晕,老刑警劉巖介牙,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奋岁,居然都是意外死亡思瘟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門闻伶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滨攻,“玉大人,你說我怎么就攤上這事蓝翰」馊疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵畜份,是天一觀的道長诞帐。 經(jīng)常有香客問我,道長爆雹,這世上最難降的妖魔是什么停蕉? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮钙态,結(jié)果婚禮上慧起,老公的妹妹穿的比我還像新娘。我一直安慰自己册倒,他們只是感情好蚓挤,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般灿意。 火紅的嫁衣襯著肌膚如雪估灿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天脾歧,我揣著相機與錄音甲捏,去河邊找鬼演熟。 笑死鞭执,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芒粹。 我是一名探鬼主播兄纺,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼化漆!你這毒婦竟也來了估脆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤座云,失蹤者是張志新(化名)和其女友劉穎疙赠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朦拖,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡圃阳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了璧帝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捍岳。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖睬隶,靈堂內(nèi)的尸體忽然破棺而出锣夹,到底是詐尸還是另有隱情,我是刑警寧澤苏潜,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布银萍,位于F島的核電站,受9級特大地震影響恤左,放射性物質(zhì)發(fā)生泄漏砖顷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一赃梧、第九天 我趴在偏房一處隱蔽的房頂上張望滤蝠。 院中可真熱鬧,春花似錦授嘀、人聲如沸物咳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽览闰。三九已至芯肤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間压鉴,已是汗流浹背崖咨。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留油吭,地道東北人击蹲。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像婉宰,于是被迫代替她去往敵國和親歌豺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容