動態(tài)代理學習

要理解和說明什么是動態(tài)代理需要先解釋面向?qū)ο笾谐R姷脑O計模式------------代理模式

什么是代理模式(Proxy)

定義:給目標對象提供一個代理對象思灰,并由代理對象控制對目標對象的引用

主要目的是為了在不改變對象具體方法的情況下實現(xiàn)對目標方法的增強,或解決直接訪問目標對象帶來的問題鉴未。

如下UML圖揭示了用戶,接口鸠姨,委托類和代理之間的關系铜秆。

image

需要注意的是:

  1. 用戶僅通過接口調(diào)用接口功能,不在乎是誰提供了具體功能讶迁;
    2.RealSubject是接口的真正實現(xiàn)類连茧,但不和用戶真正發(fā)生接觸
    3.Proxy同樣也實現(xiàn)了接口,所以可以和用戶直接接觸
    4.用戶調(diào)用Proxy時巍糯,Proxy內(nèi)部調(diào)用了RealSubject的功能啸驯,且可以在此基礎上實現(xiàn)功能的增強

代理關系的直白理解:

image

如上圖,原本廠家生產(chǎn)電腦直接銷售給顧客祟峦,廠家和顧客之間直接聯(lián)系(類似最基本的調(diào)用接口的實現(xiàn)類)坯汤。但因為需要提供后續(xù)的售后和維修服務,增加了廠家的成本(開始的實現(xiàn)類功能不夠了)搀愧。廠家為了削減成本,減輕管理負擔(避免在現(xiàn)有實現(xiàn)類上的直接修改),廠家將部分業(yè)務交給代理去完成咱筛,而廠家也不再直接接觸顧客搓幌,僅將電腦提供給代理商,由代理商完成限售和保障售后服務(代理在現(xiàn)有類的基礎上提供更多的功能)迅箩。

靜態(tài)代理

如下是廠家的接口溉愁,提供了銷售電腦的方法。同時一個通用的接口是實現(xiàn)代理的基礎

package com.ed.demo;

public interface IProducer {

    public void sellComputer();
}

接下來是廠家的真正實現(xiàn)類饲趋,和實現(xiàn)接口的代理類

package com.ed.demo.impl;

import com.ed.demo.IProducer;

public class IProducerImpl implements IProducer {

    @Override
    public void sellComputer() {
        System.out.println("出售了一臺電腦");
    }
}

實現(xiàn)接口的代理類拐揭,在售賣電腦的前提下增加了售前和售后服務

package com.ed.demo.impl;

import com.ed.demo.IProducer;

public class Seller implements IProducer {

    IProducerImpl iProducer;

    public Seller(IProducerImpl iProducer) {
        super();
        this.iProducer = iProducer;
    }

    @Override
    public void sellComputer() {
        System.out.println("進行售前引導");   //Seller額外提供的售前服務
        iProducer.sellComputer();   //廠家原來的銷售電腦功能
        System.out.println("進行售后服務");   //Seller額外提供的售后服務
    }
}

那么運行通過代理方式的調(diào)用

package com.ed.demo;

import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.Seller;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        Seller seller = new Seller(iProducer);
        seller.sellComputer();

    }
}

結(jié)果如下:

  進行售前引導
  出售了一臺電腦
  進行售后服務

現(xiàn)在可以看到,代理模式可以在不修改被代理對象的基礎上奕塑,通過擴展代理類堂污,進行一些功能的附加與增強。值得注意的是龄砰,代理類和被代理類應該共同實現(xiàn)一個接口盟猖,或者是共同繼承某個類
但靜態(tài)代理仍然存在一些缺陷换棚,如:

  • 代理類和委托類實現(xiàn)了相同的接口式镐,代理類通過委托類實現(xiàn)了相同的方法。這樣就出現(xiàn)了大量的代碼重復固蚤。如果接口增加一個方法娘汞,除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法夕玩。增加了代碼維護的復雜度你弦。
  • 代理對象只服務于一種類型的對象,如果要服務多類型的對象风秤。勢必要為每一種對象都進行代理宵蕉,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了烤礁。

動態(tài)代理

在上述靜態(tài)代理中,一個靜態(tài)代理只能服務于一個接口,實際開發(fā)中必然導致代理類過多具被。

動態(tài)代理利用反射機制,在程序運行時才根據(jù)需求實現(xiàn)一個被代理對象的接口佃扼,而不需要定義具體的代理類(上述中的Seller)激涤。

下面通過動態(tài)代理來實現(xiàn)經(jīng)銷商的功能,首先創(chuàng)建一個InvocationHandler的實現(xiàn)類

package com.ed.demo.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Object producer;

    public MyInvocationHandler(Object producer) {
        this.producer = producer;
    }

    /**
     * 
     * @param proxy: 被代理的對象
     * @param method: 要調(diào)用的方法
     * @param args: 方法調(diào)用時所需要參數(shù)
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        System.out.println("進行售前服務");
        method.invoke(producer, args);
        System.out.println("進行售后服務");
        return null;
    }
}

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

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

因為,Proxy 動態(tài)產(chǎn)生的代理會調(diào)用 InvocationHandler 實現(xiàn)類斋陪,所以 InvocationHandler 是實際執(zhí)行者朽褪。

再按如下方式進行調(diào)用

import java.lang.reflect.Proxy;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellComputer();


    }
}

結(jié)果如下:

  進行售前引導
  出售了一臺電腦
  進行售后服務

其中最重要的方法是

IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

第一個參數(shù)傳入了委托類的類加載器置吓,運行時負責將字節(jié)碼加載到JVM中并為其定義類對象。
第二個參數(shù)返回了委托類對象所引用的類實現(xiàn)的所有接口缔赠。
第三個參數(shù)是調(diào)用處理器接口衍锚,它自定義了一個 invoke 方法,用于集中處理在動態(tài)代理類對象上的方法調(diào)用嗤堰。在此處中實現(xiàn)了原方法的增強戴质。

對于動態(tài)代理而言,上述代碼中 前兩個參數(shù)幾乎是固定的踢匣。(先傳入類加載器告匠,后傳入全部接口)

如果此時廠家同時售賣顯示器

package com.ed.demo;

public interface IProducer {

    public void sellComputer();

    public void sellMonitor();
}

委托類中進行實現(xiàn)

import com.ed.demo.IProducer;

public class IProducerImpl implements IProducer {

    @Override
    public void sellComputer() {
        System.out.println("出售了一臺電腦");
    }

    @Override
    public void sellMonitor() {
        System.out.println("出售了一臺顯示器");
    }
}

這次調(diào)用sellMonitor()

package com.ed.demo;

import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.MyInvocationHandler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellMonitor();


    }
}

可以看到代理仍然提供了售前售后服務

  進行售前服務
  出售了一臺顯示器
  進行售后服務

而且如果有另外的Iproducer實現(xiàn)類作為新的委托類,也可以以同樣的方法享受代理服務离唬,這里不再演示后专。

有選擇性的中轉(zhuǎn)

在上述代碼基礎上,對MyInvocationHandler做如下修改

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if(method.getName().equals("sellMonitor")){
            //如果是出售顯示器則不進行售后服務
            method.invoke(producer, args);
            return null;
        }
        System.out.println("進行售前服務");
        method.invoke(producer, args);
        System.out.println("進行售后服務");
        return null;
    }
}

不對銷售的顯示器進行售后服務男娄。那么再次調(diào)用

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellMonitor();


    }
}

則結(jié)果中就沒有售前售后服務

 出售了一臺顯示器

可以看到利用動態(tài)代理某個接口下的多個引用可以集中到一處進行方法增強或者有選擇性的進行代理工作行贪。

動態(tài)代理的優(yōu)點

與靜態(tài)代理相比較,最大的好處是接口中的所有方法都轉(zhuǎn)移到調(diào)用處理器的一個集中的方法(InvocationHandler.invoke)進行處理模闲。同時接口方法數(shù)量較多時也可以靈活處理建瘫。

部分源碼解析

未完待續(xù)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尸折,隨后出現(xiàn)的幾起案子啰脚,更是在濱河造成了極大的恐慌,老刑警劉巖实夹,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橄浓,死亡現(xiàn)場離奇詭異,居然都是意外死亡亮航,警方通過查閱死者的電腦和手機荸实,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缴淋,“玉大人准给,你說我怎么就攤上這事≈囟叮” “怎么了露氮?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钟沛。 經(jīng)常有香客問我畔规,道長,這世上最難降的妖魔是什么恨统? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任叁扫,我火速辦了婚禮三妈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陌兑。我一直安慰自己沈跨,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布兔综。 她就那樣靜靜地躺著,像睡著了一般狞玛。 火紅的嫁衣襯著肌膚如雪软驰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天心肪,我揣著相機與錄音锭亏,去河邊找鬼。 笑死硬鞍,一個胖子當著我的面吹牛慧瘤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播固该,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼锅减,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伐坏?” 一聲冷哼從身側(cè)響起怔匣,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桦沉,沒想到半個月后每瞒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡纯露,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年剿骨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埠褪。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡浓利,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出组橄,到底是詐尸還是另有隱情荞膘,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布玉工,位于F島的核電站羽资,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏遵班。R本人自食惡果不足惜屠升,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一潮改、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腹暖,春花似錦汇在、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至殖告,卻和暖如春阿蝶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背黄绩。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工羡洁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爽丹。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓筑煮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粤蝎。 傳聞我的和親對象是個殘疾皇子真仲,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

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