如果要說設計模式中哪種模式在生活中最常見,那么代理模式是當仁不讓的。比如,你一覺睡到中午不想去食堂打飯,于是你委托阿黃讓他幫你把飯帶回來,這就產生了一種代理關系,其中委托人是你,代理人是阿黃,而事件就是打飯,你為了足不出戶就能吃到午飯而想到的這餿主意,就是一種代理方法。又比如,你女朋友從外地回來下飛機了,而你又因為一些不可描述的事情不能去接機,這時候你就可以叫你的好基友去機場幫你把女朋友接回來啊,于是你和你好基友就形成了一種代理關系,委托人是你,代理人是好基友,而事件就是接女朋友回來,這種很有可能戴綠帽子的方法就是一種代理方法。然后,將這些代理方法總結規律,抽象出來,上升到一個通用層面的高度,就是代理模式。什么是代理模式呢?通俗地講,有些事情本應該是你做的,但是你不想做或者不方便做的時候,叫別人替你完成。生活中種種“幫幫忙”的事件,都有代理模式的影子,所以說代理模式在生活中很常見。
靜態代理
那么我們如何使用代碼來描述代理模式呢?我們將上面的例子總結規律,抽象出來,就大概有下面兩點:
- 委托人和代理人都要完成同樣的事情(共同實現事件的接口)。
- 代理人要做的這件事其實是委托人請求的,也就是說,代理人本身并不知道如何做這件事,只有委托人告訴他了,代理人才知道怎么做。所以,代理人需要持有委托人的引用,在做這件事的時候就按照委托人的意愿去做。
就比如第一個例子,“打飯”這件事情,由于你和阿黃都能做,所以可以寫成一個接口,讓你和阿黃都能實現。
/**
* 一個抽象的午飯類, 具體要吃什么樣午飯, 看子類的心情。
*/
public interface Lunch {
/**
* 吃什么樣的午飯, 糖醋排骨?宮保雞丁?。。。。
*/
void catagory();
}
然后就要創建“你”這個對象了,由于你要吃午飯,就需要實現Lunch
類:
/**
* 你, 需要考慮午飯吃什么
*/
public class You implements Lunch {
@Override
public void catagory() {
// 你想吃宮保雞丁
System.out.println("我要吃宮保雞丁蓋飯");
}
}
然后你很懶啊,需要找阿黃這個代理人幫你帶午飯回來,所以還需要一個代理人對象,注意,由于代理事件是委托人(“你”)下發的,所以代理人對象里面還需要持有委托人的應用:
/**
* 代理人, 由于需要幫委托人帶飯, 也必須實現午飯接口
*/
public class ProxyMan implements Lunch {
/**
* 必須是要有委托人的存在才行,沒有委托人還需要代理人干嘛?
*/
private You mYou;
/**
* 在創建代理人對象的時候就傳入一個委托人進來,這樣就使得代理人可以持有委托人的引用了。
* 當然這只是一種方法,你也可以使用其他方法讓代理人持有委托人的引用。
*/
public ProxyMan(You you) {
mYou = you;
}
@Override
public void catagory() {
// "你"要讓代理人幫你做的事情。
mYou.catagory();
}
}
OK,大功告成,然后我們創建一個場景(客戶端)來模擬一下這個事情:
public class Proxy {
public static void main(String[] args) {
// 創建一個委托人
You you = new You();
// 創建一個代理人, 可以是阿黃, 也可以是其他人
ProxyMan aHuang = new ProxyMan(you);
// 讓阿黃幫你帶飯。
aHuang.catagory();
}
}
以上便是代理模式的一種模版,結合代碼,我們可以總結出代理模式的類圖如下:
.png)
好了,扯了這么多,大概對代理模式有一個印象了。是時候給代理模式來下一個正式的定義了:為其他對象提供一種代理以控制對這個對象的訪問。
另外,我們現在看到的代理模式,又稱之為靜態代理,因為代理類是需要我們自己手動編寫的,后面我們會談談如何不用手動編寫代理類,來實現代理模式。
靜態代理的作用
當然,你會說,這種小事,委托類自己就能完成的嘛,非要多增加一些類干嘛。的確,如果單是這種簡單的例子,使用任何設計模式都是多此一舉。但在實際工作中,使用代理模式就會有如下的有點:
- 解耦。這幾乎是設計模式解決的根本問題。在這里,委托類可以專心地做好自己,麻煩的事情讓別人代勞。
- 攔截、擴展方法。代理類在實現接口方法的時候,除了直接調用委托類的方法外,還可以在不修改委托類的情況下,增加一些其他的功能,比如順便帶瓶可樂回來?
為了能讓我們的代理人阿黃能帶瓶可樂回來,我們只需要在代理類中對catagory()
方法增加一個“帶瓶可樂回來”的功能即可:
public class ProxyMan implements Lunch {
private You mYou;
public ProxyMan(You you) {
mYou = you;
}
@Override
public void catagory() {
// "你"要讓代理人幫你做的事情。
mYou.catagory();
// 打完飯后再去買瓶可樂
System.out.println("嗯,買瓶可樂來喝");
}
}
現在你應該能明顯地感受到代理類的作用了。對,就是攔截方法、對委托的方法進行加工處理。
動態代理
然后有一天,當你中午醒來的時候,發現寢室里除了你以外空無一人,你為了雙倍經驗雙倍金幣所以不想浪費時間下樓去食堂打飯,但是你餓啊,要吃飯啊,這時你就只有等寢室再進來個人,讓他幫你帶飯(假設你不會叫外賣)。但是這會有一個問題,之前一醒來就可以讓阿黃幫你帶飯,是因為阿黃在你醒來之前就在寢室的(程序編譯階段就存在一個代理類),并且設定了阿黃“能打飯”(實現了Lunch
接口)這個屬性,所以簡單的幾行代碼就能搞定。但是現在,你醒來之后(程序開始運行),發現并沒有人可以幫你帶飯(沒有一個在編譯階段就實現了Lunch
接口的代理類),在你等待的過程中也并不知道會是誰進來,阿黃,阿貓還是阿三(我們需要在程序運行的時候動態生成一個對象)?而且最重要的是,進來的那個家伙能幫你帶飯才行(運行階段生成的對象要實現Lunch
接口)。
那么我們再梳理一遍,現在的你應該怎么做才能吃到午飯呢?
- 首先,“你”這個對象和“午飯”這個事件是必須存在的。
- 其次,創建一個代理人。由于這個代理人是幫你做事情的,所以創建這個代理人的方法必須和“你”這個對象以及“午飯”事件接口相關。
- 最后,讓代理人去幫你打飯回來。
讓我們來用代碼實現一下,You
這個委托類和Lunch
接口是要保留的,而ProxyMan
這個代理類就不需要了。在場景中(client客戶端),我們可以使用代理的一個靜態方法newProxyInstance
來創建一個代理對象,這個代理對象當然是實現了Lunch
這個接口的。那么client中的代碼如下:
public static void main(String[] args) {
// 創建一個委托人
You you = new You();
// 創建一個代理人, 第一個參數是委托類的ClassLoader, 第二個參數是要實現的接口數組,
// 第三個參數是一個匿名內部類, 對于代理人將要做的任何事情做攔截處理。
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);
}
});
// 動態生成的代理人去帶飯了
somebodyCouldTakeLunch.catagory();
}
當動態生成的代理人對象調用方法去帶飯時(somebodyCouldTakeLunch.catagory()
),就會觸發匿名內部類InvocationHandler
中的invoke()
方法,這里面沒有做其他處理,直接返回了“你”這個對象的對應方法。也就是說,動態生成的代理人是安裝你的要求帶回來宮爆雞丁蓋飯!
一個簡單的動態代理就這么完成了。然而動態代理是想當強大的,為了顯示這種強大,這里再舉一個例子,List集合大家都用過,這個接口有很多方法,如果我們想要屏蔽remove
這個方法,這時我們就可以使用動態代理,來生成一個代理類List,每當調用到代理類的remove()
方法時,就跑出異常,實現代碼如下:
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);
}
}
});
}
當我們傳入一個List對象進去,返回的就是經過處理后的代理List類,這時如果調用代理List的remove
方法時就會報錯了。
估計你已經知道了如何使用動態代理了,但是你肯定有很多疑惑,Proxy.newProxyInstance這個靜態方法是怎么完成這件事的呢?我們看一下源碼(以下分析的源碼是基于jdk 1.6,相比起來比較簡單易懂):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 檢查 h 不為空,否則拋異常
if (h == null) {
throw new NullPointerException();
}
// 獲得與制定類裝載器和一組接口相關的代理類類型對象
Class cl = getProxyClass(loader, interfaces);
// 通過反射獲取構造函數對象并生成代理類實例
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()
這個方法沒見過。同時該方法也是動態代理中最核心的東西。由于該方法比較長,去除注釋后也有120+行代碼,所以我就不粘貼代碼了,你可以自己去Java源碼中去尋找,或者你可以查看這個鏈接: getProxyClass(),里面大概的流程是:先將傳入的接口數組進行一系列檢查,并存儲所有接口名稱,然后創建一個HashMap的緩存表,里面以不同的鍵值對形式來存放著待創建的代理類、正在被創建的代理類。然后根據一系列的規則確定包名,調用ProxyGenerator.generateProxyClass()
方法創建代理類,這個方法會調用ProxyGenerator
對象的generateClassFile()
方法來生成代理類類文件的字節數組,具體生成方法可以參見這個鏈接中的源碼: generateClassFile(),我就不再深入闡述了。生成代理類對象后,將其記錄到proxyClasses
表中,然后就是更新緩存表、清楚緩存表等一系列善后工作了。
由于所有的代理類都有一個共同的父類Proxy
,Java 的繼承機制注定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在 Java 中本質上就行不通。所以Java只能進行接口的動態代理,這不得不說是一個遺憾。
靜態代理和動態代理的聯系
然而在大多數情況下,我們的代理類在敲代碼的時候就能確定下來,也就是說,我們日常開發的大多數情況都能使用靜態代理。那么能不能用動態代理呢?當然能!為什么要用動態代理呢?
當委托類實現的接口方法比較多的時候,寫一個代理類一個個地處理委托方法就太麻煩了。這時我們可以在invoke()
方法中對委托類的所有方法進行判斷、攔截、擴展等處理。減少代碼量。
Retrofit中的動態代理
Retrofit可謂是當下最火的Android網絡請求框架之一了,我們在Retrofit的wiki上可以看到Retrofit的使用方法如下:
1.創建一個請求方法的接口:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
2.生成Retrofit對象,并且創建一個實現了GitHubServiece
接口的實體類:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
3.發起網絡請求:
Call<List<Repo>> repos = service.listRepos("octocat");
我們看第二步,傳入了一個接口對象,然后就創建了一個實現了GitHubService
接口的實體類?怎么實現的?Excuse me?這時候,就需要輕輕地點擊去看源碼:
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);
}
});
}
一個大寫的動態代理!!!當然這里用的并不是規范的代理模式,因為我們知道,代理模式是需要有一個具體的委托類的,但是這里并沒有一個具體的委托類,它是直接將傳入的GitHubService
接口,既作為事件接口,又作為代理類使用,顯然這里攔截了接口中的方法,并不能對該接口的方法本身進行任何操作,這里使用動態代理,純粹是為了攔截方法,獲取接口方法上的注解信息,然后返回一個Adapter。這種可以算得上一個動態代理的擴展應用吧。
靜態代理和動態代理的應用場景很多,常見的框架都有用到(比如Spring框架、OrmLite框架),希望讀完本篇文章能幫你理解靜態代理和動態代理。