Java代理模式分析總結(jié)

動(dòng)機(jī)

學(xué)習(xí)動(dòng)機(jī)來源于RxCache,在研究這個(gè)庫的源碼時(shí)么抗,被這個(gè)庫的設(shè)計(jì)思路吸引了干奢,該庫的原理就是通過動(dòng)態(tài)代理和Dagger的依賴注入,實(shí)現(xiàn)Android移動(dòng)端Retrofit的緩存功能庐舟。

既然在項(xiàng)目中嘗試使用這個(gè)庫猪半,當(dāng)然要從設(shè)計(jì)的角度思考作者的思路,動(dòng)態(tài)代理必然涉及到Java的反射在旱,既然是反射摇零,性能當(dāng)然會(huì)有所降低,那么是否有更好的思路呢桶蝎,使用動(dòng)態(tài)代理的優(yōu)勢有哪些驻仅?

關(guān)于動(dòng)態(tài)代理,百度上面的資料數(shù)不勝數(shù)登渣,今天也借鑒其他前輩的學(xué)習(xí)總結(jié)噪服,自己實(shí)踐一次代理的實(shí)現(xiàn)。

靜態(tài)代理

代理分為動(dòng)態(tài)代理和靜態(tài)代理胜茧,我們先看靜態(tài)代理的代碼:

我們首先定義一個(gè)接口:

public interface Subject {

    void enjoyMusic();

}

我們接下來實(shí)現(xiàn)一個(gè)Subject的實(shí)現(xiàn)類:

public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }
}

在不考慮代理模式的情況下粘优,我們調(diào)用Subject的真實(shí)對(duì)象,我們代碼中必然是這樣:

    @Test
    public void testNoProxy() throws Exception {
        Subject subject= new RealSubject();
        subject.enjoyMusic();
    }

上面是我們的業(yè)務(wù)代碼呻顽,我們這樣使用當(dāng)然沒有問題雹顺,但是我們需要考慮的一點(diǎn)是,如果我們的業(yè)務(wù)代碼中多次引用了這個(gè)類廊遍,并且在之后的版本迭代中嬉愧,我們需要修改(或者替換)這個(gè)類,我們需要在引用這個(gè)對(duì)象的代碼處進(jìn)行修改——也就是說我們需要修改業(yè)務(wù)代碼喉前。

這顯然不是良好的設(shè)計(jì)没酣,我們希望業(yè)務(wù)代碼不需要修改的前提下,進(jìn)行RealSubject的修改(或者替換)卵迂,這時(shí)我們可以通過代理模式裕便,創(chuàng)建一個(gè)代理類,從而達(dá)到控制RealSubject對(duì)象的引用 :

public class SubjectProxy implements Subject {
    private Subject subject = new RealSubject();
    @Override
    public void enjoyMusic() {
        subject.enjoyMusic();
    }
}

我們在業(yè)務(wù)代碼中通過代理類狭握,達(dá)到調(diào)用真實(shí)對(duì)象RealSubject的對(duì)應(yīng)方法:

@Test
public void staticProxy() throws Exception {
    SubjectProxy proxy = new SubjectProxy();

    proxy.enjoyMusic();
}

這就是靜態(tài)代理闪金,優(yōu)勢是顯然的,如果我們需要一個(gè)新的對(duì)象NewRealSubject代替RealSubject 應(yīng)用在業(yè)務(wù)中论颅,我們不需要修改業(yè)務(wù)代碼哎垦,而是只需要在代理類中,將代碼進(jìn)行簡單的替換:

private Subject subject = new RealSubject();//before

private Subject subject = new NewRealSubject();//after

同理恃疯,即使RealSubject類有所修改(比如說構(gòu)造函數(shù)添加新的參數(shù)依賴)漏设,我們也不需要在每一處業(yè)務(wù)代碼中添加一個(gè)新的參數(shù),只需要在代理類中今妄,對(duì)代理的真實(shí)對(duì)象進(jìn)行簡單修改即可郑口。

瑕疵

現(xiàn)在我們看到了靜態(tài)代理的優(yōu)勢鸳碧,但是還有一點(diǎn)需要我們?nèi)ニ伎迹S著項(xiàng)目中業(yè)務(wù)量的逐漸龐大犬性,真實(shí)對(duì)象類的功能可能越來越多:

//接口類
public interface Subject {

    void enjoyMusic();

    void enjoyFood();

    void enjoyBeer();
    
    //...甚至更多
}

//實(shí)現(xiàn)類
public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }

    @Override
    public void enjoyFood() {
        System.out.println("enjoyFood");
    }

    @Override
    public void enjoyBeer() {
        System.out.println("enjoyBeer");
    }

    //...甚至更多
}

這樣豈不是說明瞻离,我們的代理類也要這樣:

public class SubjectProxy implements Subject {

    private Subject subject = new RealSubject();

    @Override
    public void enjoyMusic() {
        subject.enjoyMusic();
    }

    @Override
    public void enjoyFood() {
        subject.enjoyFood();
    }

    @Override
    public void enjoyBeer() {
        subject.enjoyBeer();
    }
    
    //...甚至更多
}

靜態(tài)代理的話,確實(shí)如此乒裆,隨著真實(shí)對(duì)象的功能增多套利,不可避免的,代理對(duì)象的代碼也會(huì)隨之臃腫鹤耍,這是我們不希望看到的肉迫,我們更希望的是,即使真實(shí)對(duì)象的代碼量再繁重稿黄,我們的代理類也不要有太多的改動(dòng)和臃腫喊衫。

動(dòng)態(tài)代理

直接來看代碼,我們首先聲明一個(gè)接口和實(shí)現(xiàn)類:

public interface Subject {

    void enjoyMusic();

    void enjoyFood();

    void enjoyBeer();
}

public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }

    @Override
    public void enjoyFood() {
        System.out.println("enjoyFood");
    }

    @Override
    public void enjoyBeer() {
        System.out.println("enjoyBeer");
    }
}

這兩位是老朋友了,接下來我們實(shí)現(xiàn)一個(gè)動(dòng)態(tài)代理類:

public class DynamicProxy implements InvocationHandler {

    private Object subject;

    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    public Object bind() {
        return Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                subject.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(subject, args);
        return null;
    }
}

我們來看業(yè)務(wù)代碼:

@Test
public void dynamicProxy() throws Exception {
    RealSubject realSubject = new RealSubject();
    Subject proxy = (Subject) new DynamicProxy(realSubject).bind();

    System.out.println(proxy.getClass().getName());

    proxy.enjoyMusic();
    proxy.enjoyFood();
    proxy.enjoyBeer();
}

動(dòng)態(tài)代理中,我們可以看到旁瘫,最重要的兩個(gè)類:
Proxy 和 InvocationHandler
接下來分別對(duì)這兩個(gè)類的功能進(jìn)行描述.

InvocationHandler接口

我們先看一下Java的API文檔:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn)InvocationHandler這個(gè)接口,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)到了一個(gè)handler联四,當(dāng)我們通過代理對(duì)象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由InvocationHandler這個(gè)接口的 invoke 方法來進(jìn)行調(diào)用撑教。我們來看看InvocationHandler這個(gè)接口的唯一一個(gè)方法 invoke 方法:

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

proxy:  指代最終生成的代理對(duì)象
method:  指代調(diào)用真實(shí)對(duì)象的某個(gè)方法的Method對(duì)象
args:  指代的是調(diào)用真實(shí)對(duì)象某個(gè)方法時(shí)接受的參數(shù)

我們看到朝墩,我們的動(dòng)態(tài)代理類中,實(shí)現(xiàn)了InvocationHandler接口的invoke方法伟姐,這里面三個(gè)參數(shù)的作用很簡單收苏,以proxy.enjoyMusic();為例,參數(shù)1表示最終生成的代理對(duì)象愤兵,參數(shù)2表示enjoyMusic()這個(gè)方法對(duì)象鹿霸,參數(shù)3代表調(diào)用enjoyMusic()的參數(shù),此例中是沒有調(diào)用參數(shù)的秆乳。

有一個(gè)疑問是懦鼠,這個(gè)proxy對(duì)象是什么呢,是這個(gè)new DynamicProxy(realSubject)嗎屹堰?

當(dāng)然不是肛冶,我們可以看到,這個(gè)代理對(duì)象proxy實(shí)際上是調(diào)用bind()方法獲得的扯键,也就是說是通過這個(gè)方法獲得的:

Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
this);

Proxy類

先看JavaAPI:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy這個(gè)類的作用就是用來動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類睦袖,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個(gè)方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

  • loader:  一個(gè)ClassLoader對(duì)象荣刑,定義了由哪個(gè)ClassLoader對(duì)象來對(duì)生成的代理對(duì)象進(jìn)行加載
  • interfaces:  一個(gè)Interface對(duì)象的數(shù)組馅笙,表示的是我將要給我需要代理的對(duì)象提供一組什么接口伦乔,如果我提供了一組接口給它,那么這個(gè)代理對(duì)象就宣稱實(shí)現(xiàn)了該接口(多態(tài))董习,這樣我就能調(diào)用這組接口中的方法了
  • h:  一個(gè)InvocationHandler對(duì)象烈和,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對(duì)象在調(diào)用方法的時(shí)候,會(huì)關(guān)聯(lián)到哪一個(gè)InvocationHandler對(duì)象上

可以看到皿淋,Proxy這個(gè)類才會(huì)幫助我們生成相應(yīng)的代理類斥杜,它是如何知道我們需要生成什么類的代理呢?

看第二個(gè)參數(shù)沥匈,我們把Subject.class作為參數(shù)傳進(jìn)去時(shí),那么我這個(gè)代理對(duì)象就會(huì)實(shí)現(xiàn)了這個(gè)接口忘渔,這個(gè)時(shí)候我們當(dāng)然可以將這個(gè)代理對(duì)象強(qiáng)制類型轉(zhuǎn)化高帖,因?yàn)檫@里的接口是Subject類型,所以就可以將其轉(zhuǎn)化為Subject類型了畦粮。

我們看到散址,我在業(yè)務(wù)代碼中對(duì)生成的proxy進(jìn)行了打印:

System.out.println(proxy.getClass().getName());

//結(jié)果:
//$Proxy0

也就是說宣赔,通過 Proxy.newProxyInstance 創(chuàng)建的代理對(duì)象是在jvm運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)對(duì)象预麸,它并不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型儒将,而是在運(yùn)行是動(dòng)態(tài)生成的一個(gè)對(duì)象吏祸,并且命名方式都是這樣的形式,以$開頭钩蚊,proxy為中贡翘,最后一個(gè)數(shù)字表示對(duì)象的標(biāo)號(hào)。

到此砰逻,動(dòng)態(tài)代理的基本知識(shí)就告一段落了鸣驱。

參考資料:

java的動(dòng)態(tài)代理機(jī)制詳解:(對(duì)我?guī)椭艽螅孕母兄x蝠咆!)
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html

知乎:Java 動(dòng)態(tài)代理作用是什么踊东?
https://www.zhihu.com/question/20794107

本文sample代碼已托管github:

https://github.com/qingmei2/Samples-Android/tree/master/SampleProxy/app/src/test/java/com/qingmei2/sampleproxy

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刚操,隨后出現(xiàn)的幾起案子闸翅,更是在濱河造成了極大的恐慌,老刑警劉巖赡茸,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缎脾,死亡現(xiàn)場離奇詭異,居然都是意外死亡占卧,警方通過查閱死者的電腦和手機(jī)遗菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門联喘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辙纬,你說我怎么就攤上這事豁遭。” “怎么了贺拣?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蓖谢,是天一觀的道長。 經(jīng)常有香客問我譬涡,道長闪幽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任涡匀,我火速辦了婚禮盯腌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陨瘩。我一直安慰自己腕够,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布舌劳。 她就那樣靜靜地躺著帚湘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪甚淡。 梳的紋絲不亂的頭發(fā)上大诸,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音材诽,去河邊找鬼底挫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脸侥,可吹牛的內(nèi)容都是我干的建邓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼睁枕,長吁一口氣:“原來是場噩夢啊……” “哼官边!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起外遇,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤注簿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后跳仿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诡渴,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年菲语,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妄辩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惑灵。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖眼耀,靈堂內(nèi)的尸體忽然破棺而出英支,到底是詐尸還是另有隱情,我是刑警寧澤哮伟,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布干花,位于F島的核電站,受9級(jí)特大地震影響楞黄,放射性物質(zhì)發(fā)生泄漏池凄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一鬼廓、第九天 我趴在偏房一處隱蔽的房頂上張望修赞。 院中可真熱鬧,春花似錦桑阶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眷篇,卻和暖如春萎河,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕉饼。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工虐杯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昧港。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓擎椰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親创肥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子达舒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2叹侄、Retrofit解析...
    隔壁老李頭閱讀 3,236評(píng)論 2 10
  • 1 場景問題# 1.1 訪問多條數(shù)據(jù)## 考慮這樣一個(gè)實(shí)際應(yīng)用:要一次性訪問多條數(shù)據(jù)。 這個(gè)功能的背景是這樣的趾代;在...
    七寸知架構(gòu)閱讀 2,997評(píng)論 1 52
  • 本篇文章繼續(xù)介紹Java反射機(jī)制,不同的是側(cè)重于介紹動(dòng)態(tài)代理撒强。動(dòng)態(tài)代理是代理模式中的一種笙什,是通過Java反射機(jī)制來...
    Android進(jìn)階與總結(jié)閱讀 607評(píng)論 0 0
  • 冬日的夜晚 風(fēng)瀟瀟的吹著 幾顆星零散 掛在天上 樹嘯驚魂 好冷 一顆流星倏忽而逝 降落在無際的宇宙里 只留下一道長...
    六月天氣閱讀 223評(píng)論 22 27
  • 然則輪回有情如何生起迷亂? 本始基清凈境界為無明所障得湘,即成阿賴耶,是為“癡”之自性顿仇;于“癡”之境界中,業(yè)力發(fā)動(dòng)臼闻,此...
    Zhenzen閱讀 199評(píng)論 0 0