代理模式(控制對(duì)象訪問)

提綱

最近在讀 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ā)生了什么?

FileSystem的子類們
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)接口IWindowManagerStub同時(shí)又繼承自Binder,Binder具備遠(yuǎn)程通訊的能力。所以可以稱StubIWindowManager接口實(shí)例的遠(yuǎn)程代理。

遠(yuǎn)程代理模式

上圖展示了接口IWindowManagerImpl的繼承結(jié)構(gòu),很容易聯(lián)想到這是代理模式的實(shí)現(xiàn)。那我們看下這三個(gè)類之間的關(guān)系:
1、IWindowManagerImpl 是客戶端窗口管理職責(zé)的實(shí)現(xiàn)類,它提供了窗口管理等一系列操作。
2、WindowManagerServiceandroid.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 在線看源碼的小工具

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 1:InputChannel提供函數(shù)創(chuàng)建底層的Pipe對(duì)象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,715評(píng)論 0 18
  • Android跨進(jìn)程通信IPC整體內(nèi)容如下 1、Android跨進(jìn)程通信IPC之1——Linux基礎(chǔ)2、Andro...
    隔壁老李頭閱讀 12,488評(píng)論 11 56
  • 毫不夸張地說,Binder是Android系統(tǒng)中最重要的特性之一;正如其名“粘合劑”所喻,它是系統(tǒng)間各個(gè)組件的橋梁...
    weishu閱讀 18,110評(píng)論 29 246
  • 代理模式是什么 如上圖所示,代理代表著另一終端中的某個(gè)真實(shí)服務(wù)對(duì)象,Client 調(diào)用代理(Client help...
    野生西瓜閱讀 2,441評(píng)論 2 14

友情鏈接更多精彩內(nèi)容