版權(quán)申明】非商業(yè)目的附文章鏈接可自由轉(zhuǎn)載
博文地址:http://www.reibang.com/p/d7aeded8ace0
出自:shusheng007
概述
什么是代理模式?解決什么問題(即為什么需要)汉矿?什么是靜態(tài)代理蔬墩?什么是動態(tài)代理模式恩闻?二者什么關(guān)系秩霍?具體如何實(shí)現(xiàn)肢簿?什么原理唐含?如何改進(jìn)岛请?這即為我們學(xué)習(xí)一項新知識的正確打開方式,我們接下來會以此展開,讓你秒懂溅话。
概念
什么是代理模式
定義:為其他對象提供一種代理以控制對這個對象的訪問
定義總是抽象而晦澀難懂的晓锻,讓我們回到生活中來吧。
實(shí)例:王二狗公司(天津在線回聲科技發(fā)展有限公司)老板突然在發(fā)工資的前一天帶著小姨子跑路了飞几,可憐二狗一身房貸砚哆,被迫提起勞動仲裁,勞動局就會為其指派一位代理律師全權(quán)負(fù)責(zé)二狗的仲裁事宜屑墨。那這里面就是使用了代理模式躁锁,因為在勞動仲裁這個活動中,代理律師會全權(quán)代理王二狗卵史。
解決什么問題
下面是一些使用場景战转,不過太抽象,暫時可以不要在意以躯,隨著你的不斷進(jìn)步你終究會明白的槐秧。
- 遠(yuǎn)程代理 :為位于兩個不同地址空間對象的訪問提供了一種實(shí)現(xiàn)機(jī)制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機(jī)上寸潦,提高系統(tǒng)的整體運(yùn)行效率。
- 虛擬代理:通過一個消耗資源較少的對象來代表一個消耗資源較多的對象社痛,可以在一定程度上節(jié)省系統(tǒng)的運(yùn)行開銷见转。
- 緩沖代理:為某一個操作的結(jié)果提供臨時的緩存存儲空間,以便在后續(xù)使用中能夠共享這些結(jié)果蒜哀,優(yōu)化系統(tǒng)性能斩箫,縮短執(zhí)行時間。
- 保護(hù)代理:可以控制對一個對象的訪問權(quán)限撵儿,為不同用戶提供不同級別的使用權(quán)限乘客。
- 智能引用:要為一個對象的訪問(引用)提供一些額外的操作時可以使用
什么是靜態(tài)代理
靜態(tài)代理是指預(yù)先確定了代理與被代理者的關(guān)系,例如王二狗的代理律師方文鏡是在開庭前就確定的了淀歇。那映射到編程領(lǐng)域的話易核,就是指代理類與被代理類的依賴關(guān)系在編譯期間就確定了。下面就是王二狗勞動仲裁的代碼實(shí)現(xiàn):
首先定義一個代表訴訟的接口
public interface ILawSuit {
void submit(String proof);//提起訴訟
void defend();//法庭辯護(hù)
}
王二狗訴訟類型浪默,實(shí)現(xiàn)ILawSuit
接口
public class SecondDogWang implements ILawSuit {
@Override
public void submit(String proof) {
System.out.println(String.format("老板欠薪跑路牡直,證據(jù)如下:%s",proof));
}
@Override
public void defend() {
System.out.println(String.format("鐵證如山,%s還錢","馬旭"));
}
}
代理律師訴訟類纳决,實(shí)現(xiàn)ILawSuit
接口
public class ProxyLawyer implements ILawSuit {
ILawSuit plaintiff;//持有要代理的那個對象
public ProxyLawyer(ILawSuit plaintiff) {
this.plaintiff=plaintiff;
}
@Override
public void submit(String proof) {
plaintiff.submit(proof);
}
@Override
public void defend() {
plaintiff.defend();
}
}
產(chǎn)生代理對象的靜態(tài)代理工廠類
public class ProxyFactory {
public static ILawSuit getProxy(){
return new ProxyLawyer(new SecondDogWang());
}
}
這樣就基本構(gòu)建了靜態(tài)代理關(guān)系了碰逸,然后在客戶端就可以使用代理對象來進(jìn)行操作了。
public static void main(String[] args) {
ProxyFactory.getProxy().submit("工資流水在此");
ProxyFactory.getProxy().defend();
}
輸出結(jié)果如下:
老板欠薪跑路阔加,證據(jù)如下:工資流水在此
鐵證如山饵史,馬旭還錢
可以看到,代理律師全權(quán)代理了王二狗的本次訴訟活動。那使用這種代理模式有什么好處呢胳喷,我們?yōu)槭裁床恢苯幼屚醵分苯油瓿杀敬卧V訟呢湃番?現(xiàn)實(shí)中的情況比較復(fù)雜,但是我可以簡單列出幾條:這樣代理律師就可以在提起訴訟等操作之前做一些校驗工作厌蔽,或者記錄工作牵辣。例如二狗提供的資料,律師可以選擇的移交給法庭而不是全部等等操作奴饮,就是說可以對代理的對做一些控制纬向。例如二狗不能出席法庭,代理律師可以代為出席戴卜。逾条。。
什么是動態(tài)代理
動態(tài)代理本質(zhì)上仍然是代理投剥,情況與上面介紹的完全一樣师脂,只是代理與被代理人的關(guān)系是動態(tài)確定的,例如王二狗的同事牛翠花開庭前沒有確定她的代理律師江锨,而是在開庭當(dāng)天當(dāng)庭選擇了一個律師吃警,映射到編程領(lǐng)域為這個關(guān)系是在運(yùn)行時確定的。
那既然動態(tài)代理沒有為我們增強(qiáng)代理方面的任何功能啄育,那我們?yōu)槭裁催€要用動態(tài)代理呢酌心,靜態(tài)代理不是挺好的嗎?凡是動態(tài)確定的東西大概都具有靈活性挑豌,強(qiáng)擴(kuò)展的優(yōu)勢安券。上面的例子中如果牛翠花也使用靜態(tài)代理的話,那么就需要再添加兩個類氓英。一個是牛翠花訴訟類侯勉,一個是牛翠花的代理律師類,還的在代理靜態(tài)工廠中添加一個方法铝阐。而如果使用動態(tài)代理的話址貌,就只需要生成一個訴訟類就可以了,全程只需要一個代理律師類徘键,因為我們可以動態(tài)的將很多人的案子交給這個律師來處理芳誓。
Jdk動態(tài)代理實(shí)現(xiàn)
在java的動態(tài)代理機(jī)制中,有兩個重要的類或接口啊鸭,一個是InvocationHandler
接口锹淌、另一個則是 Proxy
類,這個類和接口是實(shí)現(xiàn)我們動態(tài)代理所必須用到的赠制。
InvocationHandler
接口是給動態(tài)代理類實(shí)現(xiàn)的赂摆,負(fù)責(zé)處理被代理對象的操作的挟憔,而Proxy
是用來創(chuàng)建動態(tài)代理類實(shí)例對象的,因為只有得到了這個對象我們才能調(diào)用那些需要代理的方法烟号。
接下來我們看下實(shí)例绊谭,牛翠花動態(tài)指定代理律師是如何實(shí)現(xiàn)的。
1.構(gòu)建一個牛翠花訴訟類
public class CuiHuaNiu implements ILawSuit {
@Override
public void submit(String proof) {
System.out.println(String.format("老板欠薪跑路汪拥,證據(jù)如下:%s",proof));
}
@Override
public void defend() {
System.out.println(String.format("鐵證如山达传,%s還牛翠花血汗錢","馬旭"));
}
}
2.構(gòu)建一個動態(tài)代理類
public class DynProxyLawyer implements InvocationHandler {
private Object target;//被代理的對象
public DynProxyLawyer(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("案件進(jìn)展:"+method.getName());
Object result=method.invoke(target,args);
return result;
}
}
3.修改靜態(tài)工廠方法
public class ProxyFactory {
...
public static Object getDynProxy(Object target) {
InvocationHandler handler = new DynProxyLawyer(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}
}
4.客戶端使用
public static void main(String[] args) {
ILawSuit proxy= (ILawSuit) ProxyFactory.getDynProxy(new CuiHuaNiu());
proxy.submit("工資流水在此");
proxy.defend();
}
輸出結(jié)果為:
案件進(jìn)展:submit
老板欠薪跑路,證據(jù)如下:工資流水在此
案件進(jìn)展:defend
鐵證如山迫筑,馬旭還牛翠花血汗錢
JDK動態(tài)代理實(shí)現(xiàn)的原理
首先Jdk的動態(tài)代理實(shí)現(xiàn)方法是依賴于接口的宪赶,首先使用接口來定義好操作的規(guī)范。然后通過Proxy
類產(chǎn)生的代理對象調(diào)用被代理對象的操作脯燃,而這個操作又被分發(fā)給InvocationHandler
接口的 invoke
方法具體執(zhí)行
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
此方法的參數(shù)含義如下
proxy:代表動態(tài)代理對象
method:代表正在執(zhí)行的方法
args:代表當(dāng)前執(zhí)行方法傳入的實(shí)參
返回值:表示當(dāng)前執(zhí)行方法的返回值
例如上面牛翠花案例中搂妻,我們使用Proxy
類的newProxyInstance()
方法生成的代理對象proxy去調(diào)用了proxy.submit("工資流水在此");
操作,那么系統(tǒng)就會將此方法分發(fā)給invoke()
.其中proxy對象的類是系統(tǒng)幫我們動態(tài)生產(chǎn)的辕棚,其實(shí)現(xiàn)了我們的業(yè)務(wù)接口ILawSuit
欲主。
cgLib的動態(tài)代理實(shí)現(xiàn)
由于JDK只能針對實(shí)現(xiàn)了接口的類做動態(tài)代理,而不能對沒有實(shí)現(xiàn)接口的類做動態(tài)代理逝嚎,所以cgLib橫空出世扁瓢!CGLib(Code Generation Library)是一個強(qiáng)大、高性能的Code生成類庫补君,它可以在程序運(yùn)行期間動態(tài)擴(kuò)展類或接口引几,它的底層是使用java字節(jié)碼操作框架ASM實(shí)現(xiàn)。
1 引入cgLib 庫
cglib-nodep-3.2.6.jar:使用nodep包不需要關(guān)聯(lián)asm的jar包,jar包內(nèi)部包含asm的類.
2 定義業(yè)務(wù)類赚哗,被代理的類沒有實(shí)現(xiàn)任何接口
public class Frank {
public void submit(String proof) {
System.out.println(String.format("老板欠薪跑路她紫,證據(jù)如下:%s",proof));
}
public void defend() {
System.out.println(String.format("鐵證如山硅堆,%s還Frank血汗錢","馬旭"));
}
}
3 定義攔截器屿储,在調(diào)用目標(biāo)方法時,CGLib會回調(diào)MethodInterceptor接口方法攔截渐逃,來實(shí)現(xiàn)你自己的代理邏輯够掠,類似于JDK中的InvocationHandler接口。
public class cgLibDynProxyLawyer implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("submit"))
System.out.println("案件提交成功,證據(jù)如下:"+ Arrays.asList(params));
Object result = methodProxy.invokeSuper(o, params);
return result;
}
}
4定義動態(tài)代理工廠茄菊,生成動態(tài)代理
public class ProxyFactory {
public static Object getGcLibDynProxy(Object target){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new cgLibDynProxyLawyer());
Object targetProxy= enhancer.create();
return targetProxy;
}
}
5客戶端調(diào)用
public static void main(String[] args) {
Frank cProxy= (Frank) ProxyFactory.getGcLibDynProxy(new Frank());
cProxy.submit("工資流水在此");
cProxy.defend();
}
輸出結(jié)果:
案件提交成功,證據(jù)如下:[工資流水在此]
老板欠薪跑路疯潭,證據(jù)如下:工資流水在此
鐵證如山,馬旭還Frank血汗錢
可見面殖,通過cgLib對沒有實(shí)現(xiàn)任何接口的類做了動態(tài)代理竖哩,達(dá)到了和前面一樣的效果。這里只是簡單的講解了一些cgLib的使用方式脊僚,有興趣的可以進(jìn)一步了解其比較高級的功能相叁,例如回調(diào)過濾器(CallbackFilter)等。
cgLib的動態(tài)代理原理
CGLIB原理:動態(tài)生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法增淹。在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用椿访,順勢織入橫切邏輯。它比使用java反射的JDK動態(tài)代理要快虑润。
CGLIB底層:使用字節(jié)碼處理框架ASM成玫,來轉(zhuǎn)換字節(jié)碼并生成新的類。不鼓勵直接使用ASM拳喻,因為它要求你必須對JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉哭当。
CGLIB缺點(diǎn):對于final方法,無法進(jìn)行代理舞蔽。
動態(tài)代理在AOP中的應(yīng)用
什么是AOP? 維基百科上如是說:
定義:In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
AOP是一種編程范式福压,其目標(biāo)是通過隔離切面耦合來增加程序的模塊化。
首先聲明茬高,AOP是OOP的補(bǔ)充矾芙,其地位及其重要性遠(yuǎn)不及OOP,總體來說OOP面向名詞領(lǐng)域而AOP面向動詞領(lǐng)域,例如對一個人的設(shè)計肯定是使用OOP朵栖,例如這個人有手颊亮,腳,眼睛瞪屬性陨溅。而對這個人上廁所這個動作就會涉及到AOP终惑,例如上廁所前的先確定一下拿沒拿手紙等。要理解AOP就首先要理解什么是切面耦合(cross-cutting concerns)门扇。例如有這樣一個需求雹有,要求為一個程序中所有方法名稱以test開頭的方法打印一句log,這個行為就是一個典型的cross-cutting場景臼寄。首先這個打印log和業(yè)務(wù)毫無關(guān)系霸奕,然后其處于分散在整個程序當(dāng)中的各個模塊,如果按照我們原始的方法開發(fā)吉拳,一旦后期需求變動將是及其繁瑣的质帅。所以我們就需要將這個切面耦合封裝隔離,不要將其混入業(yè)務(wù)代碼當(dāng)中留攒。
例如在王二狗的案子中煤惩,我們希望在案子起訴后打印一句成功的log,如果不使用代理的話炼邀,我們是需要將log寫在相應(yīng)的業(yè)務(wù)邏輯里面的魄揉,例如王二狗訴訟類SecondDogWang
里面的submit()
方法中。使用了動態(tài)代理后拭宁,我們只需要在InvocationHandler
里面的invoke()方法中寫就可以了洛退,不會侵入業(yè)務(wù)代碼當(dāng)中票彪,在以后的維護(hù)過程中對業(yè)務(wù)毫無影響,這是我們希望看到的不狮。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("submit"))
System.out.println("案件提交成功,證據(jù)如下:"+ Arrays.asList(args));
Object result=method.invoke(target,args);
return result;
}
輸出結(jié)果為:
案件提交成功,證據(jù)如下:[工資流水在此]
老板欠薪跑路降铸,證據(jù)如下:工資流水在此
鐵證如山,馬旭還牛翠花血汗錢
所以AOP主要可以用于:日志記錄摇零,性能統(tǒng)計推掸,安全控制,事務(wù)處理驻仅,異常處理等場景下谅畅。
總結(jié)
靜態(tài)代理比動態(tài)代理更符合OOP原則,在日常開發(fā)中使用也較多噪服。動態(tài)代理在開發(fā)框架時使用較多毡泻,例如大名鼎鼎的Spring
。
最后希望王二狗和牛翠花他們可以順利拿回自己的血汗錢粘优。