Java 中的代理模式

一荆姆、代理

代理是英文 Proxy 翻譯過來的。我們?cè)谏钪幸姷竭^的代理映凳,大概最常見的就是朋友圈中賣面膜的同學(xué)了胆筒。

她們從廠家拿貨,然后在朋友圈中宣傳诈豌,然后賣給熟人仆救。

按理說,顧客可以直接從廠家購(gòu)買產(chǎn)品矫渔,但是現(xiàn)實(shí)生活中彤蔽,很少有這樣的銷售模式。一般都是廠家委托給代理商進(jìn)行銷售庙洼,顧客跟代理商打交道顿痪,而不直接與產(chǎn)品實(shí)際生產(chǎn)者進(jìn)行關(guān)聯(lián)。

所以油够,代理就有一種中間人的味道蚁袭。接下來,我們說說軟件中的代理模式石咬。

二揩悄、代理模式


需要注意的有下面幾點(diǎn):

  • 用戶只關(guān)心接口功能,而不在乎誰提供了功能鬼悠。上圖中接口是 Subject删性。
  • 接口真正實(shí)現(xiàn)者是上圖的 RealSubject亏娜,但是它不與用戶直接接觸,而是通過代理蹬挺。
  • 代理就是上圖中的 Proxy维贺,由于它實(shí)現(xiàn)了 Subject 接口,所以它能夠直接與用戶接觸巴帮。
  • 用戶調(diào)用 Proxy 的時(shí)候幸缕,Proxy 內(nèi)部調(diào)用了 RealSubject。所以晰韵,Proxy 是中介者发乔,它可以增強(qiáng) RealSubject 操作。

三雪猪、靜態(tài)代理

我們平常去電影院看電影的時(shí)候栏尚,在電影開始的階段是不是經(jīng)常會(huì)放廣告呢?

電影是電影公司委托給影院進(jìn)行播放的只恨,但是影院可以在播放電影的時(shí)候译仗,產(chǎn)生一些自己的經(jīng)濟(jì)收益,比如賣爆米花官觅、可樂等纵菌,然后在影片開始結(jié)束時(shí)播放一些廣告。

現(xiàn)在用代碼來進(jìn)行模擬休涤。

首先得有一個(gè)接口咱圆,通用的接口是代理模式實(shí)現(xiàn)的基礎(chǔ)。這個(gè)接口我們命名為 Movie功氨,代表電影播放的能力序苏。

package com.frank.test;

public interface Movie {
    void play();
}

然后,我們要有一個(gè)真正的實(shí)現(xiàn)這個(gè) Movie 接口的類捷凄,和一個(gè)只是實(shí)現(xiàn)接口的代理類忱详。

package com.frank.test;

public class RealMovie implements Movie {

    @Override
    public void play() {
        // TODO Auto-generated method stub
        System.out.println("您正在觀看電影 《肖申克的救贖》");
    }

}

這個(gè)表示真正的影片。它實(shí)現(xiàn)了 Movie 接口跺涤,play() 方法調(diào)用時(shí)匈睁,影片就開始播放。那么 Proxy 代理呢桶错?

package com.frank.test;

public class Cinema implements Movie {
    
    RealMovie movie;
    
    public Cinema(RealMovie movie) {
        super();
        this.movie = movie;
    }

    @Override
    public void play() {
        
        guanggao(true);
        
        movie.play();
        
        guanggao(false);
    }
    
    public void guanggao(boolean isStart){
        if ( isStart ) {
            System.out.println("電影馬上開始了航唆,爆米花、可樂牛曹、口香糖9.8折佛点,快來買按祭摹黎比!");
        } else {
            System.out.println("電影馬上結(jié)束了超营,爆米花、可樂阅虫、口香糖9.8折演闭,買回家吃吧!");
        }
    }

}

Cinema 就是 Proxy 代理對(duì)象颓帝,它有一個(gè) play() 方法米碰。不過調(diào)用 play() 方法時(shí),它進(jìn)行了一些相關(guān)利益的處理购城,那就是廣告÷雷現(xiàn)在,我們編寫測(cè)試代碼瘪板。

package com.frank.test;

public class ProxyTest {

    public static void main(String[] args) {
        
        RealMovie realmovie = new RealMovie();
        
        Movie movie = new Cinema(realmovie);
        
        movie.play();

    }

}
//測(cè)試結(jié)果:
電影馬上開始了吴趴,爆米花、可樂侮攀、口香糖9.8折锣枝,快來買啊兰英!
您正在觀看電影 《肖申克的救贖》
電影馬上結(jié)束了撇叁,爆米花、可樂畦贸、口香糖9.8折陨闹,買回家吃吧!

現(xiàn)在可以看到薄坏,代理模式可以在不修改被代理對(duì)象的基礎(chǔ)上正林,通過擴(kuò)展代理類,進(jìn)行一些功能的附加與增強(qiáng)颤殴。值得注意的是觅廓,代理類和被代理類應(yīng)該共同實(shí)現(xiàn)一個(gè)接口,或者是共同繼承某個(gè)類涵但。

上面介紹的是靜態(tài)代理的內(nèi)容杈绸,為什么叫做靜態(tài)呢?因?yàn)樗念愋褪鞘孪阮A(yù)定好的矮瘟,比如上面代碼中的 Cinema 這個(gè)類瞳脓。下面要介紹的內(nèi)容就是動(dòng)態(tài)代理。

四澈侠、動(dòng)態(tài)代理

既然是代理劫侧,那么它與靜態(tài)代理的功能與目的是沒有區(qū)別的,唯一有區(qū)別的就是動(dòng)態(tài)與靜態(tài)的差別。

那么在動(dòng)態(tài)代理的中這個(gè)動(dòng)態(tài)體現(xiàn)在什么地方烧栋?

上一節(jié)代碼中 Cinema 類是代理写妥,我們需要手動(dòng)編寫代碼讓 Cinema 實(shí)現(xiàn) Movie 接口,而在動(dòng)態(tài)代理中审姓,我們可以讓程序在運(yùn)行的時(shí)候自動(dòng)在內(nèi)存中創(chuàng)建一個(gè)實(shí)現(xiàn) Movie 接口的代理珍特,而不需要去定義 Cinema 這個(gè)類。這就是它被稱為動(dòng)態(tài)的原因魔吐。

假設(shè)有一個(gè)大商場(chǎng)扎筒,商場(chǎng)有很多的柜臺(tái),有一個(gè)柜臺(tái)賣茅臺(tái)酒酬姆。我們進(jìn)行代碼的模擬嗜桌。

package com.frank.test;

public interface SellWine {
    
     void mainJiu();
}

SellWine 是一個(gè)接口,你可以理解它為賣酒的許可證辞色。

package com.frank.test;

public class MaotaiJiu implements SellWine {

    @Override
    public void mainJiu() {
        // TODO Auto-generated method stub
        System.out.println("我賣得是茅臺(tái)酒症脂。");
    }
}

然后創(chuàng)建一個(gè)類 MaotaiJiu,對(duì)的,就是茅臺(tái)酒的意思淫僻。

我們還需要一個(gè)柜臺(tái)來賣酒:

package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class GuitaiA implements InvocationHandler {
    
    private Object pingpai;
        
    public GuitaiA(Object pingpai) {
        this.pingpai = pingpai;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("銷售開始  柜臺(tái)是: "+this.getClass().getSimpleName());
        method.invoke(pingpai, args);
        System.out.println("銷售結(jié)束");
        return null;
    }
}

然后诱篷,我們就可以賣酒了。

package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        MaotaiJiu maotaijiu = new MaotaiJiu();
            
        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
    
        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);
    
        dynamicProxy.mainJiu();
            
    }
}
//程序運(yùn)行結(jié)果
銷售開始  柜臺(tái)是: GuitaiA
我賣得是茅臺(tái)酒雳灵。
銷售結(jié)束

我們并沒有像靜態(tài)代理那樣為 SellWine 接口實(shí)現(xiàn)一個(gè)代理類棕所,但最終它仍然實(shí)現(xiàn)了相同的功能,這其中的差別悯辙,就是之前討論的動(dòng)態(tài)代理所謂“動(dòng)態(tài)”的原因琳省。

  • 動(dòng)態(tài)代理的語(yǔ)法
    動(dòng)態(tài)代碼涉及了一個(gè)非常重要的類 Proxy。正是通過 Proxy 的靜態(tài)方法 newProxyInstance 才會(huì)動(dòng)態(tài)創(chuàng)建代理躲撰。
    1.Proxy
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

下面講解它的 3 個(gè)參數(shù)意義针贬。

  • loader 自然是類加載器
  • interfaces 代碼要用來代理的接口
  • h 一個(gè) InvocationHandler 對(duì)象
    2.InvocationHandler
    InvocationHandler 是一個(gè)接口,官方文檔解釋說拢蛋,每個(gè)代理的實(shí)例都有一個(gè)與之關(guān)聯(lián)的 InvocationHandler 實(shí)現(xiàn)類桦他,如果代理的方法被調(diào)用,那么代理便會(huì)通知和轉(zhuǎn)發(fā)給內(nèi)部的 InvocationHandler 實(shí)現(xiàn)類谆棱,由它決定處理快压。
public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler 內(nèi)部只是一個(gè) invoke() 方法,正是這個(gè)方法決定了怎么樣處理代理傳遞過來的方法調(diào)用垃瞧。

  • proxy 代理對(duì)象
  • method 代理對(duì)象調(diào)用的方法
  • args 調(diào)用的方法中的參數(shù)

因?yàn)槟枇樱琍roxy 動(dòng)態(tài)產(chǎn)生的代理會(huì)調(diào)用 InvocationHandler 實(shí)現(xiàn)類,所以 InvocationHandler 是實(shí)際執(zhí)行者个从。

public class GuitaiA implements InvocationHandler {
    
    private Object pingpai;
    
    public GuitaiA(Object pingpai) {
        this.pingpai = pingpai;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("銷售開始  柜臺(tái)是: "+this.getClass().getSimpleName());
        method.invoke(pingpai, args);
        System.out.println("銷售結(jié)束");
        return null;
    }
}

GuitaiA 就是實(shí)際上賣酒的地方脉幢。
在歪沃,我們加大難度,我們不僅要賣茅臺(tái)酒嫌松,還想賣五糧液沪曙。

package com.frank.test;

public class Wuliangye implements SellWine {

    @Override
    public void mainJiu() {
        // TODO Auto-generated method stub
        System.out.println("我賣得是五糧液。");

    }
}

Wuliangye 這個(gè)類也實(shí)現(xiàn)了 SellWine 這個(gè)接口豆瘫,說明它也擁有賣酒的許可證,同樣把它放到 GuitaiA 上售賣菊值。

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        MaotaiJiu maotaijiu = new MaotaiJiu();
        
        Wuliangye wu = new Wuliangye();
        
        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
        InvocationHandler jingxiao2 = new GuitaiA(wu);
        
        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);
        SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao2);
        
        dynamicProxy.mainJiu();
        
        dynamicProxy1.mainJiu();    
    }
}
//運(yùn)行結(jié)果:
銷售開始  柜臺(tái)是: GuitaiA
我賣得是茅臺(tái)酒外驱。
銷售結(jié)束
銷售開始  柜臺(tái)是: GuitaiA
我賣得是五糧液。
銷售結(jié)束
  • 動(dòng)態(tài)代理涉及到的角色腻窒。

    紅框中 $Proxy0就是通過 Proxy 動(dòng)態(tài)生成的昵宇。
    $Proxy0實(shí)現(xiàn)了要代理的接口。
    $Proxy0通過調(diào)用 InvocationHandler來執(zhí)行任務(wù)儿子。

五瓦哎、代理的作用

可能有同學(xué)會(huì)問,已經(jīng)學(xué)習(xí)了代理的知識(shí)柔逼,但是蒋譬,它們有什么用呢?

主要作用愉适,還是在不修改被代理對(duì)象的源碼上犯助,進(jìn)行功能的增強(qiáng)。

這在 AOP 面向切面編程領(lǐng)域經(jīng)常見维咸。

在軟件業(yè)剂买,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程癌蓖,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)瞬哼。AOP是OOP的延續(xù),是軟件開發(fā)中的一個(gè)熱點(diǎn)租副,也是Spring框架中的一個(gè)重要內(nèi)容坐慰,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離用僧,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低讨越,提高程序的可重用性,同時(shí)提高了開發(fā)的效率永毅。

主要功能
日志記錄把跨,性能統(tǒng)計(jì),安全控制沼死,事務(wù)處理着逐,異常處理等等。

六、總結(jié)

  • 代理分為靜態(tài)代理和動(dòng)態(tài)代理兩種耸别。
  • 靜態(tài)代理健芭,代理類需要自己編寫代碼寫成。
  • 動(dòng)態(tài)代理秀姐,代理類通過 Proxy.newInstance() 方法生成慈迈。
  • 不管是靜態(tài)代理還是動(dòng)態(tài)代理,代理與被代理者都要實(shí)現(xiàn)兩次接口省有,它們的實(shí)質(zhì)是面向接口編程痒留。
  • 靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別是在于要不要開發(fā)者自己定義 Proxy 類。
  • 動(dòng)態(tài)代理通過 Proxy 動(dòng)態(tài)生成 proxy class蠢沿,但是它也指定了一個(gè) InvocationHandler 的實(shí)現(xiàn)類伸头。
  • 代理模式本質(zhì)上的目的是為了增強(qiáng)現(xiàn)有代碼的功能。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舷蟀,一起剝皮案震驚了整個(gè)濱河市恤磷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌野宜,老刑警劉巖扫步,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異匈子,居然都是意外死亡锌妻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門旬牲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仿粹,“玉大人,你說我怎么就攤上這事原茅】岳” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵擂橘,是天一觀的道長(zhǎng)晌区。 經(jīng)常有香客問我,道長(zhǎng)通贞,這世上最難降的妖魔是什么朗若? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任蕉饼,我火速辦了婚禮娇斩,結(jié)果婚禮上暇检,老公的妹妹穿的比我還像新娘胁勺。我一直安慰自己,他們只是感情好缚柳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布宙刘。 她就那樣靜靜地躺著患朱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旭斥。 梳的紋絲不亂的頭發(fā)上容达,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音垂券,去河邊找鬼花盐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菇爪,可吹牛的內(nèi)容都是我干的算芯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼娄帖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼也祠!你這毒婦竟也來了昙楚?” 一聲冷哼從身側(cè)響起近速,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堪旧,沒想到半個(gè)月后削葱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淳梦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年析砸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爆袍。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡首繁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出陨囊,到底是詐尸還是另有隱情弦疮,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布蜘醋,位于F島的核電站胁塞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏压语。R本人自食惡果不足惜啸罢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胎食。 院中可真熱鬧扰才,春花似錦、人聲如沸厕怜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至舵揭,卻和暖如春谤专,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背午绳。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工置侍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拦焚。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓蜡坊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赎败。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秕衙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 原作者:frank909原博客鏈接:輕松學(xué)据忘,Java 中的代理模式及動(dòng)態(tài)代理 前幾天我寫了《秒懂,Java 注解 ...
    駭客與畫家閱讀 392評(píng)論 0 1
  • 本文動(dòng)態(tài)代理部分內(nèi)容大量引自:http://www.ibm.com/developerworks/cn/java/...
    端木軒閱讀 413評(píng)論 0 0
  • 因?yàn)镽etrofit剖析源碼的時(shí)候會(huì)用到ava中的代理模式搞糕,所以這篇就先回憶一下代理設(shè)計(jì)模式勇吊。代理模式分為兩種:代...
    幾行代碼閱讀 827評(píng)論 0 2
  • 代理模式:為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。在某些情況下窍仰,一個(gè)對(duì)象不適合或者不能直接引用另一個(gè)對(duì)象汉规,而...
    luweicheng24閱讀 243評(píng)論 0 0
  • 青春里有說不完的故事,也有看不完的籃球賽驹吮。 今晚陪娃們一起看比賽针史,最后的系部冠軍爭(zhēng)奪賽,打得非常激烈碟狞,一開始的時(shí)候...
    亦塵宇光閱讀 116評(píng)論 1 3