代理模式详解(静态代理和动态代理的区别以及联系)
1. 前言
代理模式可以說是生活中處處可見。比如說在攜程上定火車票,攜程在這里就起到了一個(gè)代理的作用,比起我們在官網(wǎng)上或者直接去柜臺訂票,攜程可以為用戶提供更多人性化的選擇。再比如代購,我自己的mbp就是委托別人從香港買回來的,那么那個(gè)代購人就相當(dāng)于代理,免去了我來回的車費(fèi)以及辦簽證的麻煩。直觀的理解,代理是這么一個(gè)對象,我們可以把工作委托給它,由它幫我們?nèi)?zhí)行工作同時(shí)解決工作相關(guān)的各種麻煩,最后把工作成果交給我們。這樣一來我們只需要關(guān)注問題的核心,并告知代理,而不需要為其他瑣碎的細(xì)節(jié)操心,這正是代理模式最大的好處,讓客戶端專注于真正的事務(wù)處理。具體而言,代理模式分為靜態(tài)代理和動(dòng)態(tài)代理,它們的設(shè)計(jì)思想類似,實(shí)現(xiàn)卻大相徑庭。它們的實(shí)現(xiàn)方式以及它們的區(qū)別是面試時(shí)經(jīng)常會(huì)被問到的。下面我們就來詳細(xì)介紹它們。
2. 代理模式詳解
2.1 定義
為另一個(gè)對象提供一個(gè)替身或占位符以控制對這個(gè)對象的訪問
定義簡單明了。但還是有些沒有解釋清楚的地方,控制對對象的訪問是為了干什么?其實(shí)是為了對真實(shí)的業(yè)務(wù)方法作某種形式的增強(qiáng),比如在業(yè)務(wù)方法調(diào)用前作前置處理,在方法調(diào)用后作后置處理,而這些對客戶端都是透明的。
2.2 普通代理模式類結(jié)構(gòu)
被代理類和代理類實(shí)現(xiàn)同一接口,根據(jù)面向接口編程原則,可以使用被代理類的地方,統(tǒng)統(tǒng)可以用代理類代替,同時(shí)類內(nèi)部持有被代理類的引用,真正的業(yè)務(wù)處理邏輯可以交給被代理類去作。
2.3 普通代理模式的實(shí)現(xiàn)(靜態(tài)代理)
假設(shè)我要寫一個(gè)日志代理,在真實(shí)的業(yè)務(wù)方法調(diào)用前后各記一條日志,看看靜態(tài)代理是怎么做的。
- 業(yè)務(wù)接口
接口含有兩個(gè)抽象業(yè)務(wù)方法
- 被代理類
被代理類實(shí)現(xiàn)業(yè)務(wù)接口,并且重寫了業(yè)務(wù)方法。
- 日志代理
- 運(yùn)行結(jié)果
如上圖所示,在業(yè)務(wù)方法執(zhí)行前后分別記了一條日志,表示業(yè)務(wù)方法執(zhí)行的開始和結(jié)束。我們的代理類成功對原有業(yè)務(wù)方法做了增強(qiáng)。但是靜態(tài)代理存在以下問題:
1.代理類和被代理類耦合,適用性差。試想如果我希望為所有的業(yè)務(wù)類添加日志增強(qiáng)邏輯,那么豈不是要為幾乎每個(gè)業(yè)務(wù)類編寫代理類?這是不現(xiàn)實(shí)也是開發(fā)時(shí)無法接受的。
2.代理類的增強(qiáng)邏輯和業(yè)務(wù)邏輯過于耦合,不利于后期維護(hù)和擴(kuò)展。從service1和service2中就可以看出,除了中間的業(yè)務(wù)處理不一樣,代理類的處理邏輯是一樣的,而我們竟然沒有將其分離。
以上兩點(diǎn)其實(shí)反映的是同一個(gè)問題:我們希望自己編寫的代理類對所有業(yè)務(wù)類,所有業(yè)務(wù)方法都適用,而靜態(tài)代理的泛用性太差了。問題的關(guān)鍵在于編寫代理邏輯和業(yè)務(wù)邏輯分離的代理類,運(yùn)行時(shí)才將其和具體的業(yè)務(wù)類綁定,對其業(yè)務(wù)方法做增強(qiáng)。為此,我們需要?jiǎng)討B(tài)代理。動(dòng)態(tài)代理的實(shí)現(xiàn)方式很多,下面以jdk自帶的代理方式做說明。
3. JDK動(dòng)態(tài)代理詳解
3.1 JDK動(dòng)態(tài)代理實(shí)現(xiàn)
- 方法調(diào)用處理器
每一個(gè)代理對象都有一個(gè)與之關(guān)聯(lián)的方法調(diào)用處理器,該處理器實(shí)現(xiàn)了InvocationHandler接口并重寫了invoke方法。當(dāng)我們調(diào)用代理對象的方法的時(shí)候,對該方法的調(diào)用會(huì)轉(zhuǎn)交給方法調(diào)用處理器的invoke方法來執(zhí)行,所以方法調(diào)用處理器的invoke方法是動(dòng)態(tài)代理的核心。該方法內(nèi)是通用的代理邏輯。在我們通過反射的方式通過被代理對象target執(zhí)行業(yè)務(wù)邏輯的前后,可以對其作前置和后置增強(qiáng)。
- 客戶端代碼
第一步我們創(chuàng)建了被代理對象realService;第二步我們創(chuàng)建了動(dòng)態(tài)代理的核心:方法調(diào)用處理器。因?yàn)樘幚砥鲀?nèi)部需要委托被代理對象去執(zhí)行真正的業(yè)務(wù)方法,所以需要傳入被代理對象作參數(shù)。第三步我們通過調(diào)用反射包下的Proxy類的靜態(tài)方法去生成真正的代理對象。該方法的方法簽名如下
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException該方法含有三個(gè)參數(shù)
ClassLoader loader:指定加載的代理類的類加載器
Class<?>[] interfaces:指定代理類要實(shí)現(xiàn)的接口
InvocationHandler h:指定方法調(diào)用處理器
關(guān)于第二個(gè)參數(shù)可能要說明下,因?yàn)閖dk的動(dòng)態(tài)代理是針對接口的動(dòng)態(tài)代理,代理類需要實(shí)現(xiàn)指定的業(yè)務(wù)接口,這里就是被代理類實(shí)現(xiàn)的那些接口。這樣就能充分利用java多態(tài)特性,代碼中所有能使用被代理對象的地方都能用代理對象進(jìn)行替換。
- 運(yùn)行結(jié)果
和靜態(tài)代理的運(yùn)行結(jié)果一樣。如果我們要對其他業(yè)務(wù)類使用日志代理,只需要修改下客戶端代碼就行,這是靜態(tài)代理辦不到的。具有良好的擴(kuò)展性是動(dòng)態(tài)代理相比靜態(tài)代理最大的優(yōu)勢。
3.2 方法調(diào)用流程圖
- 1.客戶端調(diào)用代理對象的業(yè)務(wù)方法,創(chuàng)建代理對象的時(shí)候傳入了接口數(shù)組參數(shù),故而代理對象也實(shí)現(xiàn)了業(yè)務(wù)接口。
- 2.代理對象將請求轉(zhuǎn)發(fā)給方法調(diào)用處理器的invoke方法
- 3.方法調(diào)用處理器在invoke方法內(nèi)部通過反射的方式調(diào)用被代理對象的業(yè)務(wù)方法
3.3 動(dòng)態(tài)代理和靜態(tài)代理的區(qū)別
- 靜態(tài)代理編譯期生成代理類;動(dòng)態(tài)代理運(yùn)行期生成代理類。
- 靜態(tài)代理和被代理類及其業(yè)務(wù)邏輯耦合,適用性較差且代理邏輯難以擴(kuò)展;動(dòng)態(tài)代理可以在不知道被代理類的前提下編寫代理邏輯,運(yùn)行時(shí)才決定被代理對象,適用性好且代理邏輯易于擴(kuò)展。
3.4 其他實(shí)現(xiàn)動(dòng)態(tài)代理的方式
- cglib面向類的動(dòng)態(tài)代理
- javaassist字節(jié)碼操作庫實(shí)現(xiàn)
- asm
4. 總結(jié)
代理用以控制對對象的訪問,本質(zhì)上是對其功能提供某種形式的增強(qiáng)。按實(shí)現(xiàn)又可分為靜態(tài)代理和動(dòng)態(tài)代理。動(dòng)態(tài)代理因其代理邏輯和業(yè)務(wù)邏輯相分離的特點(diǎn),具有良好的適用性和可擴(kuò)展性,是Spring中AOP的底層實(shí)現(xiàn)。
轉(zhuǎn)載于:https://www.cnblogs.com/LoveShare/p/10796966.html
總結(jié)
以上是生活随笔為你收集整理的代理模式详解(静态代理和动态代理的区别以及联系)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用sphinx快速为你python注释
- 下一篇: 为什么我的对象被 IntelliJ ID