提綱
最近在讀 Android Binder 部分的源碼,之前三三兩兩的讀過一些片段。但總是感覺理解的不深刻,在讀源碼的過程中看到了代理模式的應(yīng)用,那便把代理模式單獨(dú)開一章描述清楚,需要查看其它設(shè)計(jì)模式描述可以查看我的文章《設(shè)計(jì)模式開篇》。
本篇文章將根據(jù)以下知識(shí)點(diǎn)展開描述:
1、普通代理模式(分析 Java 文件操作源碼)
2、遠(yuǎn)程代理模式(分析 Android Binder Service 源碼)
3、動(dòng)態(tài)代理實(shí)現(xiàn)(分析 API 模塊設(shè)計(jì))
普通代理模式
使用java.io.File來形容代理模式的本質(zhì)是再恰當(dāng)不過的事情了,為了保證上下文的連貫性,請(qǐng)容許我設(shè)計(jì)一個(gè)文件操作的場景。
假使你需要使用批復(fù)同事轉(zhuǎn)發(fā)給你的文件,你使用程序讀取出文件內(nèi)容,等你閱讀完畢后你會(huì)往文件中加入你的意見。在批復(fù)完成后,你會(huì)將文件通過郵件回復(fù)給同事,并同事刪除本地的備份。
在動(dòng)工之前假設(shè)你會(huì)考慮如下情景:
- 文件是否為空
- 是否有權(quán)限讀取文件
- 是否有權(quán)限寫入文件
- 刪除文件
文件操作 JDK 已經(jīng)為我們內(nèi)置好了自然不用我們重復(fù)開發(fā)輪子,讓我們看看這部分的代碼。
public class File
implements Serializable, Comparable<File>
{
public long length() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return 0L;
}
return fs.getLength(this);
}
public boolean canRead() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return fs.checkAccess(this, FileSystem.ACCESS_READ);
}
public boolean canWrite() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(path);
}
if (isInvalid()) {
return false;
}
return fs.checkAccess(this, FileSystem.ACCESS_WRITE);
}
public boolean delete() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
if (isInvalid()) {
return false;
}
return fs.delete(this);
}
}
我們發(fā)現(xiàn)java.io.File這個(gè)類并沒有真正的涉及到文件的操作,而只是對(duì)真正的操作的一層包裝。比如每個(gè)方法中都使用了SecurityManager做安全檢測,而在檢測通過時(shí)又都使用FileSystem的實(shí)例fs調(diào)用到真正的實(shí)現(xiàn)。
FileSystem是抽象類,它定義了所有File類會(huì)調(diào)用到的底層的實(shí)現(xiàn),比如下面的 delete()方法。
abstract class FileSystem {
public abstract boolean delete(File f);
}
我們來跟蹤下FileSystem的子類,顯示它支持了 Unix 與 Window 兩種文件系統(tǒng)。讓我們跟進(jìn)到UnixFileSystem里看看到底發(fā)生了什么?

class UnixFileSystem extends FileSystem {
public boolean delete(File f) {
// Keep canonicalization caches in sync after file deletion
// and renaming operations. Could be more clever than this
// (i.e., only remove/update affected entries) but probably
// not worth it since these entries expire after 30 seconds
// anyway.
cache.clear();
javaHomePrefixCache.clear();
return delete0(f);
}
private native boolean delete0(File f);
}
看來UnixFileSystem調(diào)用了本地native方法完成了對(duì)文件的刪除操作。
分析到這里我們發(fā)現(xiàn)了上層的File文件實(shí)際上并沒有完成任何的文件的操作,而只是對(duì)FileSystem的封裝調(diào)用+權(quán)限檢查。如果你仔細(xì)閱讀我貼出的代碼,你會(huì)發(fā)現(xiàn)FileSystem類本身或其子類的訪問權(quán)限都是包訪問權(quán)限,而這恰恰佐證了代理模式的本質(zhì)——控制對(duì)象訪問。
代理模式的本質(zhì):控制對(duì)象訪問。
具有控制對(duì)象訪問思想特征設(shè)計(jì)模式有很多種,比如:中介、門面,甚至單例都具備該特征,代理模式在某種程度而言比其它表現(xiàn)方式更純粹。
遠(yuǎn)程代理模式
在有了普通代理模式的基礎(chǔ),我們接下去分析說明是遠(yuǎn)程代理模式。其實(shí)遠(yuǎn)程代理與普通代理的差距很小, 以 `File``作為例子,普通代理模式的調(diào)用圖如下:

而遠(yuǎn)程代理模式與普通代理模式的區(qū)別是:有別于普通代理模式的本地調(diào)用轉(zhuǎn)發(fā),遠(yuǎn)程代理模式使用 遠(yuǎn)程協(xié)議 描述了 File --> FileSystem 的轉(zhuǎn)發(fā)過程。
很好的參考例子是 Android 的 Binder 部分,我們這里將貼出部分的相關(guān)代碼。不知是否是為了區(qū)分遠(yuǎn)程代理與普通代理,Android 中的遠(yuǎn)程代理總習(xí)慣使用Stub而不是Proxy。
以IWindowManager為例:
public interface IWindowManager extends android.os.IInterface{
public static abstract class Stub extends android.os.Binder implements android.view.IWindowManager{
// 省略部分代碼
}
}
Stub實(shí)現(xiàn)接口IWindowManager而Stub同時(shí)又繼承自Binder,Binder具備遠(yuǎn)程通訊的能力。所以可以稱Stub是IWindowManager接口實(shí)例的遠(yuǎn)程代理。

上圖展示了接口IWindowManagerImpl的繼承結(jié)構(gòu),很容易聯(lián)想到這是代理模式的實(shí)現(xiàn)。那我們看下這三個(gè)類之間的關(guān)系:
1、IWindowManagerImpl 是客戶端窗口管理職責(zé)的實(shí)現(xiàn)類,它提供了窗口管理等一系列操作。
2、WindowManagerService是android.view.IWindowManager.Stub的實(shí)現(xiàn)類,它提供了對(duì)窗口的管理的服務(wù)端實(shí)現(xiàn)。
3、IWindowmanager.Stub.Proxy則是封裝了對(duì)Binder傳輸數(shù)據(jù)的實(shí)現(xiàn)。
他們之間的關(guān)系可以這樣理解:
1、?IWindowManagerImpl是客戶端類,它具備IWindowManager的接口,但其實(shí)它并不具備真正的管理窗口的能力。
2、所以IWindowManagerImpl最終會(huì)將消息轉(zhuǎn)發(fā)給WindowManagerService,但是因?yàn)?code>WindowManagerService是遠(yuǎn)程服務(wù),所以并不能直接將消息傳遞。
3、于是借助IWindowmanager.Stub.Proxy類,封裝了遠(yuǎn)程的mRemote對(duì)象(實(shí)際就是WindowManagerService對(duì)象)并將對(duì)應(yīng)的IWindowManager接口都實(shí)現(xiàn)數(shù)據(jù)傳輸接口,以便于數(shù)據(jù)能正在的發(fā)送給窗口管理服務(wù)WindowService。
動(dòng)態(tài)代理模式
所謂動(dòng)態(tài)代理:即提供了在編譯時(shí)無法確定類型的代理方式,但無論怎么變它始終沒有脫離控制對(duì)象訪問的本質(zhì)。
讓我們舉個(gè)例子來說明動(dòng)態(tài)代理:我們?cè)谄綍r(shí)開發(fā)都會(huì)利用到接口,當(dāng)后端同事為我們提供了豐富的 API 時(shí),每當(dāng)多一個(gè)接口我們可能就要做很多事情。那么有沒有一種可能性,讓我們以成本最低的接入接口呢?
在繼續(xù)之前我們先舉個(gè)具象的例子,后端提供了我們“登錄”接口。
規(guī)定了以POST方式發(fā)起請(qǐng)求,需要傳入格式為 JSON 的數(shù)據(jù),同時(shí)需要包含兩個(gè)鍵名“username”、“password”。
// 我們定義了如下的類:
@RestService
public interface ClerkAPI {
@POST
HealthbokResponse login(
@Param("username") String target,
@Param("password") String password
);
我們使用@RestService標(biāo)記類型,這顯然在后面用得著。用@POST標(biāo)記請(qǐng)求方式,用@Param標(biāo)記傳入的參數(shù),它們都只是普通的注解定義。
@Documented
@Target (TYPE)
@Retention (RUNTIME)
public @interface RestService {
}
這些信息也恰恰是后端同事告訴我們的僅有的信息,現(xiàn)在有個(gè)嚴(yán)格的要求是我們只利用這些信息??梢栽俨桓钠渌a的情況下完成對(duì)login()方法的調(diào)用。
public class RestServiceFactory {
private static final ConcurrentMap<String, Object> serviceCaches = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public static <T> T getService(String baseUrl, Class<T> serviceClass) {
T service;
if (serviceClass.isAnnotationPresent(RestService.class)) {
String key = serviceClass.getName();
service = (T) serviceCaches.get(serviceClass.getName());
if (service == null) {
service = (T) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class[]{serviceClass}, new RestInvocationHandler(baseUrl));
T found = (T) serviceCaches.putIfAbsent(key, service);
if (found != null) {
service = found;
}
}
} else {
throw new IllegalArgumentException(serviceClass + " is not annotated with @RestService");
}
return service;
}
/**
* Intercepts all calls to the the RestService Impl
*/
@SuppressWarnings("unchecked")
private static class RestInvocationHandler implements InvocationHandler {
private String baseUrl;
private RestInvocationHandler(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 封裝請(qǐng)求信息
HealthbokRequest request;
// 真正的請(qǐng)求客戶端,你可以將它理解為 HttpClient
RestClient client = RestClient.getInstance();
synchronized (client) {
// 依據(jù)傳入的數(shù)據(jù),生成請(qǐng)求信息
request = client.onPrepareRequest(baseUrl, method, args);
}
// 發(fā)起調(diào)用,返回值即是請(qǐng)求結(jié)果
return client.call(request);
}
}
}
我們利用Proxy.newProxyInstance()動(dòng)態(tài)的為接口創(chuàng)建了代理對(duì)象,以至于上層框架并不關(guān)心傳入的接口具體是哪個(gè)接口。它只要滿足@RestService的約束,并符合@POST、@Param等一系列注解約束即可。
讓我們看下最后的調(diào)用方式,幾乎不用更改什么,除了傳入的@RestService 的 Class)以及對(duì)應(yīng)的方法調(diào)用。
RestServiceFactory
.getService("http://api.mock.com", ClerkAPI.class)
.login("1866824xxxx","24xxxx");
總結(jié)
嘮嘮叨叨寫了這么多沒有講太多理論性的東西,都是以實(shí)踐的方式記錄。從分析 JAVA 、到 ANDROID的源碼分析,再到最后自己的API 接口開源項(xiàng)目片段摘取,哪里都有代理模式的身影。
代理模式是用的非常普遍的模式,所以有必要從不同的視角去理解。但是萬變不離其宗,其本質(zhì)無論如何都不會(huì)改變。變化的只是實(shí)現(xiàn)代理模式的過程(或是遠(yuǎn)程通訊、或是動(dòng)態(tài)創(chuàng)建),所以多關(guān)注設(shè)計(jì)模式的本質(zhì)才是重要的事情。
在整理過程中的一點(diǎn)復(fù)習(xí)資料:
1、Java 動(dòng)態(tài)代理
2、grep 在線看源碼的小工具