Android 代理模式(动态代理)及其原理分析

代理模式(动态代理)及其原理分析

概念

有一种设计模式叫做代理模式,其中也用到了动态代理。动态代理就是为某一个对象提供一个代理对象,已达到对这个对象的代理访问,最典型的例子就是“律师”和“普通民众”的关系。

代理模式

我们直接上一个简单的小例子来阐述这个模式。
事件:小明要上诉,他找了一个律师帮他。

这里是上诉的步骤

public interface ILawsuit {
    /**
     * 提交申请
     */
    void submit();
}

接下来是小明要开始上诉,上诉的实现类

public class XiaoMin implements ILawsuit {
    @Override
    public void submit() {
        System.out.println("我要上诉!");
    }
}

接下来是律师实现类,他也要实现上诉的步骤

public class Lawyer implements ILawsuit {
    //持有小明的引用
    private ILawsuit mLawsuit;

    public Lawyer(ILawsuit lawsuit) {
        mLawsuit = lawsuit;
    }

    @Override
    public void submit() {
        //除了小明自己的上诉,我还可以加上一些属于律师的逻辑
        mLawsuit.submit();
    }
}

一个简单的代理模式就实现了,这里可以感受出来代理模式的一些好处了。方便后期逻辑拓展,以及简化了客户端(使用者)使用这个对象(小明)的逻辑。

但是在一个被代理对象有大量逻辑或者多个对象都需要代理的时候,我们不可能再去写一个大量逻辑的代理类了,不然也太麻烦了,因此Java就给我们了一个快速拿到代理对象的方法,此时就没有“律师”类了,请看下面的一个实现代理逻辑的类:

public class DynamicProxy implements InvocationHandler {
    //代理的对象的引用
    private Object object;

    public DynamicProxy(Object o) {
        object = o;
    }

    /**
     * 实现方法的调用
     * @param proxy 真实对象(小明)
     * @param method 原方法的对象
     * @param args 原方法的参数
     * @return 原方法的返回结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //实现方法的调用
        //每当代理对象调用一个方法的时候,最后都触发的是这一个方法
        Object result = method.invoke(object, args);
        return result;
    }
}

在这个(动态代理)的类中,我们实现了一个接口,这个接口就是 InvocationHandler ,里面只有一个 invoke() 方法,这个方法就是被代理对象真正实现的地方,例如:小明调用上诉方法,真正执行的是method.invoke(object, args) ,这一句即是实现method方法。这个类的实现就让我们可以同时对多个代理逻辑相同的对象进行代理,而且这个类只将所有方法都集中在了一个invoke() 方法中,省去了大量重复代码,例如可以在这里添加打印日志的功能等。

接下来来看小明再次怎么被代理的:

//对真实对象的代理的逻辑实现 
DynamicProxy proxy = new DynamicProxy(xiaoMin);

//定义了由哪个ClassLoader对象来对生成的代理对象进行加载
ClassLoader loader = xiaoMin.getClass().getClassLoader();

//这才是最终的代理对象
ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]    
    {ILawsuit.class},proxy);
lawyer.submit();        

这里我们关注一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);方法,先说三个参数:

  • loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。
  • interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。
  • h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

细心的你可能发现了不对的地方,我们拿到的lawyer对象如果只是一个XiaoMin对象的话,那么在调用submit()方法的时候不可能触发invoke()方法,而是直接实现。那么证实 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h); 返回的不是XiaoMin对象,而是一个JVM根据ILawsuit接口、 xiaoMin对象、proxy动态代理对象来生成的一个对象。

然后我去跟着这些方法去源码里面看一下,先看newProxyInstance()方法,这个方法负责的就是生成代理类的对象:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    throws IllegalArgumentException
{
    //要求代理逻辑对象非空
    Objects.requireNonNull(h);

    //根据传入的类构造器和接口对象,查找或者生成指定的代理类
    Class<?> cl = getProxyClass0(loader, intfs);

    //调用构造函数
    try {
        //拿到含有InvocationHandler类型参数的构造方法
        //目的是最后的类对象是跟我们的代理逻辑的对象相关联
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        
        //生成的类的修饰符不是公开的话
        if (!Modifier.isPublic(cl.getModifiers())) {
            //这里其实是拿到报异常的
            cons.setAccessible(true);
        }
        //通过反射拿到生成类的实例
        return cons.newInstance(new Object[]{h});
    }
}

其中的constructorParams是来自private static final Class<?>[] constructorParams ={ InvocationHandler.class }; 这一句话,也就是实现我们的代理逻辑的那个接口的一个数组对象,其实这里我们根据最后的返回值return cons.newInstance(new Object[]{h})就应该了解到编译器为我们生成的类中的构造函数有一个是传入我们的代理逻辑对象(InvocationHandler对象)的构造函数,因此实现了代理逻辑和生成类的关联,那么此时我们的生成类就包含了我们的被代理类(小明)的逻辑和我们(对小明)的代理逻辑。

接下来我们再点进去这个方法中的一个重要操作getProxyClass0(loader, intfs),经过两步操作,我们最终是来到了WeakCache(弱缓存)的public V get(K key, P parameter){}方法:

/**
 *V:        最后的代理类泛型对象
 *key:       classLoader(小明类对应的类加载器)
 *parameter: 接口类(小明的申诉步骤接口)的数组对象
 */
public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);

    expungeStaleEntries();

    //根据类加载器拿到一个从缓存读取数据的key
    Object cacheKey = CacheKey.valueOf(key, refQueue);

    // 从对应的类加载器中拿到的并行集合,存放的值是Supplier(接口“提供者”)
    // map一开始是空的,map也是从这个方法里进行填充的
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        // 如果没有这个并行集合,则new 一个并行集合并放入这个map。
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }

    // 创建子键,并从valueMap 中检索可能存在的对应子键的Supplier(提供者)
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    //工厂(Supplier的实现类)
    Factory factory = null;

    while (true) {
        // 提供者不为空的话直接拿到我们需要的代理类的Class对象
        // 返回一个值,结束该方法
        if (supplier != null) {
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        
        // 实例出一个工厂对象(判断代理类就是在Factory中生成的)
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }

        //当提供者为空的时候
        if (supplier == null) {
            //如果并行集合中没得对应的提供者,则放入刚创建的factory(提供者)
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                supplier = factory;
            }
        } else {
            // 不为空的话替换并行集合中对应的元素
            if (valuesMap.replace(subKey, supplier, factory)) {
                // 直接拿到提供者
                supplier = factory;
            } else {
                // 根据子键拿出对应的提供者
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

看到这个方法是不是脑袋都大了,没错,其实我的脑袋也大了,很多东西是懵懵懂懂的。但是我们依旧可以梳理一遍这个方法的大体逻辑

我们为了拿到我们想要的代理类,我们是从弱缓存中去找,先根据我们的类加载器从一个map中去找,找的目标是也是一个集合,这是一个放了可以拿到我们想要的代理类的集合————一个并行集合。但是一般的话这个map都是空的,因为它是直接new 出来的(这个类的顶部有),里面的元素也是在这个方法中被放进去的,那么也就是说我们找到的并行集合也可能是个空的,对吧?所以的话我们也是直接new 一个并行集合进去的。那我们new 出来的并行集合也是空的啊,没关系,因为我们接下来往里面添加东西就对了。重点来了,我们的Factory类,它本身也是一个Supplier(接口) 的实现类,在实例化的过程,我们根据Factory构造器传入值,生成对应的代理类,并且factory对象自身会被放入缓存集合,以便下次直接从它拿到目标代理类。也就是说我们可以通过Factory直接生产出我们想要的代理类的,而这个方法中的其它逻辑则是为了让我们能够实现保存,在下一次便可以直接调用了,以达到节省资源的目的。

最后的最后,我们再去Factory中去看看吧,验证我们的猜想是否正确,我们去找我们想要的代码,

在这里我们保留了关键的代码:

private final class Factory implements Supplier<V> {

    Factory(K key, P parameter, Object subKey,
            ConcurrentMap<Object, Supplier<V>> valuesMap) {
        this.key = key;
        this.parameter = parameter;
        this.subKey = subKey;
        this.valuesMap = valuesMap;
    }

    @Override
    public synchronized V get() { 
        // re-check
        Supplier<V> supplier = valuesMap.get(subKey);
        if (supplier != this) {
            // 简单理解,防止意外发生,返回Null让其一直循环
            return null;
        }
      
        // 激动的时候,这就是我们的目标代理类
        V value = null;
        try {
            // 再深入就又要扯好远,这里又是另外的工厂类了,反正就是生产value!!!
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } 
        //哈哈哈,拿到了!!!
        return value;
    }
}

这便是我们生产我们的目标代理类的全部过程啦,我们应该注意的是最后跟随我们到底的一直有我们的“申诉步骤”的接口,以确保我们拿到的是这个接口的对象,然后我们就可以执行里面的方法了。

我还在这里制作了一张简单的流程图

proxy.png

由于笔者水平有限,我在找了大篇的博客后也没能找出JVM为我们生成的代理类,所以我们也只能估摸着这个代理类的作用了,就是在调用被代理对象方法的时候,记录该目标方法,并直到调用invoke()方法中去,在invoke()方法中我们自行实现目标方法的调用。

补充 —— AOP

翻译过来叫做“面向切面的编程”,简单说,就是找到一个模块的切入点,对它进行切面操作。

比如打Log这一操作吧,我们好多个对象、好多个方法都需要打上Log,那我们在每一个方法里面都打上自己的Log类的Log吧。首先,打这么多的Log不累吗?而且这么多Log肯定少不了大量重复代码。所以我们面对打Log这一功能进行处理,就用动态代理的形式来做吧,我给每一个对象一个代理,让它执行方法的时候都在这个代理对象中去执行,那么是不是就是可以在代理对象中去实现打Log 的功能了,这样就将Log和方法分开来了,简化了代码,增强了后期拓展性,而且代码变整洁了,有木有? 。

你可能会问了,我给每一个对象都搞一个代理是不是更麻烦哟?那么,我的第二个目的就是要简化这一实现过程了,是不是?例如Retrofit 第三方库就有采用一种方法————利用Apt(编译时扫描和处理注解),使用的时候在需要代理的对象上面加上简单的注解就实现了这一功能,在这里就不详细解释如何实现的了。

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

推荐阅读更多精彩内容

  • Java代理和动态代理机制分析和应用 概述 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个...
    丸_子阅读 3,004评论 6 57
  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,734评论 0 14
  • 目录:1.代理模式定义&实现2.装饰模式定义&实现3.静态代理4.动态代理:JDK动态代理、Cglib动态代理5....
    lbcBoy阅读 1,583评论 2 3
  • 18:44 总算 找到了绿 很干净稳重的颜色 像一个面容沉静的 小兽似的少年 洋溢着青春活力 往上拔节生长...
    陆沉__阅读 134评论 0 0
  • 2018年8月7日下午中国抗衰老促进会副秘书长肖连敏,原卫生部副部长孙隆椿,原大连市政协主席、慈善总会会长...
    水月禅心_0c6d阅读 1,411评论 0 0