Java代理設計模式(Proxy)的四種具體實現:靜態代理和動態代理

      網友投稿 720 2022-05-29

      面試問題: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; } }

      Java代理設計模式(Proxy)的四種具體實現:靜態代理和動態代理

      真實類的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上的倉庫:https://github.com/cglib/cglib

      我們現在嘗試用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倉庫,全部代碼都在上面。感謝閱讀。

      https://github.com/i042416/JavaTwoPlusTwoEquals5/tree/master/src/proxy

      要獲取更多Jerry的原創技術文章,請關注公眾號"汪子熙"。

      Java 架構設計

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:法律行業python教程——利用python批量制作律師函
      下一篇:Vuex狀態機快速了解與應用
      相關文章
      亚洲精品无AMM毛片| 亚洲国产成人久久精品影视 | 一本色道久久综合亚洲精品蜜桃冫| 中文字幕精品亚洲无线码一区| 亚洲国产综合AV在线观看| 亚洲美女视频免费| 亚洲桃色AV无码| 国产av无码专区亚洲av毛片搜| 亚洲成AV人片久久| 18gay台湾男同亚洲男同| 亚洲精品无码av人在线观看| 亚洲精品一级无码鲁丝片| 国产精品亚洲一区二区麻豆| 亚洲午夜成激人情在线影院| 亚洲av永久无码精品网站| 国产亚洲成av片在线观看| 国产国拍精品亚洲AV片| 亚洲国产午夜福利在线播放| 亚洲精品无码久久毛片| 亚洲 另类 无码 在线| 亚洲av永久无码| 亚洲色偷偷色噜噜狠狠99| 亚洲精品亚洲人成在线| 亚洲综合精品第一页| 亚洲中文字幕精品久久| 亚洲精品亚洲人成在线| 亚洲AV综合色区无码一二三区| 国产日本亚洲一区二区三区| ass亚洲**毛茸茸pics| 2020天堂在线亚洲精品专区| 久久精品国产亚洲AV蜜臀色欲| 亚洲三级在线播放| 亚洲日本视频在线观看| 亚洲免费中文字幕| 国产色在线|亚洲| 亚洲中文字幕久久久一区| 亚洲男人的天堂网站| 久久亚洲欧美国产精品| 妇女自拍偷自拍亚洲精品| 亚洲第一成人影院| 中文字幕亚洲无线码|