秒懂Java代理與動態(tài)代理模式

版權(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

最后希望王二狗和牛翠花他們可以順利拿回自己的血汗錢粘优。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仇味,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子雹顺,更是在濱河造成了極大的恐慌丹墨,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嬉愧,死亡現(xiàn)場離奇詭異贩挣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)没酣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門王财,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裕便,你說我怎么就攤上這事绒净。” “怎么了闪金?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵疯溺,是天一觀的道長论颅。 經(jīng)常有香客問我哎垦,道長,這世上最難降的妖魔是什么恃疯? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任漏设,我火速辦了婚禮,結(jié)果婚禮上今妄,老公的妹妹穿的比我還像新娘郑口。我一直安慰自己鸳碧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布犬性。 她就那樣靜靜地躺著瞻离,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乒裆。 梳的紋絲不亂的頭發(fā)上套利,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音鹤耍,去河邊找鬼肉迫。 笑死,一個胖子當(dāng)著我的面吹牛稿黄,可吹牛的內(nèi)容都是我干的喊衫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼杆怕,長吁一口氣:“原來是場噩夢啊……” “哼族购!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起陵珍,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤联四,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撑教,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朝墩,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年伟姐,在試婚紗的時候發(fā)現(xiàn)自己被綠了收苏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡愤兵,死狀恐怖鹿霸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秆乳,我是刑警寧澤懦鼠,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站屹堰,受9級特大地震影響肛冶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扯键,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一睦袖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荣刑,春花似錦馅笙、人聲如沸伦乔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烈和。三九已至,卻和暖如春皿淋,著一層夾襖步出監(jiān)牢的瞬間斥杜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工沥匈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔗喂,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓高帖,卻偏偏與公主長得像缰儿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子散址,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345