摘要:除了和外,我們還有最后一招我直接把一個(gè)代理類的源代碼用字符串拼出來,然后基于這個(gè)字符串調(diào)用的編譯期,動(dòng)態(tài)的創(chuàng)建一個(gè)新的文件,然后動(dòng)態(tài)編譯這個(gè)文件,這樣也能得到一個(gè)新的代理類。
面試問題:Java里的代理設(shè)計(jì)模式(Proxy Design Pattern)一共有幾種實(shí)現(xiàn)方式?這個(gè)題目很像孔乙己?jiǎn)枴败钕愣沟能钭钟心膸追N寫法?”
所謂代理模式,是指客戶端(Client)并不直接調(diào)用實(shí)際的對(duì)象(下圖右下角的RealSubject),而是通過調(diào)用代理(Proxy),來間接的調(diào)用實(shí)際的對(duì)象。
代理模式的使用場(chǎng)合,一般是由于客戶端不想直接訪問實(shí)際對(duì)象,或者訪問實(shí)際的對(duì)象存在技術(shù)上的障礙,因而通過代理對(duì)象作為橋梁,來完成間接訪問。
實(shí)現(xiàn)方式一:靜態(tài)代理開發(fā)一個(gè)接口IDeveloper,該接口包含一個(gè)方法writeCode,寫代碼。
public interface IDeveloper { public void writeCode(); }
創(chuàng)建一個(gè)Developer類,實(shí)現(xiàn)該接口。
public class Developer implements IDeveloper{ private String name; public Developer(String name){ this.name = name; } @Override public void writeCode() { System.out.println("Developer " + name + " writes code"); } }
測(cè)試代碼:創(chuàng)建一個(gè)Developer實(shí)例,名叫Jerry,去寫代碼!
public class DeveloperTest { public static void main(String[] args) { IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); } }
現(xiàn)在問題來了。Jerry的項(xiàng)目經(jīng)理對(duì)Jerry光寫代碼,而不維護(hù)任何的文檔很不滿。假設(shè)哪天Jerry休假去了,其他的程序員來接替Jerry的工作,對(duì)著陌生的代碼一臉問號(hào)。經(jīng)全組討論決定,每個(gè)開發(fā)人員寫代碼時(shí),必須同步更新文檔。
為了強(qiáng)迫每個(gè)程序員在開發(fā)時(shí)記著寫文檔,而又不影響大家寫代碼這個(gè)動(dòng)作本身, 我們不修改原來的Developer類,而是創(chuàng)建了一個(gè)新的類,同樣實(shí)現(xiàn)IDeveloper接口。這個(gè)新類DeveloperProxy內(nèi)部維護(hù)了一個(gè)成員變量,指向原始的IDeveloper實(shí)例:
public class DeveloperProxy implements IDeveloper{ private IDeveloper developer; public DeveloperProxy(IDeveloper developer){ this.developer = developer; } @Override public void writeCode() { System.out.println("Write documentation..."); this.developer.writeCode(); } }
這個(gè)代理類實(shí)現(xiàn)的writeCode方法里,在調(diào)用實(shí)際程序員writeCode方法之前,加上一個(gè)寫文檔的調(diào)用,這樣就確保了程序員寫代碼時(shí)都伴隨著文檔更新。
測(cè)試代碼:
靜態(tài)代理方式的優(yōu)點(diǎn)1. 易于理解和實(shí)現(xiàn)
2. 代理類和真實(shí)類的關(guān)系是編譯期靜態(tài)決定的,和下文馬上要介紹的動(dòng)態(tài)代理比較起來,執(zhí)行時(shí)沒有任何額外開銷。
靜態(tài)代理方式的缺點(diǎn)每一個(gè)真實(shí)類都需要一個(gè)創(chuàng)建新的代理類。還是以上述文檔更新為例,假設(shè)老板對(duì)測(cè)試工程師也提出了新的要求,讓測(cè)試工程師每次測(cè)出bug時(shí),也要及時(shí)更新對(duì)應(yīng)的測(cè)試文檔。那么采用靜態(tài)代理的方式,測(cè)試工程師的實(shí)現(xiàn)類ITester也得創(chuàng)建一個(gè)對(duì)應(yīng)的ITesterProxy類。
public interface ITester { public void doTesting(); } Original tester implementation class: public class Tester implements ITester { private String name; public Tester(String name){ this.name = name; } @Override public void doTesting() { System.out.println("Tester " + name + " is testing code"); } } public class TesterProxy implements ITester{ private ITester tester; public TesterProxy(ITester tester){ this.tester = tester; } @Override public void doTesting() { System.out.println("Tester is preparing test documentation..."); tester.doTesting(); } }
正是因?yàn)橛辛遂o態(tài)代碼方式的這個(gè)缺點(diǎn),才誕生了Java的動(dòng)態(tài)代理實(shí)現(xiàn)方式。
Java動(dòng)態(tài)代理實(shí)現(xiàn)方式一:InvocationHandlerInvocationHandler的原理我曾經(jīng)專門寫文章介紹過:Java動(dòng)態(tài)代理之InvocationHandler最簡(jiǎn)單的入門教程
通過InvocationHandler, 我可以用一個(gè)EnginnerProxy代理類來同時(shí)代理Developer和Tester的行為。
public class EnginnerProxy implements InvocationHandler { Object obj; public Object bind(Object obj) { this.obj = obj; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Enginner writes document"); Object res = method.invoke(obj, args); return res; } }
真實(shí)類的writeCode和doTesting方法在動(dòng)態(tài)代理類里通過反射的方式進(jìn)行執(zhí)行。
測(cè)試輸出:
通過InvocationHandler實(shí)現(xiàn)動(dòng)態(tài)代理的局限性
假設(shè)有個(gè)產(chǎn)品經(jīng)理類(ProductOwner) 沒有實(shí)現(xiàn)任何接口。
public class ProductOwner { private String name; public ProductOwner(String name){ this.name = name; } public void defineBackLog(){ System.out.println("PO: " + name + " defines Backlog."); } }
我們?nèi)匀徊扇nginnerProxy代理類去代理它,編譯時(shí)不會(huì)出錯(cuò)。運(yùn)行時(shí)會(huì)發(fā)生什么事?
ProductOwner po = new ProductOwner("Ross"); ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po); poProxy.defineBackLog();
運(yùn)行時(shí)報(bào)錯(cuò)。所以局限性就是:如果被代理的類未實(shí)現(xiàn)任何接口,那么不能采用通過InvocationHandler動(dòng)態(tài)代理的方式去代理它的行為。
Java動(dòng)態(tài)代理實(shí)現(xiàn)方式二:CGLIBCGLIB是一個(gè)Java字節(jié)碼生成庫,提供了易用的API對(duì)Java字節(jié)碼進(jìn)行創(chuàng)建和修改。關(guān)于這個(gè)開源庫的更多細(xì)節(jié),請(qǐng)移步至CGLIB在github上的倉庫:https://github.com/cglib/cglib
我們現(xiàn)在嘗試用CGLIB來代理之前采用InvocationHandler沒有成功代理的ProductOwner類(該類未實(shí)現(xiàn)任何接口)。
現(xiàn)在我改為使用CGLIB API來創(chuàng)建代理類:
public class EnginnerCGLibProxy { Object obj; public Object bind(final Object target) { this.obj = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(obj.getClass()); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Enginner 2 writes document"); Object res = method.invoke(target, args); return res; } } ); return enhancer.create(); } }
測(cè)試代碼:
ProductOwner ross = new ProductOwner("Ross"); ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross); rossProxy.defineBackLog();
盡管ProductOwner未實(shí)現(xiàn)任何代碼,但它也成功被代理了:
用CGLIB實(shí)現(xiàn)Java動(dòng)態(tài)代理的局限性如果我們了解了CGLIB創(chuàng)建代理類的原理,那么其局限性也就一目了然。我們現(xiàn)在做個(gè)實(shí)驗(yàn),將ProductOwner類加上final修飾符,使其不可被繼承:
再次執(zhí)行測(cè)試代碼,這次就報(bào)錯(cuò)了: Cannot subclass final class XXXX。
所以通過CGLIB成功創(chuàng)建的動(dòng)態(tài)代理,實(shí)際是被代理類的一個(gè)子類。那么如果被代理類被標(biāo)記成final,也就無法通過CGLIB去創(chuàng)建動(dòng)態(tài)代理。
Java動(dòng)態(tài)代理實(shí)現(xiàn)方式三:通過編譯期提供的API動(dòng)態(tài)創(chuàng)建代理類
假設(shè)我們確實(shí)需要給一個(gè)既是final,又未實(shí)現(xiàn)任何接口的ProductOwner類創(chuàng)建動(dòng)態(tài)代碼。除了InvocationHandler和CGLIB外,我們還有最后一招:
我直接把一個(gè)代理類的源代碼用字符串拼出來,然后基于這個(gè)字符串調(diào)用JDK的Compiler(編譯期)API,動(dòng)態(tài)的創(chuàng)建一個(gè)新的.java文件,然后動(dòng)態(tài)編譯這個(gè).java文件,這樣也能得到一個(gè)新的代理類。
測(cè)試成功:
我拼好了代碼類的源代碼,動(dòng)態(tài)創(chuàng)建了代理類的.java文件,能夠在Eclipse里打開這個(gè)用代碼創(chuàng)建的.java文件,
下圖是如何動(dòng)態(tài)創(chuàng)建ProductPwnerSCProxy.java文件:
下圖是如何用JavaCompiler API動(dòng)態(tài)編譯前一步動(dòng)態(tài)創(chuàng)建出的.java文件,生成.class文件:
下圖是如何用類加載器加載編譯好的.class文件到內(nèi)存:
如果您想試試這篇文章介紹的這四種代理模式(Proxy Design Pattern), 請(qǐng)參考我的github倉庫,全部代碼都在上面。感謝閱讀。
https://github.com/i042416/Ja...
要獲取更多Jerry的原創(chuàng)技術(shù)文章,請(qǐng)關(guān)注公眾號(hào)"汪子熙"或者掃描下面二維碼:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/71808.html
摘要:面向?qū)ο笤O(shè)計(jì)里的設(shè)計(jì)模式之代理模式,相信很多朋友已經(jīng)很熟悉了。代表當(dāng)前執(zhí)行方法的實(shí)例,即方法調(diào)用者。代表具體的方法名稱。現(xiàn)在我們?cè)俅握{(diào)用,傳入構(gòu)造器返回的代理對(duì)象打印輸出,代理邏輯生效了和的一樣優(yōu)雅地實(shí)現(xiàn)了代理設(shè)計(jì)模式。 showImg(https://segmentfault.com/img/remote/1460000016760603);面向?qū)ο笤O(shè)計(jì)里的設(shè)計(jì)模式之Proxy(代理...
摘要:簡(jiǎn)述從這篇文章起,我們將繼續(xù)邂逅設(shè)計(jì)模式系列篇中的第二篇代理模式。代理模式可以說很多初級(jí)中級(jí)開發(fā)者迷惑的設(shè)計(jì)模式。首先我們需要使用類圖直觀地表示出代理模式思想。所以基于代理模式很輕松就實(shí)現(xiàn)。簡(jiǎn)述: 從這篇文章起,我們將繼續(xù)Kotlin邂逅設(shè)計(jì)模式系列篇中的第二篇代理模式。代理模式可以說很多初級(jí)中級(jí)開發(fā)者迷惑的設(shè)計(jì)模式。但是它確實(shí)應(yīng)用很廣,不用多說大家非常熟悉的Retrofit框架,內(nèi)部使用了...
摘要:本文主要是關(guān)于跨域的幾種方式,關(guān)于什么是跨域這里就不多說了,寫這個(gè)也是為了記住一些知識(shí)點(diǎn)的。我自己用和的寫過一些,但是沒有在實(shí)際工作中用過,所以對(duì)這一塊了解不深。 本文主要是關(guān)于跨域的幾種方式,關(guān)于什么是跨域這里就不多說了,寫這個(gè)也是為了記住一些知識(shí)點(diǎn)的。 一. jsonp jsonp的跨域方式很容易理解,頁面的的每一個(gè)script標(biāo)簽瀏覽器都會(huì)發(fā)送get請(qǐng)求獲取對(duì)應(yīng)的文本資源,獲取到...
閱讀 2852·2023-04-26 01:02
閱讀 1877·2021-11-17 09:38
閱讀 805·2021-09-22 15:54
閱讀 2910·2021-09-22 15:29
閱讀 897·2021-09-22 10:02
閱讀 3450·2019-08-30 15:54
閱讀 2015·2019-08-30 15:44
閱讀 1605·2019-08-26 13:46