「Java 路線」| 動態(tài)代理 & 靜態(tài)代理

點(diǎn)贊關(guān)注,不再迷路,你的支持對我意義重大!

?? Hi,我是丑丑。本文 「Java 路線」| 導(dǎo)讀 —— 夢開始的地方 已收錄,這里有 Android 進(jìn)階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯(lián)系方式在 GitHub)

前言

  • 代理模式(Proxy Pattern)也稱委托模式(Delegate Pattern),是一種結(jié)構(gòu)型設(shè)計(jì)模式,也是一項(xiàng)基礎(chǔ)設(shè)計(jì)技巧。在日常應(yīng)用中,代理模式的使用頻率非常高,許多其他的設(shè)計(jì)模式本質(zhì)上是在特定的上下文中,對代理模式進(jìn)行的針對性優(yōu)化,由此可見其重要性。
  • 本文的第一節(jié)闡述了代理模式的定義,如果你已經(jīng)對代理模式(靜態(tài)代理)比較熟悉了,可以跳過這個(gè)部分;第二節(jié)討論了動態(tài)代理及其實(shí)現(xiàn)原理;第三個(gè)節(jié)提出了一些擴(kuò)展思路的要點(diǎn),其中包含了多數(shù)人(和一部分書籍)容易犯的思維桎梏,請不要錯(cuò)過它。第四節(jié)列舉了其他一些常見的設(shè)計(jì)模式,你能理解它們和代理模式的微妙關(guān)系嗎?最后的部分是參考的文獻(xiàn)資料。

1. 代理的定義

??代理模式用于解決兩種問題:

  • 控制對基礎(chǔ)對象的訪問
  • 在訪問時(shí)增加額外的功能

??這是兩種非常樸素的場景,正因如此,才會常常從其他設(shè)計(jì)模式中發(fā)現(xiàn)代理模式的影子,參考下面的UML類圖和時(shí)序圖:

代理模式UML 類圖
代理模式UML 時(shí)序圖

??從類圖看,代理對象(Proxy)通過組合的方式持有基礎(chǔ)對象(RealSubject)的引用,并且實(shí)現(xiàn)了Subject接口,從客戶端的角度,Proxy代理對象可以作為Subject接口的替代品。
??從時(shí)序圖看,客戶端依靠代理對象(Proxy)工作,代理對象將請求轉(zhuǎn)發(fā)給基礎(chǔ)對象(RealSubject),在這個(gè)例子中,代理對象在轉(zhuǎn)發(fā)之前先進(jìn)行了訪問控制。到這里,我們可以把代理模式解釋為:由代理對象組合基礎(chǔ)對象,控制對基礎(chǔ)對象的訪問,擴(kuò)展新的功能。
??從OOP的原則看,代理模式主要體現(xiàn)了:

  • 單一指責(zé)原則

    基礎(chǔ)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的可重用性。

  • 里氏替換原則

    引用基類的地方必須能透明地使用子類對象。Proxy類實(shí)現(xiàn)了Subject接口,因此客戶端中引用Subject的地方都可以透明地替換為Proxy對象。

  • 開閉原則

    對修改是封閉的,對擴(kuò)展是開放的。RealSubject類中的實(shí)現(xiàn)只應(yīng)該因錯(cuò)誤而修改,擴(kuò)展的特性通過新建Proxy類實(shí)現(xiàn),而Proxy類通過組合的方式重用了RealSubject類中的實(shí)現(xiàn)。(繼承也是擴(kuò)展代碼的方式)

  • 依賴倒置原則

    高層次的模塊不依賴于低層次模塊的實(shí)現(xiàn)細(xì)節(jié)。因?yàn)榭蛻舳耍ǜ邔幽K)依賴于Subject接口,并不依賴于具體的RealSubject對象或Proxy對象。

繼承和組合的區(qū)別

繼承和組合是實(shí)現(xiàn)代碼復(fù)用和擴(kuò)展的兩種方式,兩者的差別體現(xiàn)在在靈活性和封裝性上。
靈活性上,繼承在編譯期靜態(tài)地定義了類的層次結(jié)構(gòu),直白明了,易于使用,但是反過來看,繼承不能在運(yùn)行期改變復(fù)用的代碼;相比之下,組合在運(yùn)行期可以通過替換引用的對象的方式來修改復(fù)用的對象。
封裝性上,繼承(白盒復(fù)用)有侵入性,子類必須全盤接收父類的(非私有)內(nèi)部實(shí)現(xiàn),一定程度上破壞了封裝;使用組合(黑盒復(fù)用)時(shí)對象間通過接口相互作用,對象的封裝性得到保護(hù)。

2. 動態(tài)代理

2.1 動態(tài)代理的定義

??Java中有兩種代理模式:靜態(tài)代理和動態(tài)代理,上一節(jié)提到的實(shí)現(xiàn)方式屬于靜態(tài)代理。
??靜態(tài)代理由程序員或代碼生成工具生成代理類,編譯之后生成Class文件,代理關(guān)系在編譯期就已經(jīng)綁定,一個(gè)代理關(guān)系是一個(gè)代理類對應(yīng)一個(gè)基礎(chǔ)接口。
??假設(shè)項(xiàng)目中需要給很多類擴(kuò)展相同的功能,比如所有和網(wǎng)絡(luò)有關(guān)的業(yè)務(wù)類需要打印請求日志。使用靜態(tài)代理時(shí),為了給每個(gè)業(yè)務(wù)類添加代理,做法是為每個(gè)業(yè)務(wù)類抽象一個(gè)接口,對應(yīng)地新建一個(gè)代理類,并在代理類中實(shí)現(xiàn)日志功能。例如:

public interface HttpApi {
    String get(String url);
}
?
public class RealModule implements HttpApi {
     @Override
     public String get(String url) {
         return "result";
     }
}
?
public class Proxy implements HttpApi {
    private HttpApi target;

    Proxy(HttpApi target) {
        this.target = target;
    }

    @Override
    public String get(String url) {
        // 擴(kuò)展的功能
        Log.i("http-statistic", url);
        // 訪問基礎(chǔ)對象
        return target.get(url);
    }
}

??假設(shè)有一個(gè)OtherHttpApi接口,可以選擇新建一個(gè)OtherProxy代理類,退而求其次也可以選擇將代碼寫在Proxy類中,讓Proxy類繼續(xù)代理OtherHttpApi。無論哪種選擇,都無法規(guī)避靜態(tài)代理的兩個(gè)缺點(diǎn):一是重復(fù)性,在程序規(guī)模稍大時(shí),需要代理的方法越多,重復(fù)的模板代碼就越多;二是脆弱性,基礎(chǔ)接口一旦改動,除了所有業(yè)務(wù)類需要改動外,代理類也必須改動。

Kotlin提供的by操作符可以在實(shí)現(xiàn)代理時(shí)減少冗余的模板代碼,但是很遺憾,在需要裝飾方法時(shí)收效甚微。

??有沒有辦法規(guī)避這些缺點(diǎn)呢?
??我們知道,JVM在真正開始引用一個(gè)類之前,需要先經(jīng)過加載、連接、初始化,將描述類信息的Class文件轉(zhuǎn)換為內(nèi)存中Class對象,這就是JVM的類加載機(jī)制。(一般地)Class文件在編譯后生成,類加載的工作是在運(yùn)行時(shí)完成,雖然會稍微增加一些性能開銷,但是卻給Java帶來的動態(tài)擴(kuò)展的特性,提供了高度的靈活性。Class文件(而不是源代碼)是JVM的語言,Java、Groovy、Kotlin等其他語言都可以把代碼編譯稱Class文件。既然如此,對于JVM來說,代理類的源代碼并不是必須的,只要有辦法得到代理類的Class文件,就可以執(zhí)行類加載。

Class文件是一個(gè)通俗的說法,表示存儲程序的二進(jìn)制字節(jié)碼(ByteCode),但不一定以磁盤文件的形式存在,可以來自網(wǎng)絡(luò)上的二進(jìn)制流,甚至在運(yùn)行時(shí)計(jì)算生成。不論哪種來源,Class文件總在類加載的加載階段轉(zhuǎn)變?yōu)閮?nèi)存中byte[]的形式,經(jīng)過連接和(可選的)初始化后,一個(gè)類才算加載完成。

??動態(tài)代理(Dymanic Proxy API)是JDK1.3中引入的特性,核心API是java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。它利用反射機(jī)制在運(yùn)行時(shí)生成代理類的字節(jié)碼,為Java平臺在帶來了運(yùn)行時(shí)動態(tài)擴(kuò)展對象行為的能力?;氐角懊胬樱?/p>

public class ProxyFactory {
    public static HttpApi getProxy(HttpApi target) {
        return (HttpApi) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new LogHandler(target));
    }

    private static class LogHandler implements InvocationHandler {
        private HttpApi target;

        LogHandler(HttpApi target) {
            this.target = target;
        }
        // method底層的方法無參數(shù)時(shí),args為空或者長度為0
        @Override
        public Object invoke(Object proxy, Method method, @Nullable Object[] args)       
               throws Throwable {
            // 擴(kuò)展的功能
            Log.i("http-statistic", (String) args[0]);
            // 訪問基礎(chǔ)對象
            return method.invoke(target, args);
        }
    }
}

??如果需要兼容多個(gè)業(yè)務(wù)接口,可以在生成代理的接口處使用泛型:

public class ProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T getProxy(T target) {
        return (T) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        new LogHandler(target));
    }

    private static class LogHandler<T> implements InvocationHandler {
        ...
    }
}
// 客戶端調(diào)用:
HttpAPi proxy = ProxyFactory.getProxy<HttpApi>(target);
OtherHttpApi proxy = ProxyFactory.getProxy<OtherHttpApi>(otherTarget);

??通過泛型參數(shù)傳遞不同的類型,客戶端可以按需實(shí)例化不同類型的代理對象?;A(chǔ)接口的所有方法都統(tǒng)一到InvocationHandler#invoke() 處理,即使有多個(gè)基礎(chǔ)業(yè)務(wù)需要代理,也不需要編寫過多重復(fù)的模板代碼;當(dāng)基礎(chǔ)接口變更時(shí),同步改動代理并不是必須的,從而規(guī)避了重復(fù)性和脆弱性。
??回顧下靜態(tài)代理和動態(tài)代理:

  • 共同點(diǎn):兩種代理模式實(shí)現(xiàn)都在不改動基礎(chǔ)對象的前提下,對基礎(chǔ)對象進(jìn)行訪問控制和擴(kuò)展,符合開閉原則。
  • 不同點(diǎn):靜態(tài)代理在程序規(guī)模稍大時(shí),重復(fù)性和脆弱性的缺點(diǎn)凸顯;動態(tài)代理(搭配泛型參數(shù))實(shí)現(xiàn)了一個(gè)代理同時(shí)處理N多個(gè)基礎(chǔ)接口,本質(zhì)上是代理類和基礎(chǔ)接口的解耦,一定程度上規(guī)避了靜態(tài)代理的缺點(diǎn)。從原理上講,靜態(tài)代理的代理類Class文件在編譯期生成,而動態(tài)代理的代理類Class文件在運(yùn)行時(shí)生成,代理類在coding階段并不存在,代理關(guān)系直到運(yùn)行時(shí)才確定。

2.2 動態(tài)代理的源碼分析

??這一節(jié),我們將分析java.lang.reflect.Proxy源碼,理解代理類Class文件是如何生成的,以及它如何將方法調(diào)用統(tǒng)一到j(luò)ava.lang.reflect.InvocationHandler接口中處理。
??先列出Proxy類的public方法:

Proxy類的public方法

為什么需要指定ClassLoader對象?

ClassLoader相當(dāng)于類的命名空間,類的唯一性由它本身和加載它的ClassLoader確定。一個(gè)Class文件如果由兩個(gè)ClassLoader加載,結(jié)果是兩個(gè)獨(dú)立的類。

Proxy.java:
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces){
    final Class<?>[] intfs = interfaces.clone();
    ...
    // 重點(diǎn):獲得代理類Class對象
    return getProxyClass0(loader, intfs);
}
?
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
    ...
    final Class<?>[] intfs = interfaces.clone();
    // 重點(diǎn):獲得代理類Class對象
    Class<?> cl = getProxyClass0(loader, intfs);
    ...
    // 獲得代理類構(gòu)造器
    // private static final Class<?>[] constructorParams = { InvocationHandler.class };
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    ...
    // 創(chuàng)建實(shí)例
    return newInstance(cons, ih);
}
?
public static boolean isProxyClass(Class<?> cl) {
    // 檢查時(shí)Proxy的子類,并且proxyClassCache中有緩存
    return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}
?
public static InvocationHandler getInvocationHandler(Object proxy){
     // 檢查是代理對象
    if (!isProxyClass(proxy.getClass())) {
        throw new IllegalArgumentException("not a proxy instance");
    }
    final Proxy p = (Proxy) proxy;
    final InvocationHandler ih = p.h;
    ...
    // 返回InvocationHandler對象
    return ih;
}

??可以看到,Proxy#getProxyClass() 和 Proxy#newProxyInstance() 都調(diào)用了 Proxy#getProxyClass0() 獲得代理類Class對象,后者還獲取了一個(gè)以InvocationHandler為參數(shù)的構(gòu)造器,最終創(chuàng)建并返回了一個(gè)代理對象實(shí)例。再看Proxy#getProxyClass0() 源碼:

Proxy.java:
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
    ...
    // 從緩存中獲取代理類,如果緩存未命中,則通過ProxyClassFactory生成代理類
    return proxyClassCache.get(loader, interfaces);
}
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
    // 代理類命名前綴
    private static final String proxyClassNamePrefix = "$Proxy";
    // 代理類命名后綴,從0遞增(原子Long)
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            // 驗(yàn)證參數(shù)interfaces和ClassLoder中加載的是同一個(gè)類
            // 驗(yàn)證參數(shù)interfaces是接口類型
            // 驗(yàn)證參數(shù)interfaces中沒有重復(fù)項(xiàng)
            // 否則拋出IllegalArgumentException
        }
        // 驗(yàn)證所有non-public接口來自同一個(gè)包

        //(一般地)代理類包名
        // public static final String PROXY_PACKAGE = "com.sun.proxy";
        String proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";

        // 代理類的全限定名
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // 重點(diǎn):生成字節(jié)碼數(shù)據(jù)
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
        // 重點(diǎn):從字節(jié)碼生成Class對象
        return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); 
    }
}

??ProxyClassFactory#defineClass0()是native方法(類似地,ClassLoader#defineClass()最終委派的方法也是native方法),從字節(jié)碼到Class對象的這部分類加載過程由JVM繼續(xù)進(jìn)行。重點(diǎn)看ProxyGenerator#generateProxyClass()如何生成代理類字節(jié)碼數(shù)據(jù):

Tip:Android系統(tǒng)中生成字節(jié)碼和從字節(jié)碼生成Class對象的步驟都在native實(shí)現(xiàn):
private static native Class<?> generateProxy(…)
對應(yīng)的native方法:dalvik/vm/native/java_lang_reflect_Proxy.cpp

ProxyGenerator.java:
public static byte[] generateProxyClass(final String var0, Class[] var1) {
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);
    // 是否保存至磁盤文件 
    final byte[] var3 = var2.generateClassFile();
    if (saveGeneratedFiles) {
        ...
         return var3;
    }
}
private byte[] generateClassFile() {
    // 只代理Object的hashCode、equals和toString
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);

    // 代理接口的每個(gè)方法
    ...
    for(var1 = 0; var1 < this.interfaces.length; ++var1) {
        ...
    }
    // 重點(diǎn):添加帶有InvocationHandler參數(shù)的構(gòu)造器
    this.methods.add(this.generateConstructor());
    var7 = this.proxyMethods.values().iterator();
    while(var7.hasNext()) {
        ...
        // 重點(diǎn):在每個(gè)代理的方法中調(diào)用InvocationHandler#invoke()
    }
    ByteArrayOutputStream var9 = new ByteArrayOutputStream();
    DataOutputStream var10 = new DataOutputStream(var9);
    ...
    return var9.toByteArray();
}

??可以看到ProxyGenerator#generateProxyClass() 是一個(gè)靜態(tài)public方法,我們直接調(diào)用,并將代理類Class文件寫入磁盤文件,使用IntelliJ IDEA的反編譯功能查看源代碼:

// Client:
byte[] classFile = ProxyGenerator.generateProxyClass("$proxy0",new Class[]{HttpApi.class});
// 直接寫入項(xiàng)目路徑下,方便使用IntelliJ IDEA的反編譯功能
String path = "/Users/pengxurui/IdeaProjects/untitled/src/proxy/HttpApi.class";
try(FileOutputStream fos = new FileOutputStream(path)){
    fos.write(classFile);
    fos.flush();
    System.out.println("success");
} catch (Exception e){
    e.printStackTrace();
    System.out.println("fail");
}
public final class $proxy0 extends Proxy implements HttpApi {
    //反射的元數(shù)據(jù)Method存儲起來,避免重復(fù)創(chuàng)建
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    /**
     * Object#hashCode()
     * Object#equals(Object)
     * Object#toString()
     */

    // 實(shí)現(xiàn)了HttpApi接口
    public final String get() throws  {
        try {
            //轉(zhuǎn)發(fā)到Invocation#invoke()
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            //Object#hashCode()
            //Object#equals(Object)
            //Object#toString()
            m3 = Class.forName("HttpApi").getMethod("get");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

??到這里就走完生成代理類Class文件的流程,可以得出:
??動態(tài)生成的代理類命名為com.sun.proxy$Proxy[從0開始的數(shù)字](例如:com.sun.proxy?$Proxy0),繼承了java.lang.reflect.Proxy類,有一個(gè)參數(shù)為InvocationHandler接口的構(gòu)造器,同時(shí)實(shí)現(xiàn)了指定的基礎(chǔ)接口,并將方法調(diào)用轉(zhuǎn)發(fā)給InvocationHandler#invoke()。參考UML類圖,仔細(xì)體會圖中紅色的箭頭,代理類(Prox$0)和HttpApi接口的代理關(guān)系是在運(yùn)行時(shí)才確定的:

動態(tài)代理UML 類圖

3. 擴(kuò)展思路的要點(diǎn)

3.1 面向切面編程

??OOP(Object Oriented Progarmming,面向?qū)ο缶幊蹋┦且环N使用封裝、繼承、多態(tài)將業(yè)務(wù)處理過程抽象為一個(gè)個(gè)清晰的層級結(jié)構(gòu)的編程思想。
??AOP(Aspect Oriented Programming,面向切面編程)是一種通過分離橫切關(guān)注點(diǎn)(cross-cutting concerns)來維持程序模塊化的編程思想。
??AOP是OOP的補(bǔ)充和完善,AOP更關(guān)注業(yè)務(wù)處理中的橫切步驟或階段,將影響多個(gè)步驟的橫切步驟從核心業(yè)務(wù)模塊單元單獨(dú)提煉出來,命名為切面(Aspect),降低了橫切步驟的侵入性。比如將日志、安全控制、異常處理、攔截點(diǎn)作為橫切關(guān)注點(diǎn)單獨(dú)提出,既有利于單獨(dú)維護(hù),又不會改動原有的業(yè)務(wù)模塊。靜態(tài)代理和動態(tài)代理都是實(shí)現(xiàn)AOP編程的方法。

3.2 視圖的概念

??回顧靜態(tài)代理和動態(tài)代理的UML類圖,我們有RealModule類實(shí)現(xiàn)了HttpApi接口。但是,必須有這層實(shí)現(xiàn)關(guān)系嗎,如果沒有,還算是代理模式嗎?

動態(tài)代理UML 類圖

??如果有人指著你的程序說:“不是代理模式!這里少了一個(gè)接口”,你會乖乖點(diǎn)頭嗎?提醒一下,設(shè)計(jì)模式本身并不是目的,解決問題才是。不要為了設(shè)計(jì)而設(shè)計(jì)!

人生有時(shí)候,乖乖的只是呆子,不乖的卻是才子??! —— 《蔡康永的說話之道2》

??很多人(和一部分書籍)存在兩個(gè)誤解:

  • 動態(tài)代理的缺點(diǎn)是接口代理,不能實(shí)現(xiàn)類代理(X)

    動態(tài)代理是接口代理,這句話本身沒有錯(cuò)誤。的確,Proxy#getProxyClass() 和Proxy#newProxyInstance() 的參數(shù)只能接收接口的Class對象,如果傳入類的Class對象,會拋出IllegalArgumentException。那么作者(Peter Jones)為什么這么設(shè)計(jì)?

    從語義上,類代理是為類的每個(gè)方法提供代理,這就包括類中聲明的方法(Class#getDeclaredMethods())和父類中聲明的方法。結(jié)果是,客戶端將面對一個(gè)可能龐大而臃腫的接口,但是客戶端可能并不對每個(gè)方法都感興趣。即使暫不考慮為冗余的方法生成代理帶來的性能損失(一定的CPU計(jì)算時(shí)間和對象內(nèi)存占用),暴露過多接口其實(shí)違背了最小接口原則:類間的依賴關(guān)系應(yīng)該建立在最小接口上。為了降低耦合,實(shí)例化代理對象后依然需要將對象映射為一個(gè)更合適的接口,既然如此,為什么不一開始就根據(jù)這個(gè)合適的接口來生成代理類呢?

  • 業(yè)務(wù)對象必須實(shí)現(xiàn)基礎(chǔ)接口,否則不能使用動態(tài)代理(X)

    這個(gè)想法可能來自于一些沒有實(shí)現(xiàn)任何接口的類,因此就沒有辦法得到接口的Class對象作為Proxy#newProxyInstance() 的參數(shù),這確實(shí)會帶來一些麻煩,舉個(gè)例子:

package com.domain;
public interface HttpApi {
    String get();
}

// 另一個(gè)包的non-public接口
package com.domain.inner;
/**non-public**/interface OtherHttpApi{
    String get();
}

package com.domain.inner;
// OtherHttpApiImpl類沒有實(shí)現(xiàn)HttpApi接口或者沒有實(shí)現(xiàn)任何接口
public class OtherHttpApiImpl  /**extends OtherHttpApi**/{
    public String get() {
        return "result";
    }
}

// Client:
 HttpApi api = (HttpApi) Proxy.newProxyInstance(...}, new InvocationHandler() {
    OtherHttpApiImpl impl = new OtherHttpApiImpl();
  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO:擴(kuò)展的新功能
        // IllegalArgumentException: object is not an instance of declaring class
        return method.invoke(impl,args);
    }
});
api.get();

??這個(gè)例子里,OtherHttpApiImpl類因?yàn)闅v史原因沒有實(shí)現(xiàn)HttpApi接口,雖然方法簽名與HttpApi接口的方法簽名完全相同,但是遺憾,參數(shù)method來自于HttpApi接口,而不是OtherHttpApiImpl類。也有補(bǔ)救的辦法,找到HttpApi接口中簽名相同的Mthod,使用這個(gè)Method來轉(zhuǎn)發(fā)調(diào)用:

HttpApi api = (HttpApi) Proxy.newProxyInstance(...}, new InvocationHandler() {
    OtherHttpApiImpl impl = new OtherHttpApiImpl();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO:擴(kuò)展的新功能
        if (method.getDeclaringClass() != impl.getClass()) {
            // 找到相同簽名的方法
            Method realMethod = impl.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
            return realMethod.invoke(impl, args);
        }else{
            return method.invoke(impl,args);
        }
    }
});

??其實(shí)沒有人規(guī)定一定要調(diào)用Method#invoke(...)轉(zhuǎn)發(fā)調(diào)用到業(yè)務(wù)對象,直接調(diào)用和反射調(diào)用都可以訪問基礎(chǔ)對象!再次回顧代理模式的定義:由代理對象組合基礎(chǔ)對象,控制對基礎(chǔ)對象的訪問,擴(kuò)展新的功能。
??這里提到“視圖”的概念,即允許將任何對象視為任何接口,當(dāng)基礎(chǔ)對象和接口不匹配時(shí),內(nèi)部使用一些映射。也許你會理解為適配器模式和外觀模式的思想,exactly!只是后兩者通常是一種靜態(tài)轉(zhuǎn)換,動態(tài)代理的威力在于,如前所述:一個(gè)代理同時(shí)處理N多個(gè)基礎(chǔ)接口,代理關(guān)系在運(yùn)行時(shí)確定。試想一下,一個(gè)(或少數(shù))基礎(chǔ)對象可以代理整個(gè)應(yīng)用的接口,或者數(shù)以萬計(jì)的應(yīng)用的接口。What a magic! 有名Retrofit框架就是這樣的例子。

4. 相關(guān)的設(shè)計(jì)模式

??文章開頭提到,許多其他設(shè)計(jì)模式本質(zhì)上是在特定的上下文中,對代理模式進(jìn)行的針對性優(yōu)化。設(shè)計(jì)模式之間確實(shí)有相似的地方,關(guān)鍵的區(qū)別在于它們關(guān)注/強(qiáng)調(diào)的目的不同。再提醒一次,設(shè)計(jì)模式是指導(dǎo)解決軟件問題的思想,而不是約束。

4.1 裝飾模式

??裝飾模式(Decorate Pattern)指動態(tài)地給基礎(chǔ)對象添加額外的功能,相對于代理模式,更多強(qiáng)調(diào)的是增強(qiáng)所裝飾對象的功能。

4.2 外觀模式

??外觀模式(Facade Pattern),封裝子系統(tǒng)間的邏輯和交互,將行為開放給高層接口,強(qiáng)調(diào)的是最小接口原則,降低了高層模塊對于子系統(tǒng)的耦合,對外觀接口的封裝也使得接口更易用。外觀模式在開發(fā)過程中運(yùn)用頻率非常高,比如很多框架為了降低用戶的使用成本,會提供一個(gè)統(tǒng)一的高層接口。
??Android中的Context類就是外觀模式的典型例子,Context是一個(gè)抽象類,通常翻譯為上下文,指程序運(yùn)行環(huán)境的基本信息。Context類有兩個(gè)直接子類,ContextImpl類和ContextWrapper類,參考如下UML類圖:

Context類UML 類圖

??子類ContextImpl類就是外觀類,真正的方法實(shí)現(xiàn)并不在ContextImpl類中,而在其內(nèi)部封裝的多個(gè)子系統(tǒng)中,比如AMS管理Activity(及其他組件)狀態(tài),PMS管理應(yīng)用包相關(guān)的信息,Resource管理資源系統(tǒng)。另一個(gè)子類ContextWrapper有三個(gè)直接子類:Service、ContextThemeWrapper、Application,分別提供了不同的擴(kuò)展,參考以下源碼:

  • Application
ActivityThread.java:
private void handleBindApplication(AppBindData data){
    ...
    // 創(chuàng)建Application實(shí)例
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    mInitialApplication = app;
    ...
    // 重點(diǎn):回調(diào)onCreate()
    mInstrumentation.callApplicationOnCreate(app);
    ...
}

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
    // 避免重復(fù)構(gòu)造
    if (mApplication != null) {
        return mApplication;
    }
    ...
    // 創(chuàng)建基礎(chǔ)對象ContextImpl
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
    // 重點(diǎn):創(chuàng)建Application實(shí)例
    app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
    appContext.setOuterContext(app);
    ...
    return app;
}

Instrumentation.java:
public Application newApplication(ClassLoader cl, String className, Context context){
    Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
    // 重點(diǎn):設(shè)置基礎(chǔ)對象
    app.attach(context);
    return app;
}
  • Activity
ActivityThread.java:
/**Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 獲取ActivityInfo信息
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }
    // 獲取Component信息
    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }
    // 重點(diǎn):創(chuàng)建基礎(chǔ)對象ContextImpl
    ContextImpl appContext = createBaseContextForActivity(r);
    // 創(chuàng)建Activity實(shí)例
    java.lang.ClassLoader cl = appContext.getClassLoader();
    Activity activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
    ...
    // 獲取Application對象
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);  
    appContext.setOuterContext(activity);
    // 重點(diǎn):設(shè)置基礎(chǔ)對象
    activity.attach(appContext, this, app, ...);
    // 設(shè)置Intent
    if (customIntent != null) {
    activity.mIntent = customIntent;
    }
    ...
    // 設(shè)置主題
    int theme = r.activityInfo.getThemeResource();
    if (theme != 0) {
        activity.setTheme(theme);
    }
    // 重點(diǎn):回調(diào)onCreate()
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }
    ...
}   
  • Service
ActivityThread.java:
private void handleCreateService(CreateServiceData data) {
    ...
    // 重點(diǎn):創(chuàng)建Service實(shí)例
    Service service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
    ...
    // 創(chuàng)建基礎(chǔ)對象ContextImpl
    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
    context.setOuterContext(service);
    // 獲取Application實(shí)例
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    // 重點(diǎn):設(shè)置基礎(chǔ)對象
    service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
    // 重點(diǎn):回調(diào)onCreate()
    service.onCreate();
    ...
}

??分析Application、Service、Activity的初始化源碼,可以看到它們處理基礎(chǔ)對象ContextImpl的流程是非常相似的:創(chuàng)建基礎(chǔ)對象ContextImpl,并調(diào)用attach(),內(nèi)部調(diào)用ContextWrapper#attachBaseContext() ,最終建立起與ContextImpl的代理關(guān)系。

Activity.java:
public class Activity extends ContextWrapper{
    final void attach(Context context,ActivityThread aThread,Application application,...){
         ...
        attachBaseContext(context);
        mMainThread = aThread;
        mApplication = application;c
    }
}

ContextWrapper.java:
public class ContextWrapper extends Context{
    private Context mBase;
        protected void attachBaseContext(Context base){
            ...
            mBase = base;
        }
}

推薦閱讀


參考


創(chuàng)作不易,你的「三連」是丑丑最大的動力,我們下次見!

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

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

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