摘要:代理模式在我們?nèi)粘V泻艹R?,生活處處有代理看張學友的演唱會很難搶票,可以找黃牛排隊買嫌出去吃飯麻煩,可以叫外賣無論是黃牛外賣騎手都得幫我們干活。靜態(tài)代理我還是以找黃牛幫我排隊買張學友的演唱會門票的例子,寫個說明。
微信公眾號:一個優(yōu)秀的廢人。如有問題,請后臺留言,反正我也不會聽。
最近在復習 Java 相關,回顧了下代理模式。代理模式在 Java 領域很多地方都有應用,它分為靜態(tài)代理和動態(tài)代理,其中 Spring AOP 就是動態(tài)代理的典型例子。動態(tài)代理又分為接口代理和 cglib (子類代理),結合我的理解寫了幾個 demo 分享給你們,這是昨晚修仙到 3 點寫出來的文章,不點在看,我覺得說不過去了。
代理模式在我們?nèi)粘V泻艹R?,生活處處有代理?/p>
看張學友的演唱會很難搶票,可以找黃牛排隊買
嫌出去吃飯麻煩,可以叫外賣
無論是黃牛、外賣騎手都得幫我們干活。但是他們不能一手包辦(比如黃牛不能幫我吃飯),他們只能做我們不能或者不想做的事。
找黃??梢詭臀遗抨犢I上張學友的演唱會門票
外賣騎手可以幫我把飯送到樓下
所以,你看。代理模式其實就是當前對象不愿意做的事情,委托給別的對象做。
靜態(tài)代理我還是以找黃牛幫我排隊買張學友的演唱會門票的例子,寫個 demo 說明?,F(xiàn)在有一個 Human 接口,無論是我還是黃牛都實現(xiàn)了這個接口。
public interface Human { void eat(); void sleep(); void lookConcert(); }
例如,我這個類,我會吃飯和睡覺,如以下類:
public class Me implements Human{ @Override public void eat() { System.out.println("eat emat ...."); } @Override public void sleep() { System.out.println("Go to bed at one o"clock in the morning"); } @Override public void lookConcert() { System.out.println("Listen to Jacky Cheung"s Concert"); } }
有黃牛類,例如:
public class Me implements Human{ @Override public void eat() { } @Override public void sleep() { } @Override public void lookConcert() { } }
現(xiàn)在我和黃牛都已經(jīng)準備好了,怎么把這二者關聯(lián)起來呢?我們要明確的是黃牛是要幫我買票的,買票必然就需要幫我排隊,于是有以下黃牛類:注意這里我們不關心,黃牛的其他行為,我們只關心他能不能排隊買票。
public class HuangNiu implements Human{ private Me me; public HuangNiu() { me = new Me(); } @Override public void eat() { } @Override public void sleep() { } @Override public void lookConcert() { // 添加排隊買票方法 this.lineUp(); me.lookConcert(); } public void lineUp() { System.out.println("line up"); } }
最終的 main 方法調(diào)用如下:
public class Client { public static void main(String[] args) { Human human = new HuangNiu(); human.lookConcert(); } }
結果如下:
由此可見,黃牛就只是做了我們不愿意做的事(排隊買票),實際看演唱會的人還是我。客戶端也并不關心代理類代理了哪個類,因為代碼控制了客戶端對委托類的訪問。客戶端代碼表現(xiàn)為 Human human = new HuangNiu();
由于代理類實現(xiàn)了抽象角色的接口,導致代理類無法通用。比如,我的狗病了,想去看醫(yī)生,但是排隊掛號很麻煩,我也想有個黃牛幫我的排隊掛號看病,但是黃牛它不懂這只狗的特性(黃牛跟狗不是同一類型,黃牛屬于 Human 但狗屬于 Animal 類)但排隊掛號和排隊買票相對于黃牛來說它兩就是一件事,這個方法是不變的,現(xiàn)場排隊。那我們能不能找一個代理說既可以幫人排隊買票也可以幫狗排隊掛號呢?
答案肯定是可以的,可以用動態(tài)代理。
基于接口的動態(tài)代理如靜態(tài)代理的內(nèi)容所描述的,靜態(tài)代理受限于接口的實現(xiàn)。動態(tài)代理就是通過使用反射,動態(tài)地獲取抽象接口的類型,從而獲取相關特性進行代理。因動態(tài)代理能夠為所有的委托方進行代理,因此給代理類起個通用點的名字 HuangNiuHandle。先看黃牛類可以變成什么樣?
public class HuangNiuHandle implements InvocationHandler { private Object proxyTarget; public Object getProxyInstance(Object target) { this.proxyTarget = target; return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object methodObject = null; System.out.println("line up"); methodObject = method.invoke(proxyTarget, args); System.out.println("go home and sleep"); return methodObject; } }
這個時候的客戶端代碼就變成這樣了
public class Client { public static void main(String[] args) { HuangNiuHandle huangNiuHandle = new HuangNiuHandle(); Human human = (Human) huangNiuHandle.getProxyInstance(new Me()); human.eat(); human.run(); human.lookConcert(); System.out.println("------------------"); Animal animal = (Animal) huangNiuHandle.getProxyInstance(new Dog()); animal.eat(); animal.run(); animal.seeADoctor(); } }
使用動態(tài)代理有三個要點,
必須實現(xiàn) InvocationHandler 接口,表明該類是一個動態(tài)代理執(zhí)行類。
InvocationHandler 接口內(nèi)有一實現(xiàn)方法如下: public Object invoke(Object proxy, Method method, Object[] args) 。使用時需要重寫這個方法
獲取代理類,需要使用 Proxy.newProxyInstance(Clas loader, Class>[] interfaces, InvocationHandler h) 這個方法去獲取Proxy對象(Proxy 類類型的實例)。
注意到 Proxy.newProxyInstance 這個方法,它需要傳入 3 個參數(shù)。解析如下:
// 第一個參數(shù),是類的加載器 // 第二個參數(shù)是委托類的接口類型,證代理類返回的是同一個實現(xiàn)接口下的類型,保持代理類與抽象角色行為的一致 // 第三個參數(shù)就是代理類本身,即告訴代理類,代理類遇到某個委托類的方法時該調(diào)用哪個類下的invoke方法 Proxy.newProxyInstance(Class loader, Class>[] interfaces, InvocationHandler h)
再來看看 invoke 方法,用戶調(diào)用代理對象的什么方法,實質上都是在調(diào)用處理器的
invoke 方法,通過該方法調(diào)用目標方法,它也有三個參數(shù):
// 第一個參數(shù)為 Proxy 類類型實例,如匿名的 $proxy 實例 // 第二個參數(shù)為委托類的方法對象 // 第三個參數(shù)為委托類的方法參數(shù) // 返回類型為委托類某個方法的執(zhí)行結果 public Object invoke(Object proxy, Method method, Object[] args)
調(diào)用該代理類之后的輸出結果:
由結果可知,黃牛不僅幫了(代理)我排隊買票,還幫了(代理)我的狗排隊掛號。所以,你看靜態(tài)代理需要自己寫代理類(代理類需要實現(xiàn)與目標對象相同的接口),還需要一一實現(xiàn)接口方法,但動態(tài)代理不需要。
注意,我們并不是所有的方法都需要黃牛這個代理去排隊。我們知道只有我看演唱會和我的狗去看醫(yī)生時,才需要黃牛,如果要實現(xiàn)我們想要的方法上面添加特定的代理,可以通過 invoke 方法里面的方法反射獲取 method 對象方法名稱即可實現(xiàn),所以動態(tài)代理類可以變成這樣:
public class HuangNiuHandle implements InvocationHandler { private Object proxyTarget; public Object getProxyInstance(Object target) { this.proxyTarget = target; return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object methodObject = null; if ("lookConcert".equals(method.getName()) || "seeADoctor".equals(method.getName())) { System.out.println("line up"); // 調(diào)用目標方法 methodObject = method.invoke(proxyTarget, args); } else { // 不使用第一個proxy參數(shù)作為參數(shù),否則會造成死循環(huán) methodObject = method.invoke(proxyTarget, args); } return methodObject; } }
結果如下:可以看到我們只在特定方法求助了黃牛
由此可見,動態(tài)代理一般應用在記錄日志等橫向業(yè)務。
值得注意的是:
基于接口類的動態(tài)代理模式,必須具備抽象角色、委托類、代理三個基本角色。委托類和代理類必須由抽象角色衍生出來,否則無法使用該模式。
動態(tài)代理模式最后返回的是具有抽象角色(頂層接口)的對象。在委托類內(nèi)被 private 或者 protected 關鍵修飾的方法將不會予以調(diào)用,即使允許調(diào)用。也無法在客戶端使用代理類轉換成子類接口,對方法進行調(diào)用。也就是說上述的動態(tài)代理返回的是委托類(Me)或 (Dog)的就接口對象 (Human)或 (Animal)。
在 invoke 方法內(nèi)為什么不使用第一個參數(shù)進行執(zhí)行回調(diào)。在客戶端使用getProxyInstance(new Child( ))時,JDK 會返回一個 proxy 的實例,實例內(nèi)有InvokecationHandler 對象及動態(tài)繼承下來的目標 。客戶端調(diào)用了目標方法,有如下操作:首先 JDK 先查找 proxy 實例內(nèi)的 handler 對象 然后執(zhí)行 handler 內(nèi)的 invoke 方法。
根據(jù) public Object invoke 這個方法第一個參數(shù) proxy 就是對應著 proxy 實例。如果在 invoke 內(nèi)使用 method.invoke(proxy,args) ,會出現(xiàn)這樣一條方法鏈,目標方法→invoke→目標方法→invoke...,最終導致堆棧溢出。
基于子類的動態(tài)代理為了省事,我這里并沒有繼承父類,但在實際開發(fā)中是需要繼承父類才比較方便擴展的。與基于接口實現(xiàn)類不同的是:
CGLib (基于子類的動態(tài)代理)使用的是方法攔截器 MethodInterceptor ,需要導入 cglib.jar 和 asm.jar 包
基于子類的動態(tài)代理,返回的是子類對象
方法攔截器對 protected 修飾的方法可以進行調(diào)用
代碼如下:
public class Me { public void eat() { System.out.println("eat meat ...."); } public void run() { System.out.println("I run with two legs"); } public void lookConcert() { System.out.println("Listen to Jacky Cheung"s Concert"); } protected void sleep() { System.out.println("Go to bed at one o"clock in the morning"); } }
Dog 類
public class Dog { public void eat() { System.out.println("eat Dog food ...."); } public void run() { System.out.println("Dog running with four legs"); } public void seeADoctor() { System.out.println("The dog go to the hospital"); } }
黃牛代理類,注意 invoke() 這里多了一個參數(shù) methodProxy ,它的作用是用于執(zhí)行目標(委托類)的方法,至于為什么用 methodProxy ,官方的解釋是速度快且在intercep t內(nèi)調(diào)用委托類方法時不用保存委托對象引用。
public class HuangNiuHandle implements MethodInterceptor { private Object proxyTarget; public Object getProxyInstance(Object target) { this.proxyTarget = target; return Enhancer.create(target.getClass(), target.getClass().getInterfaces(), this); } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object methodObject = null; if ("lookConcert".equals(method.getName()) || "seeADoctor".equals(method.getName())) { System.out.println("line up"); // 調(diào)用目標方法 methodObject = methodProxy.invokeSuper(proxy, args); } else { methodObject = method.invoke(proxyTarget, args); } return methodObject; } }
client 類
public class Client { public static void main(String[] args) { HuangNiuHandle huangNiuHandle = new HuangNiuHandle(); Me me = (Me) huangNiuHandle.getProxyInstance(new Me()); me.eat(); me.run(); me.sleep(); me.lookConcert(); System.out.println("------------------"); Dog dog = (Dog) huangNiuHandle.getProxyInstance(new Dog()); dog.eat(); dog.run(); dog.seeADoctor(); } }
結果:
注意到 Me 類中被 protected 修飾的方法 sleep 仍然可以被客戶端調(diào)用。這在基于接口的動態(tài)代理中是不被允許的。
靜態(tài)代理與動態(tài)代理的區(qū)別靜態(tài)代理需要自己寫代理類并一一實現(xiàn)目標方法,且代理類必須實現(xiàn)與目標對象相同的接口。
動態(tài)代理不需要自己實現(xiàn)代理類,它是利用 JDKAPI,動態(tài)地在內(nèi)存中構建代理對象(需要我們傳入被代理類),并且默認實現(xiàn)所有目標方法。
源碼下載:https://github.com/turoDog/re...
后語如果本文對你哪怕有一丁點幫助,請幫忙點好看,你的好看是我堅持寫作的動力。關注公眾號一個優(yōu)秀的廢人回復 1024 獲取資料:Python、C++、Java、Linux、Go、前端、算法資料分享
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/77643.html
摘要:動態(tài)地代理,可以猜測一下它的含義,在運行時動態(tài)地對某些東西代理,代理它做了其他事情。所以動態(tài)代理的內(nèi)容重點就是這個。所以下一篇我們來細致了解下的到底是怎么使用動態(tài)代理的。 之前講了《零基礎帶你看Spring源碼——IOC控制反轉》,本來打算下一篇講講Srping的AOP的,但是其中會涉及到Java的動態(tài)代理,所以先單獨一篇來了解下Java的動態(tài)代理到底是什么,Java是怎么實現(xiàn)它的。 ...
摘要:受知乎文章和設計模式之禪的啟發(fā),我也來搞一篇腦洞小開的文章由標題可知,這篇文章是寫給我女朋友看的。于是這就讓經(jīng)紀人對粉絲說只有萬,我才會寫代碼。 前言 只有光頭才能變強 回顧前面: ThreadLocal就是這么簡單 多線程三分鐘就可以入個門了! 多線程基礎必要知識點!看了學習多線程事半功倍 Java鎖機制了解一下 AQS簡簡單單過一遍 Lock鎖子類了解一下 線程池你真不來了解一下...
摘要:這種語法,在中被稱為動態(tài)代理。在動態(tài)代理機制中,這個角色只能是接口。動態(tài)代理就是實現(xiàn)的技術之一。 所謂動態(tài)代理,指的是語言提供的一種語法,能夠將對對象中不同方法的調(diào)用重定向到一個統(tǒng)一的處理函數(shù)中來。python重寫__getattr__函數(shù)能夠做到這一點,就連世界上最好的語言也提供稱為魔術方法的__call。這種語法除了能更好的實現(xiàn)動態(tài)代理外,還是RPC框架實現(xiàn)原理的一部分。 動態(tài)代理...
摘要:與靜態(tài)代理對比,動態(tài)代理是在動態(tài)生成代理類,由代理類完成對具體方法的封裝,實現(xiàn)的功能。本文將分析中兩種動態(tài)代理的實現(xiàn)方式,和,比較它們的異同。那如何動態(tài)編譯呢你可以使用,這是一個封裝了的庫,幫助你方便地實現(xiàn)動態(tài)編譯源代碼。 發(fā)現(xiàn)Java面試很喜歡問Spring AOP怎么實現(xiàn)的之類的問題,所以寫一篇文章來整理一下。關于AOP和代理模式的概念這里并不做贅述,而是直奔主題,即AOP的實現(xiàn)方...
摘要:代理模式基本概念不論是靜態(tài)代理還是動態(tài)代理其本質都是代理模式的一種實現(xiàn)那么什么是代理模式呢代理模式即給某一個對象提供一個代理并由代理對象控制對原對象的引用代理模式其實取材于實際生活例如我們生活中常見的房屋租賃代理我們在租房時一般不是直接和房 代理模式 基本概念 不論是靜態(tài)代理還是動態(tài)代理, 其本質都是代理模式的一種實現(xiàn), 那么什么是代理模式呢?代理模式, 即給某一個對象提供一個代理, ...
摘要:動態(tài)代理深度解析引言說起動態(tài)代理,很多人可能都沒有直接去使用過。因為的動態(tài)代理只能代理接口,而不能代理原始的類。接下來是真正壓軸的環(huán)節(jié),實現(xiàn)自己的動態(tài)代理類。 Java動態(tài)代理深度解析 引言 說起動態(tài)代理,很多人可能都沒有直接去使用過。但是只要用過Spring,那動態(tài)代理就是一個是個繞不過的坎,因為Spring的核心特性之一AOP就是基于動態(tài)代理來實現(xiàn)的,那么什么情況下需要用到動態(tài)代理...
閱讀 3389·2021-11-22 09:34
閱讀 663·2021-11-19 11:29
閱讀 1366·2019-08-30 15:43
閱讀 2245·2019-08-30 14:24
閱讀 1879·2019-08-29 17:31
閱讀 1237·2019-08-29 17:17
閱讀 2625·2019-08-29 15:38
閱讀 2743·2019-08-26 12:10