設(shè)計模式——代理模式(一)

1. 定義

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

2. 使用場景

當(dāng)想對某個對象做功能增強拓展脯爪,但又不想改變原有對象代碼時则北,為了達到這種良好的擴展性矿微,可以使用一個代理對象間接訪問原對象的方法,并進行功能真強和擴展尚揣。

3. 代理模式的范圍

從廣義上來講涌矢,所有通過一個中間對象間接訪問原對象的方式都可以稱之為代理。但出于可擴展性考慮惑艇,嚴格意義上的代理模式是將需要間接訪問的操作抽象成接口或抽象類蒿辙,真實對象類實現(xiàn)接口和抽象類真實操作,代理對象類持有真實對象的引用完成接口的實現(xiàn)滨巴。

4. 代理模式設(shè)計

靜態(tài)代理.jpg
public interface ISubject{
    public void doSomething();
}
public class RealSubject implements ISubject{
    @Override
    public void doSomething(){
        System.out.println(" real do something");
    }
}
public class ProxySubject implements ISubject{
    private ISubject target;
    public ProxySubject(ISubject target){
        this.target = target;
    }
    @Override
    public void doSomething(){
        //do something before
        doSomethingBefore();
        //target do something
        this.target.doSomething();
        //do something after
        doSomethingAfter();
    }
    public void doSomethingBefore(){....}
    public void doSomethingAfter(){....}
}

public class ProxyTest{
    public static void main(String[] args){
        ISubject proxy = new ProxySubject(new RealSubject());
        proxy.doSomething();
    }
}

代碼說明:
ISubject:需要代理的對象主題抽象方法接口。
RealSubject:真實的對象實現(xiàn)抽象方法接口類俺叭。
ProxySubject: 代理的對象恭取,持有真實對象引用target,并在調(diào)用target方法前做方法功能增強和擴展熄守。
ProxyTest: 測試類蜈垮。

5. 更靈活的動態(tài)代理

考慮這樣一種場景,當(dāng)需要代理的接口方法增多時裕照,勢必要在每一種方法進行代理攒发,這樣就會導(dǎo)致接口增加了一個方法,除了所有實現(xiàn)類都需要實現(xiàn)這個方法外晋南,所有的代理類也需要實現(xiàn)此方法惠猿,這無疑增加了代碼維護的復(fù)雜度。
Java中給我們提供了一種簡單實現(xiàn)動態(tài)代理的方式负间,與上面的靜態(tài)代理代理類字節(jié)碼文件在運行前就編譯生成不同偶妖,動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射機制動態(tài)生成。

6. 動態(tài)代理設(shè)計

動態(tài)代理.jpg
public interface ISubject{
    public void doSomething();
}
public class RealSubject{
    @Override
    public void doSomething(){
        System.out.println("real do something");
    }
}
public class ProxyInvocationHandler implements InvocationHandler{
    private Object target;
    public ProxyInvocationHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throw Throwable{
        //do something before
        doSomethingBefore();
        //target do something
        Object result = method.invoke(target,args);
        //do something after
        doSomethingAfter();
        return result;
    }
    public Object getProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this)
    }
    public void doSomethingBefore(){....}
    public void doSomethingAfter(){....}
}
public class DynamicProxyTest(){
    public static void main(String[] args){
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(new RealSubject());
        ISubject proxy = (ISubject)proxyInvocationHandler.getProxy();
        proxy.doSomething();
    }
}

代碼說明:
ISubject:需要代理的對象主題抽象方法接口政溃。
RealSubject:真實的對象實現(xiàn)抽象方法接口類趾访。
InvocationHandler: java中用來實現(xiàn)動態(tài)代理的接口。
ProxyInvocationHandler: 實現(xiàn)動態(tài)接口類董虱,是動態(tài)代理的核心處理類扼鞋,
實現(xiàn)動態(tài)代理接口invoke方法。

  • invoke():該方法通過反射集中處理接口中聲明的所有方法(不再需要根據(jù)接口方法的增加愤诱,拓展代理對象類的方法了)云头。
  • getProxy():該方法通過Java代理類Proxy動態(tài)生成代理類對象。

DynamicProxyTest:測試類

7. 動態(tài)代理的實現(xiàn)原理:

其實動態(tài)代理的核心就兩點:如何動態(tài)的生成代理類對象和如何調(diào)用接口中聲明的所有方法转锈。

7.1 如何動態(tài)的生成代理類對象

代理類對象的生成主要是調(diào)用Proxy類中的該方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
    ....
    // Look up or generate the designated proxy class
    Class<?> cl = getProxyClass0(loader,interfaces);
    ....
    // Invoke its constructor with the designated invocation handler
    final Constructor<?> cons = cl.getConstructor(construcotrParams);
    return cons.newInstance(new Object[]{h});
}
  • getProxyClass0(loader,interfaces): 通過指定的類加載器loader和接口interface創(chuàng)建動態(tài)代理類對象cl盘寡。
  • cl.getConstructor(new Class[]{InvocationHandler.class}):通過反射獲取動態(tài)代理類的構(gòu)造函數(shù)。
  • cons.newInstance(new Object[]{h}):通過構(gòu)造函數(shù)創(chuàng)建代理類實例撮慨。

7.2 如何調(diào)用接口中聲明的所有方法

想知道動態(tài)代理對象如何調(diào)用接口中聲明的所有方法可以從動態(tài)生成的代理類.class文件中看出竿痰。
可以采用一下方法生成代理類文件:

public void createProxyClassFile(){
    String name = "ProxyClass";
    byte[] data = ProxyGenerator.generateProxyClass(name,RealSubject.class.getInterfaces());
    try {
        FileOutputStream outputStream = new FileOutputStream(name+".class");
        outputStream.write(date);
        outputStream.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

生成的代理類.class文件如下:

public final class ProxyClass extends Proxy implements ISubject {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public ProxyClass(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void doSomething() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("DynamicProxy.JavaDynamic$ISubject").getMethod("doSomething");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

從生成的代理類文件中可以看出脆粥,代理類繼承Proxy類并實現(xiàn)了抽象接口ISubject的方法。代理類的構(gòu)造函數(shù)參數(shù)是傳入進來的InvocationHanlde實例影涉,并且每個方法都調(diào)用了super.h.invoke(this,m3,(Object[]null));而h就是Proxy中我們傳入的ProxyInvocationHandler對象变隔,invoke方法就是我們實現(xiàn)InvocationHandler接口里面的方法。這樣蟹倾,最終動態(tài)代理生成的代理對象的每個方法都是集中在ProxyInvocationHandler中invoke方法中執(zhí)行匣缘,在invoke中用反射的方式調(diào)用了真實對象的方法。

7.3 創(chuàng)建動態(tài)代理的過程

通過上面的分析和生成的代理對象類.class文件鲜棠,動態(tài)代理的創(chuàng)建流程如下:

  1. 通過實現(xiàn)InvocationHandler接口創(chuàng)建自己的核心處理類: InvocationHandler handler = new ProxyInvocationHandler(...)肌厨。
  2. 通過Proxy類指定類加載器對象和接口創(chuàng)建動態(tài)代理類: Class<?> cl =Proxy.getProxyClass0(classLoader,new Class[]{...})。
  3. 通過反射獲取動態(tài)代理類的構(gòu)造函數(shù)豁陆,其參數(shù)為InvocationHandler.class柑爸。
    Constructor<?> cons = cl.getConstructor(new Class[]{InvocationHandler.class};
  4. 通過構(gòu)造函數(shù)生成代理對象實例,將傳進來的invocationHandler實例作為參數(shù)盒音。
    Object Proxy = cons.newInstance(new Object[]{h});
  5. 生成的代理對象類繼承Proxy類并實現(xiàn)了ISubject接口表鳍,實現(xiàn)的ISubject方法實際調(diào)用的是構(gòu)造函數(shù)傳進去的invocationHandler實例的invoke方法。

8. 靜態(tài)代理和動態(tài)代理的比較

8.1 靜態(tài)代理的優(yōu)缺點

  • 靜態(tài)代理代碼實現(xiàn)邏輯簡單祥诽,易于理解譬圣。
  • 當(dāng)需要拓展抽象接口時,所有的實現(xiàn)類需要實現(xiàn)這個方法雄坪,所有的代理類也需要實現(xiàn)這個方法厘熟,且要為每一種方法都進行代理,導(dǎo)致代碼維護難度加大诸衔。

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

  • 動態(tài)代理代碼實現(xiàn)邏輯比靜態(tài)代理復(fù)雜盯漂。
  • 接口的所有方法都集中于InvocationHandler的invoke執(zhí)行,方便在接口增加方法時代碼的維護難度
  • 動態(tài)代理只支持interface代理笨农,比靜態(tài)代理局限性大就缆。(動態(tài)代理之所以僅支持interface代理,是由于整個java的單繼承機制決定的谒亦,通過動態(tài)代理生成的每個代理類都繼承了Proxy基類竭宰,導(dǎo)致其只能通過interface代理形式實現(xiàn))

9. 代理模式的種類

根據(jù)代理模式的適用范圍,可以分為遠程代理份招、虛擬代理切揭、保護代理等,本文就不在贅述了锁摔。

10. 總結(jié)

最后廓旬,一句話總結(jié)代理模式:“ 使用間接對象來控制真實對象訪問的一種策略 ”!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谐腰,一起剝皮案震驚了整個濱河市孕豹,隨后出現(xiàn)的幾起案子涩盾,更是在濱河造成了極大的恐慌,老刑警劉巖励背,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件春霍,死亡現(xiàn)場離奇詭異,居然都是意外死亡叶眉,警方通過查閱死者的電腦和手機址儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衅疙,“玉大人莲趣,你說我怎么就攤上這事×陡颍” “怎么了妖爷?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長理朋。 經(jīng)常有香客問我,道長绿聘,這世上最難降的妖魔是什么嗽上? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮熄攘,結(jié)果婚禮上兽愤,老公的妹妹穿的比我還像新娘。我一直安慰自己挪圾,他們只是感情好浅萧,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哲思,像睡著了一般洼畅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棚赔,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天帝簇,我揣著相機與錄音,去河邊找鬼靠益。 笑死丧肴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胧后。 我是一名探鬼主播芋浮,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壳快!你這毒婦竟也來了纸巷?” 一聲冷哼從身側(cè)響起镇草,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎何暇,沒想到半個月后陶夜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡裆站,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年条辟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏胯。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡羽嫡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肩袍,到底是詐尸還是另有隱情杭棵,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布氛赐,位于F島的核電站魂爪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏艰管。R本人自食惡果不足惜滓侍,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牲芋。 院中可真熱鬧撩笆,春花似錦、人聲如沸缸浦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裂逐。三九已至歹鱼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間絮姆,已是汗流浹背醉冤。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篙悯,地道東北人蚁阳。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像鸽照,于是被迫代替她去往敵國和親螺捐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

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