群里讨论的Retrofit动态代理是什么

上一篇既然是静态代理,那么必然会有一个概念是动态代理,静态代理也是为了给这一篇动态代理做的铺垫;常年混迹Android开发群的我,总会遇到有朋友提问面试被问到Retrofit请求框架里面怎么实现解耦的,在我正准备回答“不就是创建一个Retrofit对象,然后定义一个接口,在请求方法上写上对应的注解就搞定了”的时候,大佬回答一个“动态代理”瞬间把我弄懵了,???啥玩意,动态代理是啥,不知道有多少人跟我一样,从0开始写了好几年Android代码,连动态代理是啥都不知道...

动态代理

虽然是动态,但是最终还是一个代理,所以最基本的元素跟上一篇静态代理是一致的,需要一个定义行为的接口,一个代理类和一个被代理类,使用过Retrofit的朋友都知道,基本的使用方法里面,我们需要定义一个对于网络请求方法的接口,并使用注解(注解后面找时间写)将这个请求所需要的参数,地址,返回类型进行定义

上层接口
/**
 * 代理模式中的行为规范接口
 */
public interface IHttpApis {
    @GET("api://test")
    Object test(@Query("param") String param);
}
被代理对象

Retrofit框架本质上是对OkHttp框架的进一步封装,跟网络请求相关的(寻址,建立连接,发送报文等...)都是由OkHttp去进行的,Retrofit我的理解是对我们在使用OkHttp是需要自己去实例化Call对象这部分分发逻辑进行了一个封装,通过注解和动态代理,更加的简洁方便,所以被代理对象,是其内部的OkHttp部分

    //Retrofit通过Builder建造者模式进行初始化,在使用的时候传入了OkHttpClient对象

    /**
     * The HTTP client used for requests.
     * <p>
     * This is a convenience method for calling {@link #callFactory}.
     */
    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    //使用Retrofit时构建Retrofit对象
    Retrofit.Builder()
                .baseUrl("")
                 //这是传入的被代理对象
                .client(new OkHttpClient())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

代理对象

按照静态代理的逻辑,我们还需要一个代理对象,用于在model层进行方法调用,那么我们看看动态代理是怎么玩的

        //实例化代理对象
        IHttpApis apis = (IHttpApis) Proxy.newProxyInstance(IHttpApis.class.getClassLoader(),
                new Class<?>[]{IHttpApis.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return null;
                    }
                });

       //使用代理对象
        apis.test("param");

???这是咋操作的,我第一次看到动态代理整个人都懵了,还可以跳过new关键字实例化对象

按照多年的Android开发经验,实例化对象都是通过new关键字进行的,动态代理的关键点就在于不需要手动写代理类的.java文件,这部分需要插播一点jvm的类加载机制

通过手写.java类的方式

  1. java类通过javac程序编译成.class字节码对象
  2. .class对象通过类加载器加载进入jvm的方法区
  3. 从方法区中拿到对象的指向,在堆中实例化对象

没去学习动态代理之前,一直使用这样的写代码方式,从jvm看,javac程序将.java文件编译成.class文件,然后通过类加载器加载到内存中,然后在程序运行的时候,通过new关键字就可以在方法区中找到对应的类结构,就可以用来实例化对应的代理类对象,但是这这需要有一个我们写的.java文件去进行编译,而动态代理跳过了这一步,在不需要我们手动写.java代理类文件的情况下,也可以让我们获得一个实例化的代理类对象,并可以通过这个对象去执行相应的方法,那看看对应的源码,看看是怎么绕过前面的编译过程,在运行时实例化对象的,具体的使用就是通过Proxy.newProxyInstance()方法开始的

  /**源码比较长,省略掉不必要的代码逻辑*/
  // ->$Proxy.newProxyInstance()
  //步骤1
  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        //...省略掉不必要的代码合注释

        //1.获得class对象,cl对象就是实际的代理对象 proxyPkg$Proxy0
        final Class<?>[] intfs = interfaces.clone();
        //步骤2
        Class<?> cl = getProxyClass0(loader, intfs);

        //获取对应class对象的构造函数
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //构造函数传参
        final InvocationHandler ih = h;

        //根据构造函数,反射获取对象,将传入的InvocationHandler参数传如构造函数里面了
        return cons.newInstance(new Object[]{h});
    }


    /****************跟进分割*******************/
   //->$Proxy. getProxyClass0
   //class对象通过proxyClassCache中get()获取,这里cache对象是对该接口代理做了一个缓存
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //步骤3
        return proxyClassCache.get(loader, interfaces);
    }

    /****************跟进分割*******************/
    //proxyClassCache.get是一个WeakCache的实例
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
  
    //WeakCache的构造方法,和get()方法
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

    public V get(K key, P parameter) {
        //...
        //从缓存中查找
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                    valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        //缓存中没有的时候,通过subKeyFactory去获取缓存的key, subKeyFactory是构造方法传入的
        //通过其获取一个key,这个key使用的是传入接口class对象的hashCode
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
        while (true) {
            //map中第一次没缓存,走后门的循环中初始化supplier,就是一个
            if (supplier != null) {
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            //第一次循环factory为null,需要初始化一个Factory,在下面赋值给supplier
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    //下次循环直接使用
                    supplier = factory;
                }
            } else {
             ...
            }
        }
    }

  /****************跟进分割*******************/
 //->$WeakCache->$Factory.get()
private final class Factory implements Supplier<V> {
    @Override
    public synchronized V get() { 
        //通过valueFactory.apply()去初始化对象,就是走ProxyClassFactory().apply()方法
        value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        return value;
    }
}

  /****************跟进分割*******************/
//->$Proxy->$ProxyClassFactory.apply()
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    // 代理类实例化对象命名拼接规则
    private static final String proxyClassNamePrefix = "$Proxy";
    //用于递增的序号
    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) {
            ...
            //如果不是接口,抛出异常,代理模式的三要素之一的接口
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
            }

        }
        ...
        //省掉对接口class的解析

        //代理类名字拼接规则 类名proxyPkg + $Proxy + 自增序号0开始
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

           //最后传入了需要实例化的代理类类名,需要被代理的接口名,还有类加载器等参数
            return generateProxy(proxyName, interfaces, loader, methodsArray,
                    exceptionsArray);
        }
    }
}

//Android实例化类的方法是调用了native方法
@FastNative
private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
        ClassLoader loader, Method[] methods,
        Class<?>[][] exceptions);

追到最后,Android端动态代理最后执行了一个native方法,具体的把class对象字节码的生成和加载到内存的操作放在了native函数中,根据jvm的规范,就可以使用类加载器进行反射获取构造函数,然后就可以就可以实例化代理对象了
native方法暂时就就不跟了,动态生成class字节码可以在Java工程中的动态代理源码看一下,在生成了代理对象的命名后,执行了下面的逻辑

         //生成代理类名称
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        
        //ProxyGenerator.generateProxyClass生成代理类的class字节码
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            //将class加载到jvm内存中
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
  
        }

原来可以通过ProxyGenerator对象可以在运行时生成一个class字节码文件,然后动态的加载进入内存,通过代码查看一下生成的class对象

        byte[] bytes = ProxyGenerator.generateProxyClass("ProxyImpl", new Class[]{IHttpApis.class});
        File file = new File(".../ProxyImpl.class");
        FileOutputStream outputStream = new FileOutputStream(file);
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();

//对应的接口
public interface IHttpApis {
    public void get(String param);
}


//下面就是生成的ProxyImpl.class文件
public final class ProxyImpl extends Proxy implements IHttpApis {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public ProxyImpl(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 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 get(String var1) throws  {
        try {
            //实际执行方法调用的位置
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            //接口中声明的代理方法
            m3 = Class.forName("com.maniu.proxy.IHttpApis").getMethod("get", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到,生成的代理类,的构造函数会有一个InvocationHandler参数传入,然后会将实现的对应接口内声明的方法,定义为Method成员对象,其中也会含有其父类的方法,然后通过方法名和参数类型进行匹配,最终在调用该方法的时候,实际上是调用的h(InvocationHandler)的invoke方法,并将参数传递出去

以上就是动态代理模式下,通过工具类在代码运行时动态的生成class文件并加载到jvm中的流程,然后调用方法时,最终会执行InvocationHandler的invoke方法,并将参数传递出去

Retrofit中的动态代理

对动态代理有了了解,那么回看Retrofit中怎么使用的动态代理

        Retrofit retrofit =
                new Retrofit.Builder()
                        .baseUrl("")
                        .client(new OkHttpClient())
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();

        //本文开始的时候Retrofit的使用方法,会调用对用的create()方法,传入定义的接口
        IHttpApis apis = retrofit.create(IHttpApis.class);
        apis.test("params");

Retrofit中create()方法的

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          //方法会回调到这个地方执行
          @Override public Object invoke(Object proxy, Method method, Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

就是使用了动态代理的调用方式,返回了一个动态生成的代理对象$Proxy0,然后传入了InvocationHandler实例,那么在方法调用的时候,最终就会回调执行invoke()方法,然后在里面进行注解的解析,地址的拼接和OkHttp的调用(这部分后面找机会再分析)

小结

动态代理也是代理模式,只是由我们在编码期需要去写的代理类.java文件在运行时直接生成了class字节码文件,并加载到内存中,然后利用反射的原理获取构造方法对其进行实例化,返回实例好的代理对象,然后通过这个代理对象进行方法的调用,最终执行到传入的InvocationHandler的invoke方法中
通过代理模式,我们可以哎代理方法invoke()执行前后插入逻辑,多方法进行增强,例如在Retrofit中,就可以将生成OkHttp请求需要的Call对象和分发部分进行抽离,在使用中只需要声明一个接口方法,再利用注解传参,将更多的细节进行解耦,使用更加方便
虽然在项目中我还没有实际使用到(毕竟是个菜狗),但是整个动态代理的流程还是梳理清楚了,通过这样的梳理,以后在群里有人再问Retrofit的思想的时候,就可以装逼了,欢迎路过的大佬进行指点,可以给我分享一些常见的使用场景,而不是用于面试答疑

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

推荐阅读更多精彩内容