Android代理模式实现简单的AOP

此篇文章说是通过代理模式来实现简单的AOP其实只是顺带的,主要目的还是讲一下代理模式,在Android中使用的代理模式主要分为静态代理和动态代理,静态代理编译期间就已确认代理类,而动态代理在运行期间才能确认代理类。

1、代理模式简介

  • 定义:给一个对象提供一个代理对象,通过代理对象来控制目标对象的访问,代理对象就像是中介,起到客户到目标的沟通,即客户不直接操控目标对象,而是通过中介(代理对象)间接地操控目标对象。
代理模式UML类图

通过类图可以发现,代理模式的代理对象Proxy和目标对象Subject实现同一个接口,客户调用的是Proxy对象,Proxy可以控制Subject的访问,真正的功能实现是在Subject完成的。
代理模式在Java中的实现分为两种,一种是静态代理,一种是动态代理。

  • 静态代理其实就是以上UML类图的标准实现,其在编译期间就会确定真正的代理类,即.class文件编译完成后就确定下来了。
  • 动态代理是标准静态代理的一种扩展和变形,最主要一点是,动态代理在编译完成后是没有一个代理类的具体类的,但这不代表它没有代理类,其代理类会在运行期间自动生成,并通过反射的方式进行方法调用。

2、静态代理

静态代理可以说是很简单了,拿点江湖事来举例,一个武林盟主想去揍一个人,他一般不会自己去,他可以找武当派的人,或者少林寺的人,最后打人的是各个门派的弟子,武林盟主其实除了下命令之后啥都没干,干活的都是小弟。

/**
 * Author:xishuang
 * Date:2018.03.07
 * Des:少林绝学《易筋经》
 */
public interface IShaolin {
    void playYiGinChing();
}

/**
 * Author:xishuang
 * Date:2018.03.07
 * Des:武当派绝学太极拳
 */
public interface IWuDang {
    void playTaijiquan();
}

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

public class WuDangMan implements IWuDang {

    @Override
    public void playTaijiquan() {
        System.out.println("在下武当->太极拳");
    }
}

/**
 * Author:xishuang
 * Date:2018.02.06
 * Des:静态代理需要的代理类,编译器就已经确定
 */
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,再定义类图中的目标对象类ISubject,这里的各自接口实现类是ShaoLinManWuDangMan,而MengZhuProxy就是代理类盟主了,盟主不需要自己会少林绝学,他只需要叫少林寺的弟子去干活,盟主也不必会武当太极拳,他能控制武当派弟子就好。

/**
     * 静态代理
     */
    private void staticProxy() {
        MengZhuProxy proxy = new MengZhuProxy();
        proxy.playTaijiquan();
        proxy.playYiGinChing();
    }

运行效果就是这样,没啥大惊小怪的,基本上就是按照UML类图照葫芦画瓢就完成了。


3、动态代理

相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数,这也就是AOP的一种实现方式,但是只能针对接口方法进行处理,具体原因后面会顺势提到。

动态代理

通过其UML类图可以发现其比标准的类图要复杂,主要引入了Proxy类,这是Java自带的,我们通过调用Proxy类的newProxyInstance方法来获取一个代理类实例,此外还引入了InvocationHandler接口及其实现类,称之为调节处理器类。
初看起来有点懵逼,让我们理一下,会发现动态代理包含了两层的静态代理关系:

  • 第一层是ProxyInvocationHandler,在这层关系中,Proxy是代理类,而InvocationHandler是真正的实现类,即目标对象类。
  • 第二层是InvocationHandlerISubject,这里的话,InvocationHandler中持有ISubject的实现类对象,所以InvocationHandler是代理类,而ISubject是最终的实现类。
    会发现,饶了一圈,最后的目标对象还是ISubject,和静态代理的最主要区别在于SubjectProxy这个类是在程序运行中动态生成的。

续上面的武林盟主栗子,我们只需要增加一个InvocationHandler类,这个类会接收到所有对接口方法的调用,在其中我们可以对接口方法进行拦截和实现自己的功能。

/**
 * Author:xishuang
 * Date:2018.02.07
 * Des:具体的代理逻辑实现
 */
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 正在调用得方法
     * @param args   方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("playTaijiquan")) {
            System.out.println("给自己加戏-动态代理");
            mWuDangMan.playTaijiquan();
        } else if (method.getName().equals("playYiGinChing")) {
            System.out.println("给自己加戏-动态代理");
            mShaolinMan.playYiGinChing();
        }
        return proxy;
    }
}

最关键在于invoke()方法,它会拦截所有实现了接口的方法,根据不同的方法名来达到我们的目的,在InvocationHandler类完成之后,就可以进行调用,调用的代码和运行结果如下:

/**
     * 动态代理
     */
    private static void daynamicProxy() {
        // 创建一个与代理对象相关联的InvocationHandler
        InvocationHandler handler = new MengZhuInvocationHandler(new WuDangMan(), new ShaoLinMan());
        // 创建一个代理对象studentProxy来代理student,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        IWuDang studentProxy = (IWuDang) Proxy.newProxyInstance(getClassLoader(), new Class[]{IWuDang.class, IShaolin.class}, handler);
        studentProxy.playTaijiquan();
        ((IShaolin) studentProxy).playYiGinChing();
    }


对比静态代理的使用过程,动态代理的使用显得要复杂一些,其中最关键的是Proxy.newProxyInstance()方法,用于返回动态生成的代理对象,需要传入三个参数,类加载器ClassLoader,需要实现的接口数组,以及InvocationHandler拦截对象,这些参数都是生成动态代理对象所需要的。

4、动态代理调用分析

单单从代码调用关系上来看是比较难看出UML类图中的那种关系的,最主要原因是没有直观的看到动态生成的SubjectProxy类,所以我们就顺着Proxy.newProxyInstance()这条线来看一下背后的调用轨迹。

/**
 * 返回指定接口的动态生成类的实例
 */
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
                                          InvocationHandler invocationHandler)
            throws IllegalArgumentException {

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

首先通过类加载器和接口数组来生成类文件,然后通过反射的方式来实例化对象,并把InvocationHandler在构造方法中传入以便后续调用,继续看getProxyClass()方法。

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

        for (Class<?> c : interfaces) {
            if (!c.isInterface()) {
                // 传入的要实现类必须是接口类,否则会抛出异常
                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;
    }

这里只抽取关键代码,可以发现源码中限制了我们传入的需要实现的类必须是接口类,非接口类会抛出异常,最后通过本地native方法generateProxy()生成并返回类对象。
这一个过程是比较明了的,把动态生成的类文件打印出来就知道代理类中的具体调用了,这里我们使用Java中提供的ProxyGenerator类的静态方法generateProxyClass(),来打印出动态生成的代理类class字节码。

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中调用不了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 {
            // 通过反射机制来调用各个方法,动态代理会对运行期间的性能造成一定影响
            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并实现我们定义的接口,其中每个方法调用时没有做具体的事情,而是直接调用super.h.invoke()super.h就是InvocationHandler对象,所以SubjectProxy把任务抛给了InvocationHandler进行处理,而InvocationHandler就是通过我们可以控制的,绕来绕去把控制权又交到了我们的手中。
前面提到过动态代理只能代理接口方法,原因也在这里,动态生成的类直接实现我们定义的接口,从而实现其中的接口方法,在方法调用时再把功能交还到我们的手中。动态代理可以统一对接口的所有实现类进行操作,而不用修改每个实现类,通过Proxy->InvocationHandler->ISubject的顺序把控制权一步步的交接,最后真正的实现类和静态代理是一样的,只是因为要动态生成代理类从而导致中间过程饶了一点。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容