代理模式解析,靜態(tài)代理、動(dòng)態(tài)代理一文全都告訴你
Proxy Pattern
經(jīng)過(guò)兩個(gè)月的學(xué)習(xí),我們的設(shè)計(jì)模式系列學(xué)習(xí)也將近到了尾聲。不過(guò)設(shè)計(jì)模式這東西一時(shí)半會(huì)肯定是掌握不下來(lái)的,需要我們?cè)诤罄m(xù)的工作中持續(xù)的去練習(xí)。有了這方面的思維,剩下的就是靠自己刻意訓(xùn)練了。
今天帶來(lái)的是代理模式,Proxy Pattern。這個(gè)模式想必大家都不陌生,因?yàn)?Java 程序員面試一般都會(huì)問(wèn)的 Spring Aop 經(jīng)過(guò)會(huì)說(shuō)到動(dòng)態(tài)代理,這其實(shí)就是代理模式的一種實(shí)現(xiàn)。不過(guò)大部分人可能都沒(méi)有系統(tǒng)的去了解過(guò),本文的目的就是徹底搞清楚代理模式的小99。
閱讀之前也思考關(guān)于代理模式的幾個(gè)問(wèn)題,帶著搞清楚問(wèn)題的目的去學(xué)習(xí)。
帶著問(wèn)題出發(fā)
什么是代理模式? 靜態(tài)代理? 動(dòng)態(tài)代理? 常見(jiàn)的代理模式變種?
代理模式的介紹
代理模式: 為一個(gè)對(duì)象提供一個(gè)替身或者占位符,以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。即通過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象。這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能。不過(guò)這里的側(cè)重點(diǎn)還是在于對(duì) “對(duì)象“ 訪問(wèn)的控制。
被代理的對(duì)象可以是:遠(yuǎn)程對(duì)象、創(chuàng)建開(kāi)銷大的對(duì)象或者需要安全控制的對(duì)象; 代理模式有不同的形式,主要有三種:靜態(tài)代理、動(dòng)態(tài)代理(JDK和Cglib);
代理模式類圖

靜態(tài)代理
靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象(目標(biāo)對(duì)象)與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者繼承相同的父類
應(yīng)用實(shí)例
具體要求
定義一個(gè)接口:ITeacherDao 目前對(duì)象 TeacherDAO 實(shí)現(xiàn) ITeacherDao 使用靜態(tài)代理就需要在 TeacherDAOProxy 中也實(shí)現(xiàn) ITeacherDao 接口 調(diào)用的時(shí)候通過(guò)調(diào)用代理對(duì)象的方法來(lái)調(diào)用目標(biāo)對(duì)象 特別注意:代理對(duì)象要與目標(biāo)對(duì)象實(shí)現(xiàn)相同的接口,然后通過(guò)調(diào)用相同的方法來(lái)調(diào)用目標(biāo)對(duì)象的方法
思路分析圖解(類圖)

代碼實(shí)現(xiàn)
public?class?Client?{
?public?static?void?main(String[]?args)?{
??//?TODO?Auto-generated?method?stub
??//創(chuàng)建目標(biāo)對(duì)象(被代理對(duì)象)
??TeacherDao?teacherDao?=?new?TeacherDao();
??
??//創(chuàng)建代理對(duì)象,?同時(shí)將被代理對(duì)象傳遞給代理對(duì)象
??TeacherDaoProxy?teacherDaoProxy?=?new?TeacherDaoProxy(teacherDao);
??
??//通過(guò)代理對(duì)象,調(diào)用到被代理對(duì)象的方法
??//即:執(zhí)行的是代理對(duì)象的方法,代理對(duì)象再去調(diào)用目標(biāo)對(duì)象的方法?
??teacherDaoProxy.teach();
?}
}
//接口
public?interface?ITeacherDao?{
?
?void?teach();?//?授課的方法
}
public?class?TeacherDao?implements?ITeacherDao?{
?@Override
?public?void?teach()?{
??//?TODO?Auto-generated?method?stub
??System.out.println("?老師授課中??。。。。。");
?}
}
//代理對(duì)象,靜態(tài)代理
public?class?TeacherDaoProxy?implements?ITeacherDao{
?
?private?ITeacherDao?target;?//?目標(biāo)對(duì)象,通過(guò)接口來(lái)聚合
?
?//構(gòu)造器
?public?TeacherDaoProxy(ITeacherDao?target)?{
??this.target?=?target;
?}
?@Override
?public?void?teach()?{
??//?TODO?Auto-generated?method?stub
??System.out.println("開(kāi)始代理??完成某些操作。。。。。?");//方法
??target.teach();
??System.out.println("提交。。。。。");//方法
?}
}
靜態(tài)代理優(yōu)缺點(diǎn)
優(yōu)點(diǎn):再不改變目標(biāo)對(duì)象功能的前提下,能通過(guò)代理對(duì)象對(duì)目標(biāo)功能擴(kuò)展 缺點(diǎn):很明顯,因?yàn)榇韺?duì)象要和目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類 一旦接口新增方法,目標(biāo)對(duì)象和代理對(duì)象都要維護(hù)
動(dòng)態(tài)代理
動(dòng)態(tài)代理這塊分為 JDK 動(dòng)態(tài)代理和 Cglib 動(dòng)態(tài)代理,我們先來(lái)看看 JDK 動(dòng)態(tài)代理是怎么玩的。
JDK 動(dòng)態(tài)代理
代理對(duì)象,不需要實(shí)現(xiàn)接口,但是目標(biāo)對(duì)象要實(shí)現(xiàn)接口,否則不能用 JDK動(dòng)態(tài)代理 代理對(duì)象的生成是利用 JDK 的 API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象
JDK 中生成代理對(duì)象的API
代理類所在的包: java.lang.reflect.ProxyJDK 實(shí)現(xiàn)代理只需要使用 newProxyInstance方法,但是該方法需要接收三個(gè)參數(shù),完整的寫(xiě)法是:static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
JDK 動(dòng)態(tài)代理應(yīng)用實(shí)例
這里我們來(lái)將上面的例子使用 JDK 動(dòng)態(tài)代理的方式 改進(jìn)成代理動(dòng)態(tài)。
思路圖解(類圖)

代碼實(shí)現(xiàn)
public?class?Client?{
?public?static?void?main(String[]?args)?{
??//?TODO?Auto-generated?method?stub
??//創(chuàng)建目標(biāo)對(duì)象
??ITeacherDao?target?=?new?TeacherDao();
??
??//給目標(biāo)對(duì)象,創(chuàng)建代理對(duì)象,?可以轉(zhuǎn)成?ITeacherDao
??ITeacherDao?proxyInstance?=?(ITeacherDao)new?ProxyFactory(target).getProxyInstance();
?
??//?proxyInstance=class?com.sun.proxy.$Proxy0?內(nèi)存中動(dòng)態(tài)生成了代理對(duì)象
??System.out.println("proxyInstance="?+?proxyInstance.getClass());
??
??//通過(guò)代理對(duì)象,調(diào)用目標(biāo)對(duì)象的方法
??//proxyInstance.teach();
??
??proxyInstance.sayHello("?tom?");
?}
}
//接口
public?interface?ITeacherDao?{
?void?teach();?//?授課方法
?void?sayHello(String?name);
}
public?class?ProxyFactory?{
?//維護(hù)一個(gè)目標(biāo)對(duì)象?,?Object
?private?Object?target;
?//構(gòu)造器?,?對(duì)target?進(jìn)行初始化
?public?ProxyFactory(Object?target)?{
??
??this.target?=?target;
?}?
?
?//給目標(biāo)對(duì)象?生成一個(gè)代理對(duì)象
?public?Object?getProxyInstance()?{
??
??//說(shuō)明
??/*
???*??public?static?Object?newProxyInstance(ClassLoader?loader,
??????????????????????????????????????????Class>[]?interfaces,
??????????????????????????????????????????InvocationHandler?h)
??????????????????????????????????????????
????????????//1. ClassLoader loader :?指定當(dāng)前目標(biāo)對(duì)象使用的類加載器, 獲取加載器的方法固定
????????????//2.?Class>[]?interfaces:?目標(biāo)對(duì)象實(shí)現(xiàn)的接口類型,使用泛型方法確認(rèn)類型
????????????//3.?InvocationHandler?h?:?事情處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事情處理器方法,?會(huì)把當(dāng)前執(zhí)行的目標(biāo)對(duì)象方法作為參數(shù)傳入
???*/
??return?Proxy.newProxyInstance(target.getClass().getClassLoader(),?
????target.getClass().getInterfaces(),?
????new?InvocationHandler()?{
?????
?????@Override
?????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
??????//?TODO?Auto-generated?method?stub
??????System.out.println("JDK代理開(kāi)始~~");
??????//反射機(jī)制調(diào)用目標(biāo)對(duì)象的方法
??????Object?returnVal?=?method.invoke(target,?args);
??????System.out.println("JDK代理提交");
??????return?returnVal;
?????}
????});?
?}
?
}
public?class?TeacherDao?implements?ITeacherDao?{
?@Override
?public?void?teach()?{
??//?TODO?Auto-generated?method?stub
??System.out.println("?老師授課中....?");
?}
?@Override
?public?void?sayHello(String?name)?{
??//?TODO?Auto-generated?method?stub
??System.out.println("hello?"?+?name);
?}
?
}
代碼也很簡(jiǎn)單,如果你對(duì)這里不熟悉的話,一定要寫(xiě)下上面的代碼感受下。
Cglib動(dòng)態(tài)代理
靜態(tài)代理和 JDK 動(dòng)態(tài)代理都要求目標(biāo)對(duì)象實(shí)現(xiàn)一個(gè)接口,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)對(duì)象,并沒(méi)有實(shí)現(xiàn)任何接口,這種情況就可以使用目標(biāo)對(duì)象的子類來(lái)實(shí)現(xiàn)代理,這就是 Cglib代理。
Cglib 代理也叫子類代理,它是通過(guò)在內(nèi)存中構(gòu)建一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象的擴(kuò)展。
Cglib 是一個(gè)強(qiáng)大的高性能的代碼生成包,可以在運(yùn)行期擴(kuò)展 Java 類與實(shí)現(xiàn) Java 接口,它廣泛的被許多 AOP 框架使用,例如 Spring AOP,實(shí)現(xiàn)方法攔截。
在 AOP 編程中如何選擇使用哪種動(dòng)態(tài)代理呢?
目標(biāo)對(duì)象實(shí)現(xiàn)接口,利用JDK動(dòng)態(tài)代理 目標(biāo)對(duì)象不需要實(shí)現(xiàn)接口,用Cglib實(shí)現(xiàn)代理 Cglib 包底層是通過(guò)字節(jié)碼處理框架 ASM 來(lái)轉(zhuǎn)換字節(jié)并生成新的類
Cglib 動(dòng)態(tài)代理實(shí)現(xiàn)步驟
需要引入 cglib 的 jar 包

在內(nèi)存中動(dòng)態(tài)創(chuàng)建子類,因此需要代理的類(目標(biāo)對(duì)象)不能為final,否則報(bào)錯(cuò):

如果被代理的對(duì)象的方法是 final/static 的,那么目標(biāo)方法將不會(huì)被攔截,不會(huì)執(zhí)行目標(biāo)對(duì)象方法額外的業(yè)務(wù)處理,因?yàn)?final 和 static 修飾的方法 是無(wú)法被子類擴(kuò)展的
應(yīng)用示例
要求:將前面的例子用 Cglib 代理模式實(shí)現(xiàn)
思路圖解(類圖)

代碼實(shí)現(xiàn)
public?class?Client?{
?public?static?void?main(String[]?args)?{
??//?TODO?Auto-generated?method?stub
??//創(chuàng)建目標(biāo)對(duì)象
??TeacherDao?target?=?new?TeacherDao();
??//獲取到代理對(duì)象,并且將目標(biāo)對(duì)象傳遞給代理對(duì)象
??TeacherDao?proxyInstance?=?(TeacherDao)new?ProxyFactory(target).getProxyInstance();
??//執(zhí)行代理對(duì)象的方法,觸發(fā)intecept?方法,從而實(shí)現(xiàn)?對(duì)目標(biāo)對(duì)象的調(diào)用
??String?res?=?proxyInstance.teach();
??System.out.println("res="?+?res);
?}
}
public?class?ProxyFactory?implements?MethodInterceptor?{
?//維護(hù)一個(gè)目標(biāo)對(duì)象
?private?Object?target;
?
?//構(gòu)造器,傳入一個(gè)被代理的對(duì)象
?public?ProxyFactory(Object?target)?{
??this.target?=?target;
?}
?//返回一個(gè)代理對(duì)象:??是?target?對(duì)象的代理對(duì)象
?public?Object?getProxyInstance()?{
??//1.?創(chuàng)建一個(gè)工具類
??Enhancer?enhancer?=?new?Enhancer();
??//2.?設(shè)置父類
??enhancer.setSuperclass(target.getClass());
??//3.?設(shè)置回調(diào)函數(shù)
??enhancer.setCallback(this);
??//4.?創(chuàng)建子類對(duì)象,即代理對(duì)象
??return?enhancer.create();
??
?}
?//重寫(xiě)??intercept?方法,會(huì)調(diào)用目標(biāo)對(duì)象的方法
?@Override
?public?Object?intercept(Object?arg0,?Method?method,?Object[]?args,?MethodProxy?arg3)?throws?Throwable?{
??//?TODO?Auto-generated?method?stub
??System.out.println("Cglib代理模式?~~?開(kāi)始");
??Object?returnVal?=?method.invoke(target,?args);
??System.out.println("Cglib代理模式?~~?提交");
??return?returnVal;
?}
}
public?class?TeacherDao?{
?public?String?teach()?{
??System.out.println("?老師授課中??,?我是cglib代理,不需要實(shí)現(xiàn)接口?");
??return?"hello";
?}
}
幾種常見(jiàn)代理模式的變體
防火墻代理
內(nèi)網(wǎng)通過(guò)代理實(shí)現(xiàn)對(duì)防火墻的穿透,實(shí)現(xiàn)對(duì)公網(wǎng)的訪問(wèn) 緩存代理
當(dāng)請(qǐng)求圖片等文件時(shí),先到緩存代理取,如果可以取到資源就皆大歡喜,如果取不到,再到公網(wǎng)或者數(shù)據(jù)庫(kù)取,然后緩存 遠(yuǎn)程代理
遠(yuǎn)程對(duì)象的本地代理,通過(guò)它可以把遠(yuǎn)程對(duì)象當(dāng)本地對(duì)象調(diào)用。遠(yuǎn)程代理對(duì)象通過(guò)網(wǎng)絡(luò)協(xié)議和真正的遠(yuǎn)程對(duì)象通信
總結(jié)
本文通過(guò)類圖和代碼示例,對(duì)代理模式做了詳細(xì)的講解,旨在讓你掌握,代理模式的定義以及面試常問(wèn)的靜態(tài)代理、代理代理、以及 JDK 動(dòng)態(tài)代理和 Cglib 動(dòng)態(tài)代理的區(qū)別以及實(shí)現(xiàn)方式。
全文代碼,均為筆者本地跑通過(guò)的,可以直接用來(lái)測(cè)試使用。
全文完,fighting!
4.中間件等
更多信息請(qǐng)關(guān)注公眾號(hào):「軟件老王」,關(guān)注不迷路,軟件老王和他的IT朋友們,分享一些他們的技術(shù)見(jiàn)解和生活故事。
