什么是代理模式?
其實代理和我們的生活息息相關铣鹏,簡單來說就是:中介敷扫,比如我們要租房子,我們找中介公司就可以了吝沫,比如鏈家呻澜,自如等,又比如我們去飯店吃飯惨险,我們通過服務員點菜,而不是直接向廚師要等等脊髓;
因此辫愉,代理模式的定義為:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下将硝,一個對象不適合或者不能直接引用另一個對象恭朗,而代理對象可以在客戶端和目標對象之間起到中介的作用。
從定義里我們知道代理中有三個角色:
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)代理是我們在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)代理浪箭,動態(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)代理的創(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源碼提供支持跟畅,感謝小伙伴們閱讀庇忌;