如果要說設(shè)計(jì)模式中哪種模式在生活中最常見工碾,那么代理模式是當(dāng)仁不讓的。比如踪央,你一覺睡到中午不想去食堂打飯臀玄,于是你委托阿黃讓他幫你把飯帶回來,這就產(chǎn)生了一種代理關(guān)系畅蹂,其中委托人是你健无,代理人是阿黃,而事件就是打飯液斜,你為了足不出戶就能吃到午飯而想到的這餿主意累贤,就是一種代理方法。又比如旗唁,你女朋友從外地回來下飛機(jī)了畦浓,而你又因?yàn)橐恍┎豢擅枋龅氖虑椴荒苋ソ訖C(jī),這時候你就可以叫你的好基友去機(jī)場幫你把女朋友接回來啊检疫,于是你和你好基友就形成了一種代理關(guān)系讶请,委托人是你,代理人是好基友屎媳,而事件就是接女朋友回來夺溢,這種很有可能戴綠帽子的方法就是一種代理方法。然后烛谊,將這些代理方法總結(jié)規(guī)律风响,抽象出來,上升到一個通用層面的高度丹禀,就是代理模式状勤。什么是代理模式呢?通俗地講双泪,有些事情本應(yīng)該是你做的持搜,但是你不想做或者不方便做的時候,叫別人替你完成焙矛。生活中種種“幫幫忙”的事件葫盼,都有代理模式的影子,所以說代理模式在生活中很常見村斟。
靜態(tài)代理
那么我們?nèi)绾问褂么a來描述代理模式呢贫导?我們將上面的例子總結(jié)規(guī)律抛猫,抽象出來,就大概有下面兩點(diǎn):
- 委托人和代理人都要完成同樣的事情(共同實(shí)現(xiàn)事件的接口)孩灯。
- 代理人要做的這件事其實(shí)是委托人請求的闺金,也就是說,代理人本身并不知道如何做這件事峰档,只有委托人告訴他了掖看,代理人才知道怎么做。所以面哥,代理人需要持有委托人的引用,在做這件事的時候就按照委托人的意愿去做毅待。
就比如第一個例子尚卫,“打飯”這件事情,由于你和阿黃都能做尸红,所以可以寫成一個接口吱涉,讓你和阿黃都能實(shí)現(xiàn)。
/**
* 一個抽象的午飯類, 具體要吃什么樣午飯, 看子類的心情外里。
*/
public interface Lunch {
/**
* 吃什么樣的午飯, 糖醋排骨?宮保雞丁?怎爵。。盅蝗。鳖链。
*/
void catagory();
}
然后就要創(chuàng)建“你”這個對象了,由于你要吃午飯墩莫,就需要實(shí)現(xiàn)Lunch
類:
/**
* 你, 需要考慮午飯吃什么
*/
public class You implements Lunch {
@Override
public void catagory() {
// 你想吃宮保雞丁
System.out.println("我要吃宮保雞丁蓋飯");
}
}
然后你很懶啊芙委,需要找阿黃這個代理人幫你帶午飯回來,所以還需要一個代理人對象狂秦,注意灌侣,由于代理事件是委托人(“你”)下發(fā)的,所以代理人對象里面還需要持有委托人的應(yīng)用:
/**
* 代理人, 由于需要幫委托人帶飯, 也必須實(shí)現(xiàn)午飯接口
*/
public class ProxyMan implements Lunch {
/**
* 必須是要有委托人的存在才行,沒有委托人還需要代理人干嘛?
*/
private You mYou;
/**
* 在創(chuàng)建代理人對象的時候就傳入一個委托人進(jìn)來裂问,這樣就使得代理人可以持有委托人的引用了侧啼。
* 當(dāng)然這只是一種方法,你也可以使用其他方法讓代理人持有委托人的引用堪簿。
*/
public ProxyMan(You you) {
mYou = you;
}
@Override
public void catagory() {
// "你"要讓代理人幫你做的事情痊乾。
mYou.catagory();
}
}
OK,大功告成戴甩,然后我們創(chuàng)建一個場景(客戶端)來模擬一下這個事情:
public class Proxy {
public static void main(String[] args) {
// 創(chuàng)建一個委托人
You you = new You();
// 創(chuàng)建一個代理人, 可以是阿黃, 也可以是其他人
ProxyMan aHuang = new ProxyMan(you);
// 讓阿黃幫你帶飯符喝。
aHuang.catagory();
}
}
以上便是代理模式的一種模版,結(jié)合代碼甜孤,我們可以總結(jié)出代理模式的類圖如下:
好了协饲,扯了這么多畏腕,大概對代理模式有一個印象了。是時候給代理模式來下一個正式的定義了:為其他對象提供一種代理以控制對這個對象的訪問茉稠。
另外描馅,我們現(xiàn)在看到的代理模式,又稱之為靜態(tài)代理而线,因?yàn)榇眍愂切枰覀冏约菏謩泳帉懙拿郏竺嫖覀儠務(wù)勅绾尾挥檬謩泳帉懘眍悾瑏韺?shí)現(xiàn)代理模式膀篮。
靜態(tài)代理的作用
當(dāng)然嘹狞,你會說,這種小事誓竿,委托類自己就能完成的嘛磅网,非要多增加一些類干嘛。的確筷屡,如果單是這種簡單的例子涧偷,使用任何設(shè)計(jì)模式都是多此一舉。但在實(shí)際工作中毙死,使用代理模式就會有如下的有點(diǎn):
- 解耦燎潮。這幾乎是設(shè)計(jì)模式解決的根本問題。在這里扼倘,委托類可以專心地做好自己确封,麻煩的事情讓別人代勞。
- 攔截再菊、擴(kuò)展方法隅肥。代理類在實(shí)現(xiàn)接口方法的時候,除了直接調(diào)用委托類的方法外袄简,還可以在不修改委托類的情況下腥放,增加一些其他的功能,比如順便帶瓶可樂回來绿语?
為了能讓我們的代理人阿黃能帶瓶可樂回來秃症,我們只需要在代理類中對catagory()
方法增加一個“帶瓶可樂回來”的功能即可:
public class ProxyMan implements Lunch {
private You mYou;
public ProxyMan(You you) {
mYou = you;
}
@Override
public void catagory() {
// "你"要讓代理人幫你做的事情。
mYou.catagory();
// 打完飯后再去買瓶可樂
System.out.println("嗯吕粹,買瓶可樂來喝");
}
}
現(xiàn)在你應(yīng)該能明顯地感受到代理類的作用了种柑。對,就是攔截方法匹耕、對委托的方法進(jìn)行加工處理聚请。
動態(tài)代理
然后有一天,當(dāng)你中午醒來的時候,發(fā)現(xiàn)寢室里除了你以外空無一人驶赏,你為了雙倍經(jīng)驗(yàn)雙倍金幣所以不想浪費(fèi)時間下樓去食堂打飯炸卑,但是你餓啊,要吃飯啊煤傍,這時你就只有等寢室再進(jìn)來個人盖文,讓他幫你帶飯(假設(shè)你不會叫外賣)。但是這會有一個問題蚯姆,之前一醒來就可以讓阿黃幫你帶飯五续,是因?yàn)榘ⅫS在你醒來之前就在寢室的(程序編譯階段就存在一個代理類),并且設(shè)定了阿黃“能打飯”(實(shí)現(xiàn)了Lunch
接口)這個屬性龄恋,所以簡單的幾行代碼就能搞定疙驾。但是現(xiàn)在,你醒來之后(程序開始運(yùn)行)郭毕,發(fā)現(xiàn)并沒有人可以幫你帶飯(沒有一個在編譯階段就實(shí)現(xiàn)了Lunch
接口的代理類)荆萤,在你等待的過程中也并不知道會是誰進(jìn)來,阿黃铣卡,阿貓還是阿三(我們需要在程序運(yùn)行的時候動態(tài)生成一個對象)?而且最重要的是偏竟,進(jìn)來的那個家伙能幫你帶飯才行(運(yùn)行階段生成的對象要實(shí)現(xiàn)Lunch
接口)煮落。
那么我們再梳理一遍,現(xiàn)在的你應(yīng)該怎么做才能吃到午飯呢踊谋?
- 首先蝉仇,“你”這個對象和“午飯”這個事件是必須存在的。
- 其次殖蚕,創(chuàng)建一個代理人轿衔。由于這個代理人是幫你做事情的,所以創(chuàng)建這個代理人的方法必須和“你”這個對象以及“午飯”事件接口相關(guān)睦疫。
- 最后害驹,讓代理人去幫你打飯回來。
讓我們來用代碼實(shí)現(xiàn)一下蛤育,You
這個委托類和Lunch
接口是要保留的宛官,而ProxyMan
這個代理類就不需要了。在場景中(client客戶端)瓦糕,我們可以使用代理的一個靜態(tài)方法newProxyInstance
來創(chuàng)建一個代理對象底洗,這個代理對象當(dāng)然是實(shí)現(xiàn)了Lunch
這個接口的。那么client中的代碼如下:
public static void main(String[] args) {
// 創(chuàng)建一個委托人
You you = new You();
// 創(chuàng)建一個代理人, 第一個參數(shù)是委托類的ClassLoader, 第二個參數(shù)是要實(shí)現(xiàn)的接口數(shù)組,
// 第三個參數(shù)是一個匿名內(nèi)部類, 對于代理人將要做的任何事情做攔截處理咕娄。
Lunch somebodyCouldTakeLunch = (Lunch) Proxy.newProxyInstance(you.getClass().getClassLoader(), new Class[]{Lunch.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(you, args);
}
});
// 動態(tài)生成的代理人去帶飯了
somebodyCouldTakeLunch.catagory();
}
當(dāng)動態(tài)生成的代理人對象調(diào)用方法去帶飯時(somebodyCouldTakeLunch.catagory()
)亥揖,就會觸發(fā)匿名內(nèi)部類InvocationHandler
中的invoke()
方法,這里面沒有做其他處理圣勒,直接返回了“你”這個對象的對應(yīng)方法费变。也就是說摧扇,動態(tài)生成的代理人是安裝你的要求帶回來宮爆雞丁蓋飯!
一個簡單的動態(tài)代理就這么完成了胡控。然而動態(tài)代理是想當(dāng)強(qiáng)大的扳剿,為了顯示這種強(qiáng)大,這里再舉一個例子昼激,List集合大家都用過庇绽,這個接口有很多方法,如果我們想要屏蔽remove
這個方法橙困,這時我們就可以使用動態(tài)代理瞧掺,來生成一個代理類List,每當(dāng)調(diào)用到代理類的remove()
方法時凡傅,就跑出異常辟狈,實(shí)現(xiàn)代碼如下:
public static List getList(List list) {
return (List) Proxy.newProxyInstance(List.class.getClassLoader(), new Class[]{List.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("remove")) {
throw new UnsupportedOperationException();
} else {
return method.invoke(list, args);
}
}
});
}
當(dāng)我們傳入一個List對象進(jìn)去,返回的就是經(jīng)過處理后的代理List類夏跷,這時如果調(diào)用代理List的remove
方法時就會報(bào)錯了哼转。
估計(jì)你已經(jīng)知道了如何使用動態(tài)代理了,但是你肯定有很多疑惑槽华,Proxy.newProxyInstance這個靜態(tài)方法是怎么完成這件事的呢壹蔓?我們看一下源碼(以下分析的源碼是基于jdk 1.6,相比起來比較簡單易懂):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 檢查 h 不為空猫态,否則拋異常
if (h == null) {
throw new NullPointerException();
}
// 獲得與制定類裝載器和一組接口相關(guān)的代理類類型對象
Class cl = getProxyClass(loader, interfaces);
// 通過反射獲取構(gòu)造函數(shù)對象并生成代理類實(shí)例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
其他都好懂佣蓉,可能就是getProxyClass()
這個方法沒見過。同時該方法也是動態(tài)代理中最核心的東西亲雪。由于該方法比較長勇凭,去除注釋后也有120+行代碼,所以我就不粘貼代碼了义辕,你可以自己去Java源碼中去尋找虾标,或者你可以查看這個鏈接: getProxyClass(),里面大概的流程是:先將傳入的接口數(shù)組進(jìn)行一系列檢查灌砖,并存儲所有接口名稱夺巩,然后創(chuàng)建一個HashMap的緩存表,里面以不同的鍵值對形式來存放著待創(chuàng)建的代理類周崭、正在被創(chuàng)建的代理類柳譬。然后根據(jù)一系列的規(guī)則確定包名,調(diào)用ProxyGenerator.generateProxyClass()
方法創(chuàng)建代理類续镇,這個方法會調(diào)用ProxyGenerator
對象的generateClassFile()
方法來生成代理類類文件的字節(jié)數(shù)組美澳,具體生成方法可以參見這個鏈接中的源碼: generateClassFile(),我就不再深入闡述了。生成代理類對象后制跟,將其記錄到proxyClasses
表中舅桩,然后就是更新緩存表、清楚緩存表等一系列善后工作了雨膨。
由于所有的代理類都有一個共同的父類Proxy
擂涛,Java 的繼承機(jī)制注定了這些動態(tài)代理類們無法實(shí)現(xiàn)對class的動態(tài)代理,原因是多繼承在 Java 中本質(zhì)上就行不通聊记。所以Java只能進(jìn)行接口的動態(tài)代理撒妈,這不得不說是一個遺憾。
靜態(tài)代理和動態(tài)代理的聯(lián)系
然而在大多數(shù)情況下排监,我們的代理類在敲代碼的時候就能確定下來狰右,也就是說,我們?nèi)粘i_發(fā)的大多數(shù)情況都能使用靜態(tài)代理舆床。那么能不能用動態(tài)代理呢棋蚌?當(dāng)然能!為什么要用動態(tài)代理呢挨队?
當(dāng)委托類實(shí)現(xiàn)的接口方法比較多的時候谷暮,寫一個代理類一個個地處理委托方法就太麻煩了。這時我們可以在invoke()
方法中對委托類的所有方法進(jìn)行判斷盛垦、攔截湿弦、擴(kuò)展等處理。減少代碼量情臭。
Retrofit中的動態(tài)代理
Retrofit可謂是當(dāng)下最火的Android網(wǎng)絡(luò)請求框架之一了,我們在Retrofit的wiki上可以看到Retrofit的使用方法如下:
1.創(chuàng)建一個請求方法的接口:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
2.生成Retrofit對象赌蔑,并且創(chuàng)建一個實(shí)現(xiàn)了GitHubServiece
接口的實(shí)體類:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
3.發(fā)起網(wǎng)絡(luò)請求:
Call<List<Repo>> repos = service.listRepos("octocat");
我們看第二步俯在,傳入了一個接口對象,然后就創(chuàng)建了一個實(shí)現(xiàn)了GitHubService
接口的實(shí)體類娃惯?怎么實(shí)現(xiàn)的跷乐?Excuse me?這時候趾浅,就需要輕輕地點(diǎn)擊去看源碼:
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
一個大寫的動態(tài)代理c堤帷!皿哨!當(dāng)然這里用的并不是規(guī)范的代理模式浅侨,因?yàn)槲覀冎溃砟J绞切枰幸粋€具體的委托類的证膨,但是這里并沒有一個具體的委托類如输,它是直接將傳入的GitHubService
接口,既作為事件接口,又作為代理類使用不见,顯然這里攔截了接口中的方法澳化,并不能對該接口的方法本身進(jìn)行任何操作,這里使用動態(tài)代理稳吮,純粹是為了攔截方法缎谷,獲取接口方法上的注解信息,然后返回一個Adapter灶似。這種可以算得上一個動態(tài)代理的擴(kuò)展應(yīng)用吧列林。
靜態(tài)代理和動態(tài)代理的應(yīng)用場景很多,常見的框架都有用到(比如Spring框架喻奥、OrmLite框架)席纽,希望讀完本篇文章能幫你理解靜態(tài)代理和動態(tài)代理。