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

此篇文章說是通過代理模式來實現(xiàn)簡單的AOP其實只是順帶的涮母,主要目的還是講一下代理模式甲锡,在Android中使用的代理模式主要分為靜態(tài)代理和動態(tài)代理衙耕,靜態(tài)代理編譯期間就已確認代理類安寺,而動態(tài)代理在運行期間才能確認代理類。

1第美、代理模式簡介

  • 定義:給一個對象提供一個代理對象蝶锋,通過代理對象來控制目標(biāo)對象的訪問,代理對象就像是中介什往,起到客戶到目標(biāo)的溝通扳缕,即客戶不直接操控目標(biāo)對象,而是通過中介(代理對象)間接地操控目標(biāo)對象别威。
代理模式UML類圖

通過類圖可以發(fā)現(xiàn)躯舔,代理模式的代理對象Proxy和目標(biāo)對象Subject實現(xiàn)同一個接口,客戶調(diào)用的是Proxy對象省古,Proxy可以控制Subject的訪問粥庄,真正的功能實現(xiàn)是在Subject完成的。
代理模式在Java中的實現(xiàn)分為兩種豺妓,一種是靜態(tài)代理惜互,一種是動態(tài)代理。

  • 靜態(tài)代理其實就是以上UML類圖的標(biāo)準實現(xiàn)琳拭,其在編譯期間就會確定真正的代理類训堆,即.class文件編譯完成后就確定下來了。
  • 動態(tài)代理是標(biāo)準靜態(tài)代理的一種擴展和變形臀栈,最主要一點是蔫慧,動態(tài)代理在編譯完成后是沒有一個代理類的具體類的,但這不代表它沒有代理類权薯,其代理類會在運行期間自動生成姑躲,并通過反射的方式進行方法調(diào)用。

2盟蚣、靜態(tài)代理

靜態(tài)代理可以說是很簡單了黍析,拿點江湖事來舉例,一個武林盟主想去揍一個人屎开,他一般不會自己去阐枣,他可以找武當(dāng)派的人,或者少林寺的人奄抽,最后打人的是各個門派的弟子蔼两,武林盟主其實除了下命令之后啥都沒干,干活的都是小弟逞度。

/**
 * 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接口档泽,這個接口可以是多個俊戳,這里定義了兩個IShaolinIWuDang揖赴,再定義類圖中的目標(biāo)對象類ISubject,這里的各自接口實現(xiàn)類是ShaoLinManWuDangMan抑胎,而MengZhuProxy就是代理類盟主了燥滑,盟主不需要自己會少林絕學(xué),他只需要叫少林寺的弟子去干活阿逃,盟主也不必會武當(dāng)太極拳铭拧,他能控制武當(dāng)派弟子就好。

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

運行效果就是這樣恃锉,沒啥大驚小怪的羽历,基本上就是按照UML類圖照葫蘆畫瓢就完成了。


3淡喜、動態(tài)代理

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

動態(tài)代理

通過其UML類圖可以發(fā)現(xiàn)其比標(biāo)準的類圖要復(fù)雜,主要引入了Proxy類褥琐,這是Java自帶的锌俱,我們通過調(diào)用Proxy類的newProxyInstance方法來獲取一個代理類實例,此外還引入了InvocationHandler接口及其實現(xiàn)類敌呈,稱之為調(diào)節(jié)處理器類贸宏。
初看起來有點懵逼,讓我們理一下磕洪,會發(fā)現(xiàn)動態(tài)代理包含了兩層的靜態(tài)代理關(guān)系:

  • 第一層是ProxyInvocationHandler吭练,在這層關(guān)系中,Proxy是代理類析显,而InvocationHandler是真正的實現(xiàn)類鲫咽,即目標(biāo)對象類。
  • 第二層是InvocationHandlerISubject谷异,這里的話分尸,InvocationHandler中持有ISubject的實現(xiàn)類對象,所以InvocationHandler是代理類歹嘹,而ISubject是最終的實現(xiàn)類箩绍。
    會發(fā)現(xiàn),饒了一圈荞下,最后的目標(biāo)對象還是ISubject伶选,和靜態(tài)代理的最主要區(qū)別在于SubjectProxy這個類是在程序運行中動態(tài)生成的史飞。

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

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

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

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

    /**
     * 進行具體的代理操作
     *
     * @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("給自己加戲-動態(tài)代理");
            mWuDangMan.playTaijiquan();
        } else if (method.getName().equals("playYiGinChing")) {
            System.out.println("給自己加戲-動態(tài)代理");
            mShaolinMan.playYiGinChing();
        }
        return proxy;
    }
}

最關(guān)鍵在于invoke()方法陨簇,它會攔截所有實現(xiàn)了接口的方法吐绵,根據(jù)不同的方法名來達到我們的目的,在InvocationHandler類完成之后河绽,就可以進行調(diào)用己单,調(diào)用的代碼和運行結(jié)果如下:

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


對比靜態(tài)代理的使用過程耙饰,動態(tài)代理的使用顯得要復(fù)雜一些纹笼,其中最關(guān)鍵的是Proxy.newProxyInstance()方法,用于返回動態(tài)生成的代理對象苟跪,需要傳入三個參數(shù)廷痘,類加載器ClassLoader,需要實現(xiàn)的接口數(shù)組件已,以及InvocationHandler攔截對象笋额,這些參數(shù)都是生成動態(tài)代理對象所需要的。

4篷扩、動態(tài)代理調(diào)用分析

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

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

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

首先通過類加載器和接口數(shù)組來生成類文件枢冤,然后通過反射的方式來實例化對象,并把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()) {
                // 傳入的要實現(xiàn)類必須是接口類,否則會拋出異常
                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)源碼中限制了我們傳入的需要實現(xiàn)的類必須是接口類趟咆,非接口類會拋出異常,最后通過本地native方法generateProxy()生成并返回類對象梅屉。
這一個過程是比較明了的值纱,把動態(tài)生成的類文件打印出來就知道代理類中的具體調(diào)用了,這里我們使用Java中提供的ProxyGenerator類的靜態(tài)方法generateProxyClass()坯汤,來打印出動態(tài)生成的代理類class字節(jié)碼虐唠。

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

需要提到的一點是,在Android Studio中調(diào)用不了ProxyGenerator這個類惰聂,避免太麻煩疆偿,所以我這邊是直接把項目遷到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 {
            // 通過反射機制來調(diào)用各個方法,動態(tài)代理會對運行期間的性能造成一定影響
            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并實現(xiàn)我們定義的接口,其中每個方法調(diào)用時沒有做具體的事情妹沙,而是直接調(diào)用super.h.invoke()super.h就是InvocationHandler對象饲趋,所以SubjectProxy把任務(wù)拋給了InvocationHandler進行處理,而InvocationHandler就是通過我們可以控制的撤蟆,繞來繞去把控制權(quán)又交到了我們的手中奕塑。
前面提到過動態(tài)代理只能代理接口方法,原因也在這里家肯,動態(tài)生成的類直接實現(xiàn)我們定義的接口龄砰,從而實現(xiàn)其中的接口方法,在方法調(diào)用時再把功能交還到我們的手中讨衣。動態(tài)代理可以統(tǒng)一對接口的所有實現(xiàn)類進行操作寝贡,而不用修改每個實現(xiàn)類,通過Proxy->InvocationHandler->ISubject的順序把控制權(quán)一步步的交接值依,最后真正的實現(xiàn)類和靜態(tài)代理是一樣的,只是因為要動態(tài)生成代理類從而導(dǎo)致中間過程饒了一點碟案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愿险,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子价说,更是在濱河造成了極大的恐慌辆亏,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳖目,死亡現(xiàn)場離奇詭異扮叨,居然都是意外死亡,警方通過查閱死者的電腦和手機领迈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門彻磁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狸捅,你說我怎么就攤上這事衷蜓。” “怎么了尘喝?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵磁浇,是天一觀的道長。 經(jīng)常有香客問我朽褪,道長置吓,這世上最難降的妖魔是什么无虚? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮衍锚,結(jié)果婚禮上友题,老公的妹妹穿的比我還像新娘。我一直安慰自己构拳,他們只是感情好咆爽,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著置森,像睡著了一般斗埂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凫海,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天呛凶,我揣著相機與錄音,去河邊找鬼行贪。 笑死漾稀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的建瘫。 我是一名探鬼主播崭捍,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼啰脚!你這毒婦竟也來了殷蛇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤橄浓,失蹤者是張志新(化名)和其女友劉穎粒梦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荸实,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡匀们,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了准给。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泄朴。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖露氮,靈堂內(nèi)的尸體忽然破棺而出叼旋,到底是詐尸還是另有隱情,我是刑警寧澤沦辙,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布夫植,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏详民。R本人自食惡果不足惜延欠,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沈跨。 院中可真熱鬧由捎,春花似錦、人聲如沸饿凛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涧窒。三九已至心肪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纠吴,已是汗流浹背硬鞍。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戴已,地道東北人固该。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像糖儡,于是被迫代替她去往敵國和親伐坏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

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