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

代理模式是設(shè)計(jì)模式中非常重要的一種類型,而設(shè)計(jì)模式又是編程中非常重要的知識(shí)點(diǎn)埠偿,特別是在業(yè)務(wù)系統(tǒng)的重構(gòu)中,更是有舉足輕重的地位羽圃。代理模式從類型上來(lái)說(shuō)抖剿,可以分為靜態(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)方式的異同,最后說(shuō)說(shuō)動(dòng)態(tài)代理在我們周邊框架中的應(yīng)用妓布。

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

//做蛋糕的機(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í)候來(lái)了一個(gè)顧客,他想要一個(gè)水果蛋糕翁涤,但他特別喜歡杏仁萌踱,希望在水果蛋糕上加上一層杏仁。這時(shí)候我們應(yīng)該怎么做呢章咧?

因?yàn)槲覀兊牡案鈾C(jī)只能做水果蛋糕(程序設(shè)定好了)能真,沒辦法做杏仁水果蛋糕。最簡(jiǎn)單的辦法是直接修改水果蛋糕機(jī)的程序疼约,做一臺(tái)能做杏仁水果蛋糕的蛋糕機(jī)蝙泼。這種方式對(duì)應(yīng)的代碼修改也很簡(jiǎn)單,直接在原來(lái)的代碼上進(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)角度上來(lái)說(shuō),這種方式從代碼上不是很優(yōu)雅而钞,修改了原來(lái)的代碼拘荡。根據(jù)代碼圈中「對(duì)修改封閉、對(duì)擴(kuò)展開放」的思想网缝,我們?cè)趪L試滿足新的業(yè)務(wù)需求的時(shí)候應(yīng)該盡量少修改原來(lái)的代碼蟋定,而是在原來(lái)的代碼上進(jìn)行拓展。

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

下面我們就來(lái)試著實(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...
    }
}

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

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

其實(shí)在 Java 中早已經(jīng)有了針對(duì)這種情況而設(shè)計(jì)的一個(gè)接口,專門用來(lái)解決類似的問題镰矿,它就是動(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)作(撒杏仁)。

接下來(lái)我們針對(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)系作者
  • 序言:七十年代末宰衙,一起剝皮案震驚了整個(gè)濱河市供炼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袋哼,老刑警劉巖涛贯,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異虫腋,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)趋翻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門盒蟆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)历等,“玉大人,你說(shuō)我怎么就攤上這事募闲≡复” “怎么了仍侥?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)患蹂。 經(jīng)常有香客問我砸紊,道長(zhǎng),這世上最難降的妖魔是什么沼溜? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任系草,我火速辦了婚禮唆涝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘廊酣。我一直安慰自己,他們只是感情好嚎京,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布鞍帝。 她就那樣靜靜地躺著,像睡著了一般摄凡。 火紅的嫁衣襯著肌膚如雪蚓曼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天床绪,我揣著相機(jī)與錄音其弊,去河邊找鬼。 笑死痹雅,一個(gè)胖子當(dāng)著我的面吹牛糊识,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愉耙,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朴沿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸠真!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起吠卷,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祭隔,失蹤者是張志新(化名)和其女友劉穎路操,沒想到半個(gè)月后千贯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔谴,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年峰弹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鞠呈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片右钾。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖窘茁,靈堂內(nèi)的尸體忽然破棺而出后控,到底是詐尸還是另有隱情空镜,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布张抄,位于F島的核電站署惯,受9級(jí)特大地震影響镣隶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜安岂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一域那、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧败许,春花似錦、人聲如沸市殷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柿究。三九已至黄选,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間貌夕,已是汗流浹背民镜。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留们童,地道東北人鲸鹦。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像齐板,于是被迫代替她去往敵國(guó)和親葛菇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 如何使用動(dòng)態(tài)代理? 參照上面的例子庵朝,我們可以知道要實(shí)現(xiàn)動(dòng)態(tài)代理需要做兩方面的工作又厉。 首先需要新建一個(gè)類椎瘟,并且這個(gè)類...
    java部落閱讀 2,113評(píng)論 1 16
  • 酉_酉閱讀 1,914評(píng)論 23 72
  • 一個(gè)女人來(lái)咨詢戀愛的苦惱。 男友一個(gè)接著一個(gè)仇冯,無(wú)法忍受空窗期,甚至出現(xiàn)過(guò)上午剛和男友分手苛坚,晚上就和另一個(gè)男人去看電...
    醉后揮毫筆有神閱讀 380評(píng)論 1 1
  • 小長(zhǎng)假已過(guò),是否回到了工作狀態(tài)中了呢娇昙?甜心已經(jīng)準(zhǔn)備戰(zhàn)斗了尺迂。 生活不至于眼前的茍且還有詩(shī)和遠(yuǎn)方哦。 哈哈冒掌,那么甜心今...
    甜心PPT閱讀 8,529評(píng)論 35 147