Android代理模式實(shí)現(xiàn)簡(jiǎn)單的AOP

此篇文章說(shuō)是通過(guò)代理模式來(lái)實(shí)現(xiàn)簡(jiǎn)單的AOP其實(shí)只是順帶的,主要目的還是講一下代理模式,在Android中使用的代理模式主要分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理編譯期間就已確認(rèn)代理類,而動(dòng)態(tài)代理在運(yùn)行期間才能確認(rèn)代理類。

1、代理模式簡(jiǎn)介

  • 定義:給一個(gè)對(duì)象提供一個(gè)代理對(duì)象,通過(guò)代理對(duì)象來(lái)控制目標(biāo)對(duì)象的訪問(wèn),代理對(duì)象就像是中介,起到客戶到目標(biāo)的溝通,即客戶不直接操控目標(biāo)對(duì)象,而是通過(guò)中介(代理對(duì)象)間接地操控目標(biāo)對(duì)象。
代理模式UML類圖

通過(guò)類圖可以發(fā)現(xiàn),代理模式的代理對(duì)象Proxy和目標(biāo)對(duì)象Subject實(shí)現(xiàn)同一個(gè)接口,客戶調(diào)用的是Proxy對(duì)象,Proxy可以控制Subject的訪問(wèn),真正的功能實(shí)現(xiàn)是在Subject完成的。
代理模式在Java中的實(shí)現(xiàn)分為兩種,一種是靜態(tài)代理,一種是動(dòng)態(tài)代理。

  • 靜態(tài)代理其實(shí)就是以上UML類圖的標(biāo)準(zhǔn)實(shí)現(xiàn),其在編譯期間就會(huì)確定真正的代理類,即.class文件編譯完成后就確定下來(lái)了。
  • 動(dòng)態(tài)代理是標(biāo)準(zhǔn)靜態(tài)代理的一種擴(kuò)展和變形,最主要一點(diǎn)是,動(dòng)態(tài)代理在編譯完成后是沒(méi)有一個(gè)代理類的具體類的,但這不代表它沒(méi)有代理類,其代理類會(huì)在運(yùn)行期間自動(dòng)生成,并通過(guò)反射的方式進(jìn)行方法調(diào)用。

2、靜態(tài)代理

靜態(tài)代理可以說(shuō)是很簡(jiǎn)單了,拿點(diǎn)江湖事來(lái)舉例,一個(gè)武林盟主想去揍一個(gè)人,他一般不會(huì)自己去,他可以找武當(dāng)派的人,或者少林寺的人,最后打人的是各個(gè)門派的弟子,武林盟主其實(shí)除了下命令之后啥都沒(méi)干,干活的都是小弟。

/**
 * Author:xishuang
 * Date:2018.03.07
 * Des:少林絕學(xué)《易筋經(jīng)》
 */
public interface IShaolin {
    void playYiGinChing();
}

/**
 * Author:xishuang
 * Date:2018.03.07
 * Des:武當(dāng)派絕學(xué)太極拳
 */
public interface IWuDang {
    void playTaijiquan();
}

public class ShaoLinMan implements IShaolin {
    @Override
    public void playYiGinChing() {
        System.out.println("在下少林->易筋經(jīng)");
    }
}

public class WuDangMan implements IWuDang {

    @Override
    public void playTaijiquan() {
        System.out.println("在下武當(dāng)->太極拳");
    }
}

/**
 * Author:xishuang
 * Date:2018.02.06
 * Des:靜態(tài)代理需要的代理類,編譯器就已經(jīng)確定
 */
public class MengZhuProxy implements IWuDang, IShaolin {
    private IWuDang mWuDangMan;
    private IShaolin mShaolinMan;

    public MengZhuProxy() {
        mWuDangMan = new WuDangMan();
        mShaolinMan = new ShaoLinMan();
    }

    @Override
    public void playYiGinChing() {
        System.out.println("給自己加戲");
        mShaolinMan.playYiGinChing();
    }

    @Override
    public void playTaijiquan() {
        System.out.println("給自己加戲");
        mWuDangMan.playTaijiquan();
    }
}

就像代碼里頭展示的,定義好ISubject接口,這個(gè)接口可以是多個(gè),這里定義了兩個(gè)IShaolinIWuDang,再定義類圖中的目標(biāo)對(duì)象類ISubject,這里的各自接口實(shí)現(xiàn)類是ShaoLinManWuDangMan,而MengZhuProxy就是代理類盟主了,盟主不需要自己會(huì)少林絕學(xué),他只需要叫少林寺的弟子去干活,盟主也不必會(huì)武當(dāng)太極拳,他能控制武當(dāng)派弟子就好。

/**
     * 靜態(tài)代理
     */
    private void staticProxy() {
        MengZhuProxy proxy = new MengZhuProxy();
        proxy.playTaijiquan();
        proxy.playYiGinChing();
    }

運(yùn)行效果就是這樣,沒(méi)啥大驚小怪的,基本上就是按照UML類圖照葫蘆畫瓢就完成了。


3、動(dòng)態(tài)代理

相比于靜態(tài)代理, 動(dòng)態(tài)代理的優(yōu)勢(shì)在于可以很方便的對(duì)代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類的函數(shù),這也就是AOP的一種實(shí)現(xiàn)方式,但是只能針對(duì)接口方法進(jìn)行處理,具體原因后面會(huì)順勢(shì)提到。

動(dòng)態(tài)代理

通過(guò)其UML類圖可以發(fā)現(xiàn)其比標(biāo)準(zhǔn)的類圖要復(fù)雜,主要引入了Proxy類,這是Java自帶的,我們通過(guò)調(diào)用Proxy類的newProxyInstance方法來(lái)獲取一個(gè)代理類實(shí)例,此外還引入了InvocationHandler接口及其實(shí)現(xiàn)類,稱之為調(diào)節(jié)處理器類。
初看起來(lái)有點(diǎn)懵逼,讓我們理一下,會(huì)發(fā)現(xiàn)動(dòng)態(tài)代理包含了兩層的靜態(tài)代理關(guān)系:

  • 第一層是ProxyInvocationHandler,在這層關(guān)系中,Proxy是代理類,而InvocationHandler是真正的實(shí)現(xiàn)類,即目標(biāo)對(duì)象類。
  • 第二層是InvocationHandlerISubject,這里的話,InvocationHandler中持有ISubject的實(shí)現(xiàn)類對(duì)象,所以InvocationHandler是代理類,而ISubject是最終的實(shí)現(xiàn)類。
    會(huì)發(fā)現(xiàn),饒了一圈,最后的目標(biāo)對(duì)象還是ISubject,和靜態(tài)代理的最主要區(qū)別在于SubjectProxy這個(gè)類是在程序運(yùn)行中動(dòng)態(tài)生成的。

續(xù)上面的武林盟主栗子,我們只需要增加一個(gè)InvocationHandler類,這個(gè)類會(huì)接收到所有對(duì)接口方法的調(diào)用,在其中我們可以對(duì)接口方法進(jìn)行攔截和實(shí)現(xiàn)自己的功能。

/**
 * Author:xishuang
 * Date:2018.02.07
 * Des:具體的代理邏輯實(shí)現(xiàn)
 */
public class MengZhuInvocationHandler implements InvocationHandler {

    /**
     * InvocationHandler持有的被代理對(duì)象
     */
    private IWuDang mWuDangMan;
    private IShaolin mShaolinMan;

    MengZhuInvocationHandler(IWuDang wuDang, IShaolin shaolin) {
        mWuDangMan = wuDang;
        mShaolinMan = shaolin;
    }

    /**
     * 進(jìn)行具體的代理操作
     *
     * @param proxy  所代理的類
     * @param method 正在調(diào)用得方法
     * @param args   方法的參數(shù)
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("playTaijiquan")) {
            System.out.println("給自己加戲-動(dòng)態(tài)代理");
            mWuDangMan.playTaijiquan();
        } else if (method.getName().equals("playYiGinChing")) {
            System.out.println("給自己加戲-動(dòng)態(tài)代理");
            mShaolinMan.playYiGinChing();
        }
        return proxy;
    }
}

最關(guān)鍵在于invoke()方法,它會(huì)攔截所有實(shí)現(xiàn)了接口的方法,根據(jù)不同的方法名來(lái)達(dá)到我們的目的,在InvocationHandler類完成之后,就可以進(jìn)行調(diào)用,調(diào)用的代碼和運(yùn)行結(jié)果如下:

/**
     * 動(dòng)態(tài)代理
     */
    private static void daynamicProxy() {
        // 創(chuàng)建一個(gè)與代理對(duì)象相關(guān)聯(lián)的InvocationHandler
        InvocationHandler handler = new MengZhuInvocationHandler(new WuDangMan(), new ShaoLinMan());
        // 創(chuàng)建一個(gè)代理對(duì)象studentProxy來(lái)代理student,代理對(duì)象的每個(gè)執(zhí)行方法都會(huì)替換執(zhí)行Invocation中的invoke方法
        IWuDang studentProxy = (IWuDang) Proxy.newProxyInstance(getClassLoader(), new Class[]{IWuDang.class, IShaolin.class}, handler);
        studentProxy.playTaijiquan();
        ((IShaolin) studentProxy).playYiGinChing();
    }


對(duì)比靜態(tài)代理的使用過(guò)程,動(dòng)態(tài)代理的使用顯得要復(fù)雜一些,其中最關(guān)鍵的是Proxy.newProxyInstance()方法,用于返回動(dòng)態(tài)生成的代理對(duì)象,需要傳入三個(gè)參數(shù),類加載器ClassLoader,需要實(shí)現(xiàn)的接口數(shù)組,以及InvocationHandler攔截對(duì)象,這些參數(shù)都是生成動(dòng)態(tài)代理對(duì)象所需要的。

4、動(dòng)態(tài)代理調(diào)用分析

單單從代碼調(diào)用關(guān)系上來(lái)看是比較難看出UML類圖中的那種關(guān)系的,最主要原因是沒(méi)有直觀的看到動(dòng)態(tài)生成的SubjectProxy類,所以我們就順著Proxy.newProxyInstance()這條線來(lái)看一下背后的調(diào)用軌跡。

/**
 * 返回指定接口的動(dòng)態(tài)生成類的實(shí)例
 */
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
                                          InvocationHandler invocationHandler)
            throws IllegalArgumentException {

       ...
            return getProxyClass(loader, interfaces)
                    .getConstructor(InvocationHandler.class)
                    .newInstance(invocationHandler);
        ...
    }

首先通過(guò)類加載器和接口數(shù)組來(lái)生成類文件,然后通過(guò)反射的方式來(lái)實(shí)例化對(duì)象,并把InvocationHandler在構(gòu)造方法中傳入以便后續(xù)調(diào)用,繼續(xù)看getProxyClass()方法。

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
            throws IllegalArgumentException {
        ...

        for (Class<?> c : interfaces) {
            if (!c.isInterface()) {
                // 傳入的要實(shí)現(xiàn)類必須是接口類,否則會(huì)拋出異常
                throw new IllegalArgumentException(c + " is not an interface");
            }
            ...
        }

        ...

        Class<?> result;
        synchronized (loader.proxyCache) {
            result = loader.proxyCache.get(interfaceList);
            if (result == null) {
                String name = baseName + nextClassNameIndex++;
                result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray);
                loader.proxyCache.put(interfaceList, result);
            }
        }

        return result;
    }

這里只抽取關(guān)鍵代碼,可以發(fā)現(xiàn)源碼中限制了我們傳入的需要實(shí)現(xiàn)的類必須是接口類,非接口類會(huì)拋出異常,最后通過(guò)本地native方法generateProxy()生成并返回類對(duì)象。
這一個(gè)過(guò)程是比較明了的,把動(dòng)態(tài)生成的類文件打印出來(lái)就知道代理類中的具體調(diào)用了,這里我們使用Java中提供的ProxyGenerator類的靜態(tài)方法generateProxyClass(),來(lái)打印出動(dòng)態(tài)生成的代理類class字節(jié)碼。

private static void generyClass() {
        byte[] classFile = ProxyGenerator.generateProxyClass("GenerateProxy", new Class[]{IWuDang.class, IShaolin.class});
        String path = "D:/項(xiàng)目/proxyTest/GenerateProxy.class";
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("文件寫入成功");
        } catch (Exception e) {
            System.out.println("文件寫入失敗");
        }
    }

需要提到的一點(diǎn)是,在Android Studio中調(diào)用不了ProxyGenerator這個(gè)類,避免太麻煩,所以我這邊是直接把項(xiàng)目遷到IntelliJ IDEA中,最后生成代理class文件。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class GenerateProxy extends Proxy implements IWuDang, IShaolin {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public GenerateProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void playTaijiquan() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void playYiGinChing() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            // 通過(guò)反射機(jī)制來(lái)調(diào)用各個(gè)方法,動(dòng)態(tài)代理會(huì)對(duì)運(yùn)行期間的性能造成一定影響
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("IWuDang").getMethod("playTaijiquan");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("IShaolin").getMethod("playYiGinChing");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

SubjectProxy繼承了Proxy并實(shí)現(xiàn)我們定義的接口,其中每個(gè)方法調(diào)用時(shí)沒(méi)有做具體的事情,而是直接調(diào)用super.h.invoke()super.h就是InvocationHandler對(duì)象,所以SubjectProxy把任務(wù)拋給了InvocationHandler進(jìn)行處理,而InvocationHandler就是通過(guò)我們可以控制的,繞來(lái)繞去把控制權(quán)又交到了我們的手中。
前面提到過(guò)動(dòng)態(tài)代理只能代理接口方法,原因也在這里,動(dòng)態(tài)生成的類直接實(shí)現(xiàn)我們定義的接口,從而實(shí)現(xiàn)其中的接口方法,在方法調(diào)用時(shí)再把功能交還到我們的手中。動(dòng)態(tài)代理可以統(tǒng)一對(duì)接口的所有實(shí)現(xiàn)類進(jìn)行操作,而不用修改每個(gè)實(shí)現(xiàn)類,通過(guò)Proxy->InvocationHandler->ISubject的順序把控制權(quán)一步步的交接,最后真正的實(shí)現(xiàn)類和靜態(tài)代理是一樣的,只是因?yàn)橐獎(jiǎng)討B(tài)生成代理類從而導(dǎo)致中間過(guò)程饒了一點(diǎn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容