提綱
最近在讀 Android Binder 部分的源碼贫堰,之前三三兩兩的讀過一些片段廷粒。但總是感覺理解的不深刻享扔,在讀源碼的過程中看到了代理模式的應用狱意,那便把代理模式單獨開一章描述清楚筋量,需要查看其它設計模式描述可以查看我的文章《設計模式開篇》窿克。
本篇文章將根據(jù)以下知識點展開描述:
1、普通代理模式(分析 Java 文件操作源碼)
2毛甲、遠程代理模式(分析 Android Binder Service 源碼)
3年叮、動態(tài)代理實現(xiàn)(分析 API 模塊設計)
普通代理模式
使用java.io.File
來形容代理模式的本質是再恰當不過的事情了,為了保證上下文的連貫性玻募,請容許我設計一個文件操作的場景只损。
假使你需要使用批復同事轉發(fā)給你的文件,你使用程序讀取出文件內容七咧,等你閱讀完畢后你會往文件中加入你的意見跃惫。在批復完成后,你會將文件通過郵件回復給同事艾栋,并同事刪除本地的備份爆存。
在動工之前假設你會考慮如下情景:
- 文件是否為空
- 是否有權限讀取文件
- 是否有權限寫入文件
- 刪除文件
文件操作 JDK 已經(jīng)為我們內置好了自然不用我們重復開發(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
這個類并沒有真正的涉及到文件的操作先较,而只是對真正的操作的一層包裝。比如每個方法中都使用了SecurityManager
做安全檢測悼粮,而在檢測通過時又都使用FileSystem
的實例fs
調用到真正的實現(xiàn)闲勺。
FileSystem
是抽象類,它定義了所有File
類會調用到的底層的實現(xiàn)扣猫,比如下面的 delete()
方法菜循。
abstract class FileSystem {
public abstract boolean delete(File f);
}
我們來跟蹤下FileSystem
的子類,顯示它支持了 Unix 與 Window 兩種文件系統(tǒng)申尤。讓我們跟進到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
調用了本地native
方法完成了對文件的刪除操作衙耕。
分析到這里我們發(fā)現(xiàn)了上層的File
文件實際上并沒有完成任何的文件的操作,而只是對FileSystem
的封裝調用+權限檢查勺远。如果你仔細閱讀我貼出的代碼臭杰,你會發(fā)現(xiàn)FileSystem
類本身或其子類的訪問權限都是包訪問權限,而這恰恰佐證了代理模式的本質——控制對象訪問谚中。
代理模式的本質:控制對象訪問渴杆。
具有控制對象訪問思想特征設計模式有很多種,比如:中介宪塔、門面磁奖,甚至單例都具備該特征,代理模式在某種程度而言比其它表現(xiàn)方式更純粹某筐。
遠程代理模式
在有了普通代理模式的基礎比搭,我們接下去分析說明是遠程代理模式。其實遠程代理與普通代理的差距很小南誊, 以 `File``作為例子身诺,普通代理模式的調用圖如下:
而遠程代理模式與普通代理模式的區(qū)別是:有別于普通代理模式的本地調用轉發(fā),遠程代理模式使用 遠程協(xié)議 描述了 File --> FileSystem 的轉發(fā)過程抄囚。
很好的參考例子是 Android 的 Binder 部分霉赡,我們這里將貼出部分的相關代碼。不知是否是為了區(qū)分遠程代理與普通代理幔托,Android 中的遠程代理總習慣使用Stub
而不是Proxy
穴亏。
以IWindowManager
為例:
public interface IWindowManager extends android.os.IInterface{
public static abstract class Stub extends android.os.Binder implements android.view.IWindowManager{
// 省略部分代碼
}
}
Stub
實現(xiàn)接口IWindowManager
而Stub
同時又繼承自Binder
,Binder
具備遠程通訊的能力重挑。所以可以稱Stub
是IWindowManager
接口實例的遠程代理嗓化。
上圖展示了接口IWindowManagerImpl
的繼承結構,很容易聯(lián)想到這是代理模式的實現(xiàn)谬哀。那我們看下這三個類之間的關系:
1刺覆、IWindowManagerImpl 是客戶端窗口管理職責的實現(xiàn)類,它提供了窗口管理等一系列操作史煎。
2谦屑、WindowManagerService
是android.view.IWindowManager.Stub
的實現(xiàn)類,它提供了對窗口的管理的服務端實現(xiàn)劲室。
3伦仍、IWindowmanager.Stub.Proxy
則是封裝了對Binder
傳輸數(shù)據(jù)的實現(xiàn)结窘。
他們之間的關系可以這樣理解:
1很洋、?IWindowManagerImpl
是客戶端類,它具備IWindowManager
的接口隧枫,但其實它并不具備真正的管理窗口的能力喉磁。
2谓苟、所以IWindowManagerImpl
最終會將消息轉發(fā)給WindowManagerService
,但是因為WindowManagerService
是遠程服務协怒,所以并不能直接將消息傳遞涝焙。
3、于是借助IWindowmanager.Stub.Proxy
類孕暇,封裝了遠程的mRemote
對象(實際就是WindowManagerService
對象)并將對應的IWindowManager
接口都實現(xiàn)數(shù)據(jù)傳輸接口仑撞,以便于數(shù)據(jù)能正在的發(fā)送給窗口管理服務WindowService
。
動態(tài)代理模式
所謂動態(tài)代理:即提供了在編譯時無法確定類型的代理方式妖滔,但無論怎么變它始終沒有脫離控制對象訪問的本質隧哮。
讓我們舉個例子來說明動態(tài)代理:我們在平時開發(fā)都會利用到接口,當后端同事為我們提供了豐富的 API 時座舍,每當多一個接口我們可能就要做很多事情沮翔。那么有沒有一種可能性,讓我們以成本最低的接入接口呢曲秉?
在繼續(xù)之前我們先舉個具象的例子采蚀,后端提供了我們“登錄”接口。
規(guī)定了以POST方式發(fā)起請求承二,需要傳入格式為 JSON 的數(shù)據(jù)榆鼠,同時需要包含兩個鍵名“username”、“password”亥鸠。
// 我們定義了如下的類:
@RestService
public interface ClerkAPI {
@POST
HealthbokResponse login(
@Param("username") String target,
@Param("password") String password
);
我們使用@RestService
標記類型璧眠,這顯然在后面用得著。用@POST
標記請求方式读虏,用@Param
標記傳入的參數(shù)责静,它們都只是普通的注解定義。
@Documented
@Target (TYPE)
@Retention (RUNTIME)
public @interface RestService {
}
這些信息也恰恰是后端同事告訴我們的僅有的信息盖桥,現(xiàn)在有個嚴格的要求是我們只利用這些信息灾螃。可以再不更改其它代碼的情況下完成對login()
方法的調用揩徊。
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 {
// 封裝請求信息
HealthbokRequest request;
// 真正的請求客戶端腰鬼,你可以將它理解為 HttpClient
RestClient client = RestClient.getInstance();
synchronized (client) {
// 依據(jù)傳入的數(shù)據(jù),生成請求信息
request = client.onPrepareRequest(baseUrl, method, args);
}
// 發(fā)起調用塑荒,返回值即是請求結果
return client.call(request);
}
}
}
我們利用Proxy.newProxyInstance()
動態(tài)的為接口創(chuàng)建了代理對象熄赡,以至于上層框架并不關心傳入的接口具體是哪個接口。它只要滿足@RestService
的約束齿税,并符合@POST
彼硫、@Param
等一系列注解約束即可。
讓我們看下最后的調用方式,幾乎不用更改什么拧篮,除了傳入的@RestService 的 Class)以及對應的方法調用词渤。
RestServiceFactory
.getService("http://api.mock.com", ClerkAPI.class)
.login("1866824xxxx","24xxxx");
總結
嘮嘮叨叨寫了這么多沒有講太多理論性的東西,都是以實踐的方式記錄串绩。從分析 JAVA 缺虐、到 ANDROID的源碼分析,再到最后自己的API 接口開源項目片段摘取礁凡,哪里都有代理模式的身影高氮。
代理模式是用的非常普遍的模式,所以有必要從不同的視角去理解顷牌。但是萬變不離其宗纫溃,其本質無論如何都不會改變。變化的只是實現(xiàn)代理模式的過程(或是遠程通訊韧掩、或是動態(tài)創(chuàng)建)紊浩,所以多關注設計模式的本質才是重要的事情。
在整理過程中的一點復習資料:
1疗锐、Java 動態(tài)代理
2坊谁、grep 在線看源碼的小工具