Java代理模式

什么是代理模式?

其實代理和我們的生活息息相關铣鹏,簡單來說就是:中介敷扫,比如我們要租房子,我們找中介公司就可以了吝沫,比如鏈家呻澜,自如等,又比如我們去飯店吃飯惨险,我們通過服務員點菜,而不是直接向廚師要等等脊髓;
因此辫愉,代理模式的定義為:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下将硝,一個對象不適合或者不能直接引用另一個對象恭朗,而代理對象可以在客戶端和目標對象之間起到中介的作用。
從定義里我們知道代理中有三個角色:
1依疼、被代理對象(我)
2痰腮、代理對象(中介、服務員)
3律罢、目標對象(房東膀值、廚師)

在我們的程序中,代理模式有三種角色:
1误辑、抽象角色:通過接口或抽象類聲明真實角色實現的業(yè)務方法沧踏。
2、代理角色:實現抽象角色巾钉,是真實角色的代理翘狱,通過真實角色的業(yè)務邏輯方法來實現抽象方法,并可以附加自己的操作砰苍。
3赚导、真實角色:實現抽象角色,定義真實角色所要實現的業(yè)務邏輯辟癌,供代理角色調用。

代理模式的類圖如下:


代理模式類圖

靜態(tài)代理

靜態(tài)代理是由程序員編寫的代理類处面,并在程序運行前就編譯好的魂角,而不是由程序動態(tài)產生代理類,這就是所謂的靜態(tài)野揪。
舉個例子斯稳,以去餐廳吃飯為例迹恐,首先定義一個抽象角色(接口)
抽象角色

public interface Subject {
    void eat();
}

真實角色

public class Person implements Subject {
    @Override
    public void eat() {
        System.out.println("我餓了憎茂,我要吃飯");
    }
}

代理角色

public class ProxyWaiter implements Subject {
    private Person person;

    public ProxyWaiter(Person p) {
        this.person = p;
    }

    @Override
    public void eat() {
        person.eat();
        System.out.println("我是服務員竖幔,我負責點菜");
    }
}

使用

  Person person=new Person();
  ProxyWaiter proxyWaiter=new ProxyWaiter(person);
  proxyWaiter.eat();

控制臺輸出:


靜態(tài)代理輸出

靜態(tài)代理比較簡單拳氢,比較適用于控制對實際對象的訪問饿幅,比如我們想在訪問實際對象前或后加入其它操作或者過濾等栗恩,都可以用靜態(tài)代理來實現,但是也會有一些問題磕秤,就是如果我們的代理的對象方法較多時市咆,如果我們每一個方法前或后都插入相同操作的話蒙兰,就會產生冗余代碼,程序的可讀性降低采缚,同時也不利于維護扳抽,所以這是我們就要用到動態(tài)代理了贸呢;

動態(tài)代理

我們知道,靜態(tài)代理是我們在java虛擬機編譯之前就寫好的類楞陷,而動態(tài)代理朴沿,代理類并不是在java代碼中定義的赌渣,而是在運行時根據我們的代碼“指示”動態(tài)生成的坚芜,相比于靜態(tài)代理钞楼, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數進行統(tǒng)一的處理圆凰,而不用修改每個代理類中的方法。
我們還是以上面的例子為例闪水,來看一下動態(tài)代理的實現過程
Subject接口類和Person類和上例一樣,動態(tài)代理的核心是要實現InvocationHandler接口球榆,這個類隨后會詳細介紹持钉,實現代碼如下:

public class RentingInvocationHandler<T> implements InvocationHandler {

    T target;//持有一個被代理的對象

    public RentingInvocationHandler(T t) {
        this.target = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是動態(tài)代理每强,代理執(zhí)行" + method.getName() + "方法");
        method.invoke(target, args);
        return null;
    }
}

然后執(zhí)行動態(tài)代理

InvocationHandler waiterHandler = new RentingInvocationHandler<Subject>(person);
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, waiterHandler);
subject.eat();

執(zhí)行結果:


動態(tài)代理執(zhí)行結果

接下來我們來分析一下動態(tài)代理浪箭,動態(tài)代理的代理類創(chuàng)建分為4步:
第一步:創(chuàng)建一個與代理對象相關聯的InvocationHandler

InvocationHandler subHandler = new RentingInvocationHandler<Subject>(person);

也就是我們上例中的RentingInvocationHandler脆烟,它實現了InvocationHandler接口驼抹;
InvocationHandler是調用處理器拜鹤,每一個被代理的對象都與一個InvocationHandler關聯敏簿,動態(tài)代理中動態(tài)生成的代理類惯裕,通過InvocationHandler,來調用實際對象的方法撑刺;

第二步:使用Proxy類的getProxyClass方法生成一個動態(tài)代理類對象subjectProxyClass够傍;

Class<?> subjectProxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(), new Class<?>[] {Subject.class});

我們知道冕屯,jvm虛擬機在運行時通過讀取磁盤上的類文件安聘,在jvm內存中生成該類文件的Class對象搞挣,通過該Class對象我們可以通過反射來獲取該類的構造函數等囱桨;所以以上方法就是在jvm內存中生成我們動態(tài)代理類的Class對象舍肠;所以下一步就是獲取動態(tài)代理類的構造函數了翠语;

第三步:很明顯,我們通過Class對象來直接獲取動態(tài)代理對象的構造函數点骑;

Constructor<?> constructor = subjectProxyClass.getConstructor(InvocationHandler.class);

第四步:通過構造函數黑滴,來創(chuàng)建一個動態(tài)代理對象實例subProxy;

Subject subProxy = (Subject) cons.newInstance(subHandler);

到此紧索,一個動態(tài)對象就創(chuàng)建完畢袁辈,當然,上面的四個步驟java中已經為我們封裝珠漂,我們直接使用一下代碼即可

InvocationHandler subHandler = new RentingInvocationHandler<Subject>(person);
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, subHandler);

下面為動態(tài)代理的原理圖:


動態(tài)代理

接下來我們根據原理圖再來總結一下動態(tài)代理的創(chuàng)建過程:
1晚缩、首先我們定義個抽象角色即接口,然后創(chuàng)建一個實際對象來實現這個接口媳危,即我們的被代理類(Person)
2荞彼、創(chuàng)建一個InvocationHandler(調用處理器),即我們在上例中的RentingInvocationHandler待笑,它實現了InvocationHandler接口卿泽,并在內部持有一個被代理類的實例;
3滋觉、使用Proxy類的getProxyClass方法生成一個動態(tài)代理類對象subjectProxyClass椎侠,該Class對象主要用來獲取動態(tài)代理類的構造器丐吓;
4、通過上面的Class對象獲取帶InvocationHandler參數的構造器咳促,因為我們在上述第2步已經創(chuàng)建了一個InvocationHandler,將我們創(chuàng)建好的InvocationHandler通過該構造器來構造生成動態(tài)代理對象,這樣就創(chuàng)建了一個動態(tài)代理類$Proxy1;
5、因為動態(tài)代理類$Proxy1中我們傳入了RentingInvocationHandler,而我們的RentingInvocationHandler中又持有被代理類的實例,所以當我們使用動態(tài)代理類調用eat()方法時,是通過RentingInvocationHandler調用了它內部的核心方法invoke()想罕,在invoke方法中我們將被代理對象的實例傳進去楼镐,最終調用了被代理對象的方法秉宿,如上原理圖所示;

至此動態(tài)代理的原理就分析結束了,本文只對動態(tài)代理做了最上層的原理分析仍稀,并未深入源碼層進行更深的研究千康,比如動態(tài)代理對象是如何生成,InvocationHandler的invoke方法是如何調用被代理類的方法,感興趣的小伙伴可以自行去查看源碼,本文主要目的是理解動態(tài)代理的原理,并為接下來的閱讀Retrofit源碼提供支持跟畅,感謝小伙伴們閱讀庇忌;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末捎迫,一起剝皮案震驚了整個濱河市彰导,隨后出現的幾起案子,更是在濱河造成了極大的恐慌堰燎,老刑警劉巖掏父,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赊淑,死亡現場離奇詭異组哩,居然都是意外死亡黍衙,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來智袭,“玉大人奔缠,你說我怎么就攤上這事『鹨埃” “怎么了校哎?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞳步。 經常有香客問我闷哆,道長,這世上最難降的妖魔是什么单起? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任抱怔,我火速辦了婚禮,結果婚禮上嘀倒,老公的妹妹穿的比我還像新娘屈留。我一直安慰自己,他們只是感情好测蘑,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布灌危。 她就那樣靜靜地躺著,像睡著了一般碳胳。 火紅的嫁衣襯著肌膚如雪勇蝙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天挨约,我揣著相機與錄音味混,去河邊找鬼。 笑死诫惭,一個胖子當著我的面吹牛惜傲,可吹牛的內容都是我干的。 我是一名探鬼主播贝攒,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼时甚!你這毒婦竟也來了隘弊?” 一聲冷哼從身側響起哈踱,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梨熙,沒想到半個月后开镣,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡咽扇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年邪财,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片质欲。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡树埠,死狀恐怖,靈堂內的尸體忽然破棺而出嘶伟,到底是詐尸還是另有隱情怎憋,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布九昧,位于F島的核電站绊袋,受9級特大地震影響,放射性物質發(fā)生泄漏铸鹰。R本人自食惡果不足惜癌别,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹋笼。 院中可真熱鬧展姐,春花似錦、人聲如沸姓建。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽速兔。三九已至墅拭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涣狗,已是汗流浹背谍婉。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镀钓,地道東北人穗熬。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像丁溅,于是被迫代替她去往敵國和親唤蔗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內容