android常用設計模式之代理設計模式及動態(tài)代理原理

定義:代理模式屬結構型設計模式比勉。為其他對象提供一種代理以控制對這個對象的訪問劳较。

代理模式結構圖

代理類結構圖.jpg

在代理模式中有如下角色:

  • ISubject: 抽象主題類,聲明真實主題與代理的共同接口方法浩聋。
  • RealSubject:真實主題類,代理類所代表的真實主題观蜗。客戶端通過代理類間接地調用真實主題類的方法衣洁。
  • Proxy:代理類墓捻,持有對真實主題類的引用,在其所實現(xiàn)的接口方法中調用真實主題類中相應的接口方法執(zhí)行坊夫。

1. 簡單實現(xiàn)代碼

public interface IShop {
    void buy();
}

public class BuyProxy implements IShop {

    private IShop mShop;

    public BuyProxy(IShop shop){
        mShop = shop;
    }

    @Override
    public void buy() {
        mShop.buy();
    }
}

public class Customer implements IShop {
    @Override
    public void buy() {
        System.out.print("顧客購物");
    }
}

public class BuyProxyTest {
    private Customer mCustomer;
    private BuyProxy mBuyProxy;
    @Before
    public void setUp() throws Exception {
        mCustomer = new Customer();
        mBuyProxy = new BuyProxy(mCustomer);
    }

    @Test
    public void buy() throws Exception {
        mBuyProxy.buy();
    }

}

2.動態(tài)代理

從編碼角度來說砖第,代理模式分為靜態(tài)代理和動態(tài)代理。上面的例子是靜態(tài)代理环凿,在代碼運行前就已經(jīng)存在了代理類的class編譯文件梧兼;而動態(tài)代理則是在代碼運行時通過反射來動態(tài)生成類并確定代理誰。Java提供動態(tài)代理接口InvocationHandler智听,實現(xiàn)該接口需要重寫invoke方法羽杰。
下面用動態(tài)代理實現(xiàn)上面的例子,代碼如下:

//動態(tài)代理類
public class DynamicBuyProxy implements InvocationHandler {
    IShop shop;

    public DynamicBuyProxy setShop(IShop shop) {
        this.shop = shop;
        return this;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object resutl = method.invoke(shop, args);
        if("buy".equals(method.getName())){
            System.out.println("---通過動態(tài)代理購買---");
        }
        return null;
    }
}

//動態(tài)代理模式單元測試類
public class DynamicBuyProxyTest {
    DynamicBuyProxy mDynamicProxy;
    IShop mCustomer;
    ClassLoader mClassLoader;
    @Before
    public void setUp() throws Exception {
        mCustomer = new Customer();
        mDynamicProxy = new DynamicBuyProxy();
        mDynamicProxy.setShop(mCustomer);
        mClassLoader = mCustomer.getClass().getClassLoader();
    }

    @Test
    public void invoke() throws Exception {
        IShop proxyer = (IShop) Proxy.newProxyInstance(mClassLoader, new Class[]{IShop.class}, mDynamicProxy);
        proxyer.buy();
        System.out.println("proxyer.classname:"+proxyer.getClass().getName()); //Attend01
    }
}

注意上面代碼注釋Attend01處到推, 我們輸出了動態(tài)代理為我們生成的代理類對象類型考赛。
執(zhí)行單元測試后結果如下:


dynamicproxy.png

意料之中的是代理類正常的輸出了我們想要的代理類邏輯。
而代理類類型卻出乎我們意料com.sun.proxy.$Proxy5莉测,從這里引出它的原理颜骤。

原理

實際上通過 Proxy.newProxyInstance 創(chuàng)建的代理對象是在jvm運行時動態(tài)生成的一個對象,它并不是我們的InvocationHandler類型捣卤,也不是我們定義的那組接口的類型忍抽,而是在運行是動態(tài)生成的一個對象八孝,并且命名方式都是這樣的形式,以$開頭鸠项,proxy為中唆阿,最后一個數(shù)字表示對象的標號。
下面來看它的源碼:

import java.lang.reflect.InvocationHandler;   
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;   
import java.lang.reflect.UndeclaredThrowableException;  
   
public final class $Proxy0 extends Proxy implements IShop {  
    private static Method m1;  
    private static Method m0;  
    private static Method m3;  
    private static Method m2;  
  
    static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals",  
                    new Class[] { Class.forName("java.lang.Object") });  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                    new Class[0]);  
            m3 = Class.forName("com.zyl.designpatterns.structuralpatterns.proxy.IShop").getMethod("buy",  
                    new Class[0]);  
            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  
        } catch (NoSuchMethodException nosuchmethodexception) {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        } catch (ClassNotFoundException classnotfoundexception) {  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    }  
  
    public $Proxy0(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }  
  
    @Override  
    public final boolean equals(Object obj) {  
        try {  
            return ((Boolean) super.h.invoke(this, m1, new Object[] { obj }))  
                    .booleanValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public final int hashCode() {  
        try {  
            return ((Integer) super.h.invoke(this, m0, null)).intValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public final String toString() {  
        try {  
            return (String) super.h.invoke(this, m2, null);  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public void buy() {  
        try {  
            super.h.invoke(this, m3, null);  //Attend02
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
  
    }  
}  

可以看到上面代碼注釋Attend02中h實際上就是我們的InvocationHandler接口的實現(xiàn)類DynamicBuyProxy锈锤,調用它的invoke方法就是調用了我們InvocationHandler.invoke()方法。
是不是豁然開朗了闲询,實際上它就是JVM為我們生成了一個代理類久免,靜態(tài)代理是我們編譯之前寫好的, 而動態(tài)代理是由JVM根據(jù)我們提供的接口為我們動態(tài)生成的扭弧。

場景

是不是感覺用它的地方不多呢阎姥,但是實際上動態(tài)代理場景有很多,比如Spring的核心AOP鸽捻、Android最近大火的Retrofit等等呼巴。

優(yōu)點

  • 真實主題類就是實現(xiàn)實際的業(yè)務邏輯,不用關心其他的非本職工作御蒲。
  • 任何主題類隨時都會該發(fā)生變化衣赶,但是因為它實現(xiàn)了公共接口,所以代理類可以不做任何修改就能夠使用厚满。

如果對動態(tài)代理的作用還是比較模糊, 建議看看這篇知乎的解答Java 動態(tài)代理作用是什么府瞄?

代碼已上傳github

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碘箍,隨后出現(xiàn)的幾起案子遵馆,更是在濱河造成了極大的恐慌,老刑警劉巖丰榴,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件货邓,死亡現(xiàn)場離奇詭異,居然都是意外死亡四濒,警方通過查閱死者的電腦和手機换况,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峻黍,“玉大人复隆,你說我怎么就攤上這事∧飞” “怎么了挽拂?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長骨饿。 經(jīng)常有香客問我亏栈,道長台腥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任绒北,我火速辦了婚禮黎侈,結果婚禮上,老公的妹妹穿的比我還像新娘闷游。我一直安慰自己峻汉,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布脐往。 她就那樣靜靜地躺著休吠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪业簿。 梳的紋絲不亂的頭發(fā)上瘤礁,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音梅尤,去河邊找鬼柜思。 笑死,一個胖子當著我的面吹牛巷燥,可吹牛的內(nèi)容都是我干的赡盘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼矾湃,長吁一口氣:“原來是場噩夢啊……” “哼亡脑!你這毒婦竟也來了?” 一聲冷哼從身側響起邀跃,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤霉咨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拍屑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體途戒,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年僵驰,在試婚紗的時候發(fā)現(xiàn)自己被綠了喷斋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贬媒,到底是詐尸還是另有隱情,我是刑警寧澤顽腾,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站诺核,受9級特大地震影響抄肖,放射性物質發(fā)生泄漏久信。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一漓摩、第九天 我趴在偏房一處隱蔽的房頂上張望裙士。 院中可真熱鬧,春花似錦管毙、人聲如沸腿椎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酥诽。三九已至,卻和暖如春皱埠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咖驮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工边器, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人托修。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓忘巧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親睦刃。 傳聞我的和親對象是個殘疾皇子砚嘴,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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