Java代理設計模式(Proxy)的四種具體實現:靜態代理和動態代理
面試問題:Java裡的代理設計模式(Proxy Design Pattern)一共有幾種實現方式?這個題目很像孔乙己問“茴香豆的茴字有哪幾種寫法?”
所謂代理模式,是指客戶端(Client)並不直接呼叫實際的物件(下圖右下角的RealSubject),而是透過呼叫代理(Proxy),來間接的呼叫實際的物件。
代理模式的使用場合,一般是由於客戶端不想直接訪問實際物件,或者訪問實際的物件存在技術上的障礙,因而透過代理物件作為橋樑,來完成間接訪問。
實現方式一:靜態代理
開發一個介面IDeveloper,該介面包含一個方法writeCode,寫程式碼。
public interface IDeveloper { public void writeCode(); }
建立一個Developer類,實現該介面。
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"); } }
測試程式碼:建立一個Developer例項,名叫Jerry,去寫程式碼!
public class DeveloperTest { public static void main(String[] args) { IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); } }
現在問題來了。Jerry的專案經理對Jerry光寫程式碼,而不維護任何的文件很不滿。假設哪天Jerry休假去了,其他的程式設計師來接替Jerry的工作,對著陌生的程式碼一臉問號。經全組討論決定,每個開發人員寫程式碼時,必須同步更新文件。
為了強迫每個程式設計師在開發時記著寫文件,而又不影響大家寫程式碼這個動作本身, 我們不修改原來的Developer類,而是建立了一個新的類,同樣實現IDeveloper介面。這個新類DeveloperProxy內部維護了一個成員變數,指向原始的IDeveloper例項:
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(); } }
這個代理類實現的writeCode方法裡,在呼叫實際程式設計師writeCode方法之前,加上一個寫文件的呼叫,這樣就確保了程式設計師寫程式碼時都伴隨著文件更新。
測試程式碼:
靜態代理方式的優點
1. 易於理解和實現
2. 代理類和真實類的關係是編譯期靜態決定的,和下文馬上要介紹的動態代理比較起來,執行時沒有任何額外開銷。
靜態代理方式的缺點
每一個真實類都需要一個建立新的代理類。還是以上述文件更新為例,假設老闆對測試工程師也提出了新的要求,讓測試工程師每次測出bug時,也要及時更新對應的測試文件。那麼採用靜態代理的方式,測試工程師的實現類ITester也得建立一個對應的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(); } }
正是因為有了靜態程式碼方式的這個缺點,才誕生了Java的動態代理實現方式。
Java動態代理實現方式一:InvocationHandler
InvocationHandler的原理我曾經專門寫文章介紹過: Java動態代理之InvocationHandler最簡單的入門教程
透過InvocationHandler, 我可以用一個EnginnerProxy代理類來同時代理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; } }
真實類的writeCode和doTesting方法在動態代理類裡透過反射的方式進行執行。
測試輸出:
透過InvocationHandler實現動態代理的侷限性
假設有個產品經理類(ProductOwner) 沒有實現任何介面。
public class ProductOwner { private String name; public ProductOwner(String name){ this.name = name; } public void defineBackLog(){ System.out.println("PO: " + name + " defines Backlog."); } }
我們仍然採取EnginnerProxy代理類去代理它,編譯時不會出錯。執行時會發生什麼事?
ProductOwner po = new ProductOwner("Ross"); ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po); poProxy.defineBackLog();
執行時報錯。所以侷限性就是:如果被代理的類未實現任何介面,那麼不能採用透過InvocationHandler動態代理的方式去代理它的行為。
Java動態代理實現方式二:CGLIB
CGLIB是一個Java位元組碼生成庫,提供了易用的API對Java位元組碼進行建立和修改。關於這個開源庫的更多細節,請移步至CGLIB在github上的倉庫:
我們現在嘗試用CGLIB來代理之前採用InvocationHandler沒有成功代理的ProductOwner類(該類未實現任何介面)。
現在我改為使用CGLIB API來建立代理類:
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(); } }
測試程式碼:
ProductOwner ross = new ProductOwner("Ross"); ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross); rossProxy.defineBackLog();
儘管ProductOwner未實現任何程式碼,但它也成功被代理了:
用CGLIB實現Java動態代理的侷限性
如果我們瞭解了CGLIB建立代理類的原理,那麼其侷限性也就一目瞭然。我們現在做個實驗,將ProductOwner類加上final修飾符,使其不可被繼承:
再次執行測試程式碼,這次就報錯了: Cannot subclass final class XXXX。
所以透過CGLIB成功建立的動態代理,實際是被代理類的一個子類。那麼如果被代理類被標記成final,也就無法透過CGLIB去建立動態代理。
Java動態代理實現方式三:透過編譯期提供的API動態建立代理類
假設我們確實需要給一個既是final,又未實現任何介面的ProductOwner類建立動態程式碼。除了InvocationHandler和CGLIB外,我們還有最後一招:
我直接把一個代理類的原始碼用字串拼出來,然後基於這個字串呼叫JDK的Compiler(編譯期)API,動態的建立一個新的.java檔案,然後動態編譯這個.java檔案,這樣也能得到一個新的代理類。
測試成功:
我拼好了程式碼類的原始碼,動態建立了代理類的.java檔案,能夠在Eclipse裡開啟這個用程式碼建立的.java檔案,
下圖是如何動態建立ProductPwnerSCProxy.java檔案:
下圖是如何用JavaCompiler API動態編譯前一步動態建立出的.java檔案,生成.class檔案:
下圖是如何用類載入器載入編譯好的.class檔案到記憶體:
如果您想試試這篇文章介紹的這四種代理模式(Proxy Design Pattern), 請參考我的github倉庫,全部程式碼都在上面。感謝閱讀。
要獲取更多Jerry的原創技術文章,請關注公眾號"汪子熙"或者掃描下面二維碼:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24475491/viewspace-2213092/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- java靜態代理和動態代理Java
- 23種設計模式之代理模式(靜態代理)設計模式
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- Java設計模式學習06——靜態代理與動態代理Java設計模式
- Java中的靜態代理和動態代理Java
- 靜態代理和動態代理
- Java設計模式-之代理模式(動態代理)Java設計模式
- Java基礎系列-靜態代理和動態代理Java
- Java Proxy動態代理Java
- 代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理模式JDKCGLib
- Java靜態代理模式Java模式
- 設計模式總結——代理模式以及java的動態代理設計模式Java
- 輕鬆理解 Java 靜態代理/動態代理Java
- 面試常問的設計模式之代理模式的詳細解析!分析說明靜態代理模式和動態代理模式面試設計模式
- Java 靜態代理和動態代理的使用及原理解析Java
- java執行原理、靜態代理和動態代理區分Java
- Java代理之靜態代理Java
- 【JAVA】代理模式之Java動態代理Java模式
- 設計模式_JAVA動態代理設計模式設計模式Java
- JAVA學習篇--靜態代理VS動態代理Java
- 代理模式-靜態代理解讀模式
- 設計模式:動態代理設計模式
- 代理模式 - 動態代理模式
- 【設計模式】-代理模式及動態代理詳解設計模式
- 23種設計模式之——動態代理模式設計模式
- 靜態代理、動態代理與Mybatis的理解MyBatis
- AOP之靜態代理VS動態代理
- 設計模式學習筆記(七)代理模式以及動態代理的實現設計模式筆記
- Java靜態代理Java
- 靜態代理和動態代理(jdk/cglib)詳解JDKCGLib
- JavaScript代理模式,怎麼實現物件的動態代理?JavaScript模式物件
- 3.靜態代理&動態代理&CGlibCGLib
- Java動態程式設計---動態代理Java程式設計
- 如何實現Java 設定動態代理ip的具體操作步驟Java
- java動態代理——代理方法的假設和驗證及Proxy原始碼分析五Java原始碼
- Java設計模式之代理模式(Proxy)Java設計模式
- 淺談Java和SAP ABAP的靜態代理和動態代理,以及ABAP面向切面程式設計的嘗試Java程式設計
- 《Proxy系列專題》:代理模式(靜態、JDK、CGLib)模式JDKCGLib