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

版權(quán)申明】非商業(yè)目的附文章鏈接可自由轉(zhuǎn)載
博文地址:http://www.itdecent.cn/p/d7aeded8ace0
出自:shusheng007

概述

什么是代理模式?解決什么問題(即為什么需要)?什么是靜態(tài)代理?什么是動態(tài)代理模式?二者什么關(guān)系?具體如何實現(xiàn)?什么原理?如何改進(jìn)?這即為我們學(xué)習(xí)一項新知識的正確打開方式,我們接下來會以此展開,讓你秒懂。

概念

什么是代理模式

定義:為其他對象提供一種代理以控制對這個對象的訪問

定義總是抽象而晦澀難懂的,讓我們回到生活中來吧。

實例:王二狗公司(天津在線回聲科技發(fā)展有限公司)老板突然在發(fā)工資的前一天帶著小姨子跑路了,可憐二狗一身房貸,被迫提起勞動仲裁,勞動局就會為其指派一位代理律師全權(quán)負(fù)責(zé)二狗的仲裁事宜。那這里面就是使用了代理模式,因為在勞動仲裁這個活動中,代理律師會全權(quán)代理王二狗。

解決什么問題

下面是一些使用場景,不過太抽象,暫時可以不要在意,隨著你的不斷進(jìn)步你終究會明白的。

  • 遠(yuǎn)程代理 :為位于兩個不同地址空間對象的訪問提供了一種實現(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)系在編譯期間就確定了。下面就是王二狗勞動仲裁的代碼實現(xiàn):

首先定義一個代表訴訟的接口

public interface ILawSuit {
    void submit(String proof);//提起訴訟
    void defend();//法庭辯護(hù)
}

王二狗訴訟類型,實現(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還錢","馬旭"));
    }
}

代理律師訴訟類,實現(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)實中的情況比較復(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)代理實現(xiàn)

在java的動態(tài)代理機(jī)制中,有兩個重要的類或接口,一個是InvocationHandler接口、另一個則是 Proxy類,這個類和接口是實現(xiàn)我們動態(tài)代理所必須用到的。

InvocationHandler接口是給動態(tài)代理類實現(xiàn)的,負(fù)責(zé)處理被代理對象的操作的,而Proxy是用來創(chuàng)建動態(tài)代理類實例對象的,因為只有得到了這個對象我們才能調(diào)用那些需要代理的方法。

接下來我們看下實例,牛翠花動態(tài)指定代理律師是如何實現(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)代理實現(xiàn)的原理

首先Jdk的動態(tài)代理實現(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í)行方法傳入的實參
返回值:表示當(dāng)前執(zhí)行方法的返回值

例如上面牛翠花案例中,我們使用Proxy類的newProxyInstance()方法生成的代理對象proxy去調(diào)用了proxy.submit("工資流水在此");操作,那么系統(tǒng)就會將此方法分發(fā)給invoke().其中proxy對象的類是系統(tǒng)幫我們動態(tài)生產(chǎn)的,其實現(xiàn)了我們的業(yè)務(wù)接口ILawSuit。

cgLib的動態(tài)代理實現(xiàn)

由于JDK只能針對實現(xiàn)了接口的類做動態(tài)代理,而不能對沒有實現(xiàn)接口的類做動態(tài)代理,所以cgLib橫空出世!CGLib(Code Generation Library)是一個強(qiáng)大、高性能的Code生成類庫,它可以在程序運(yùn)行期間動態(tài)擴(kuò)展類或接口,它的底層是使用java字節(jié)碼操作框架ASM實現(xiàn)。

1 引入cgLib 庫
cglib-nodep-3.2.6.jar:使用nodep包不需要關(guān)聯(lián)asm的jar包,jar包內(nèi)部包含asm的類.

2 定義業(yè)務(wù)類,被代理的類沒有實現(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接口方法攔截,來實現(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對沒有實現(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缺點:對于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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容