Java動(dòng)態(tài)代理 深度詳解(一)

代理模式是設(shè)計(jì)模式中非常重要的一種類型,而設(shè)計(jì)模式又是編程中非常重要的知識(shí)點(diǎn),特別是在業(yè)務(wù)系統(tǒng)的重構(gòu)中,更是有舉足輕重的地位。代理模式從類型上來說,可以分為靜態(tài)代理和動(dòng)態(tài)代理兩種類型。

今天我將用非常簡(jiǎn)單易懂的例子向大家介紹動(dòng)態(tài)代理的兩種類型,接著重點(diǎn)介紹動(dòng)態(tài)代理的兩種實(shí)現(xiàn)方式(Java 動(dòng)態(tài)代理和 CGLib 動(dòng)態(tài)代理),最后深入剖析這兩種實(shí)現(xiàn)方式的異同,最后說說動(dòng)態(tài)代理在我們周邊框架中的應(yīng)用。

在開始之前,我們先假設(shè)這樣一個(gè)場(chǎng)景:有一個(gè)蛋糕店,它們都是使用蛋糕機(jī)來做蛋糕的,而且不同種類的蛋糕由不同的蛋糕機(jī)來做,這樣就有:水果蛋糕機(jī)、巧克力蛋糕機(jī)等。這個(gè)場(chǎng)景用 Java 語言描述就是下面這樣:

//做蛋糕的機(jī)器
public interface CakeMachine{
    void makeCake();
}

//專門做水果蛋糕的機(jī)器
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("Making a fruit cake...");
    }
}

//專門做巧克力蛋糕的機(jī)器
public class ChocolateCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.printf("making a Chocolate Cake...");
    }
}

//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
        new FruitCakeMachine().makeCake();  //making a Fruit Cake...
        new ChocolateCakeMachine().makeCake();  //making a Chocolate Cake...
    }
}

上面的代碼抽象出了一個(gè) CakeMachine 接口,有各種蛋糕機(jī)(FruitCakeMachine、ChocolateCakeMachine 等)實(shí)現(xiàn)了該接口,最后蛋糕店(CakeShop)直接利用這些蛋糕機(jī)做蛋糕。

這樣的一個(gè)例子真實(shí)地描述了實(shí)際生活中的場(chǎng)景。但生活中的場(chǎng)景往往是復(fù)雜多變的,假設(shè)這個(gè)時(shí)候來了一個(gè)顧客,他想要一個(gè)水果蛋糕,但他特別喜歡杏仁,希望在水果蛋糕上加上一層杏仁。這時(shí)候我們應(yīng)該怎么做呢?

因?yàn)槲覀兊牡案鈾C(jī)只能做水果蛋糕(程序設(shè)定好了),沒辦法做杏仁水果蛋糕。最簡(jiǎn)單的辦法是直接修改水果蛋糕機(jī)的程序,做一臺(tái)能做杏仁水果蛋糕的蛋糕機(jī)。這種方式對(duì)應(yīng)的代碼修改也很簡(jiǎn)單,直接在原來的代碼上進(jìn)行修改,生成一臺(tái)專門做杏仁水果蛋糕的機(jī)器就好了,修改后的 FruitCakeMachien 類應(yīng)該是這樣子:

//專門做水果蛋糕的機(jī)器,并且加上一層杏仁
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("making a Fruit Cake...");
        System.out.println("adding apricot...");
    }
}

雖然上面這種方式實(shí)現(xiàn)了我們的業(yè)務(wù)需求。但是仔細(xì)想一想,在現(xiàn)實(shí)生活中如果我們遇到這樣的一個(gè)需求,我們不可能因?yàn)橐粋€(gè)顧客的特殊需求就去修改一臺(tái)蛋糕機(jī)的硬件程序,這樣成本太高!而且從代碼實(shí)現(xiàn)角度上來說,這種方式從代碼上不是很優(yōu)雅,修改了原來的代碼。根據(jù)代碼圈中「對(duì)修改封閉、對(duì)擴(kuò)展開放」的思想,我們?cè)趪L試滿足新的業(yè)務(wù)需求的時(shí)候應(yīng)該盡量少修改原來的代碼,而是在原來的代碼上進(jìn)行拓展。

那我們究竟應(yīng)該怎么做更加合適一些呢?我們肯定是直接用水果蛋糕機(jī)做一個(gè)蛋糕,然后再人工撒上一層杏仁啦。這其實(shí)就對(duì)應(yīng)了即使模式中的代理模式,在這個(gè)業(yè)務(wù)場(chǎng)景中,服務(wù)員(代理人)跟顧客說沒問題,可以做水果杏仁蛋糕,于是服務(wù)員充當(dāng)了一個(gè)代理的角色,先讓水果蛋糕機(jī)做出了水果蛋糕,之后再往上面撒了一層杏仁。在這個(gè)例子中,實(shí)際做事情的還是水果蛋糕機(jī),服務(wù)員(撒杏仁的人)只是充當(dāng)了一個(gè)代理的角色。

下面我們就來試著實(shí)現(xiàn)這樣一個(gè)代理模式的設(shè)計(jì)。我們需要做的,其實(shí)就是設(shè)計(jì)一個(gè)代理類(FruitCakeMachineProxy),這個(gè)代理類就相當(dāng)于那個(gè)撒上一層杏仁的人,之后讓蛋糕店直接調(diào)用即可代理類去實(shí)現(xiàn)即可。

//水果蛋糕機(jī)代理
public class FruitCakeMachineProxy implements CakeMachine{
    private CakeMachine cakeMachine;
    public FruitCakeMachineProxy(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("adding apricot...");
    }
}
//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
          FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        FruitCakeMachineProxy fruitCakeMachineProxy = new FruitCakeMachineProxy(fruitCakeMachine);
        fruitCakeMachineProxy.makeCake();   //making a Fruit Cake...   adding apricot...
    }
}

通過代理實(shí)現(xiàn)這樣的業(yè)務(wù)場(chǎng)景,這樣我們就不需要在原來的類上進(jìn)行修改,從而使得代碼更加優(yōu)雅,拓展性更強(qiáng)。如果下次客人喜歡葡萄干水果蛋糕了了,那可以再寫一個(gè) CurrantCakeMachineProxy 類來撒上一層葡萄干,原來的代碼也不會(huì)被修改。上面說的這種業(yè)務(wù)場(chǎng)景就是代理模式的實(shí)際應(yīng)用,準(zhǔn)確地說這種是靜態(tài)代理。

業(yè)務(wù)場(chǎng)景的復(fù)雜度往往千變?nèi)f化,如果有另外一個(gè)客人,他也想在巧克力蛋糕上撒一層杏仁,那我們豈不是也要再寫一個(gè)代理類讓他做同樣的一件事情。如果有客人想在抹茶蛋糕上撒一層杏仁,有客人想在五仁蛋糕上撒一層杏仁……那我們豈不是要寫無數(shù)個(gè)代理類?

其實(shí)在 Java 中早已經(jīng)有了針對(duì)這種情況而設(shè)計(jì)的一個(gè)接口,專門用來解決類似的問題,它就是動(dòng)態(tài)代理 —— InvocationHandler。

動(dòng)態(tài)代理與靜態(tài)代理的區(qū)別是靜態(tài)代理只能針對(duì)特定一種類型(某種蛋糕機(jī))做某種代理動(dòng)作(撒杏仁),而動(dòng)態(tài)代理則可以對(duì)所有類型(所有蛋糕機(jī))做某種代理動(dòng)作(撒杏仁)。

接下來我們針對(duì)這個(gè)業(yè)務(wù)場(chǎng)景做一個(gè)代碼的抽象實(shí)現(xiàn)。首先我們分析一下可以知道這種場(chǎng)景的共同點(diǎn)是希望在各種蛋糕上都做「撒一層杏仁」的動(dòng)作,所以我們就做一個(gè)杏仁動(dòng)態(tài)代理(ApricotHandler)。

//杏仁動(dòng)態(tài)代理
public class ApricotHandler implements InvocationHandler{

    private Object object;

    public ApricotHandler(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //調(diào)用真正的蛋糕機(jī)做蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}

撒杏仁的代理寫完之后,我們直接讓蛋糕店開工:

public class CakeShop {
    public static void main(String[] args) {
        //水果蛋糕撒一層杏仁
        CakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
        CakeMachine fruitCakeProxy = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
        fruitCakeProxy.makeCake();
        //巧克力蛋糕撒一層杏仁
        CakeMachine chocolateCakeMachine = new ChocolateCakeMachine();
        ApricotHandler chocolateCakeApricotHandler = new ApricotHandler(chocolateCakeMachine);
        CakeMachine chocolateCakeProxy = (CakeMachine) Proxy.newProxyInstance(chocolateCakeMachine.getClass().getClassLoader(),
                chocolateCakeMachine.getClass().getInterfaces(), chocolateCakeApricotHandler);
        chocolateCakeProxy.makeCake();
    }
}

輸出結(jié)果為:

making a Fruit Cake...
adding apricot...
making a Chocolate Cake...
adding apricot...

從輸出結(jié)果可以知道,這與我們想要的結(jié)果是一致的。與靜態(tài)代理相比,動(dòng)態(tài)代理具有更加的普適性,能減少更多重復(fù)的代碼。試想這個(gè)場(chǎng)景如果使用靜態(tài)代理的話,我們需要對(duì)每一種類型的蛋糕機(jī)都寫一個(gè)代理類(FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy等)。但是如果使用動(dòng)態(tài)代理的話,我們只需要寫一個(gè)通用的撒杏仁代理類(ApricotHandler)就可以直接完成所有操作了。直接省去了寫 FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy 的功夫,極大地提高了效率。

看到這里,大家應(yīng)該清楚為什么有了靜態(tài)代理之后,還需要有動(dòng)態(tài)代理了吧。靜態(tài)代理只能針對(duì)某一種類型的實(shí)現(xiàn)(蛋糕機(jī))進(jìn)行操作,如果要針對(duì)所有類型的實(shí)現(xiàn)(所有蛋糕機(jī))都進(jìn)行同樣的操作,那就必須要?jiǎng)討B(tài)代理出馬了。

歡迎加入學(xué)習(xí)交流群569772982,大家一起學(xué)習(xí)交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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