retrofit源码中用到的设计模式(一)代理模式

一.上面是代理模式?

所谓代理,就是一个人或者机构代表另一个人或者机构采取行动。
反应到程序上简单地理解就是A类有method1(),B类呢,持有A的引用,也有个method1()方法,B中的methd1的内容其实就是调用了A的method1,这样调用者调用B的method1其实本质是调用了A的method1,只是不是直接调用,B就像是A的一个代理一样,你找A做什么不要直接找它,找B来做。

代理模式常用的情况分为:静态代理和动态代理

二.静态代理

静态代理是指预先确定了代理与被代理者的关系,那映射到编程领域的话,就是指代理类与被代理类的依赖关系在编译期间就确定了。

用网上找的一个例子来讲吧
王二狗打官司(被代理的对象),请了一个律师替他上诉(代理对象)

首先定义一个代表诉讼的接口

public interface ILawSuit {
    void submit(String proof);//提起诉讼
    void defend();//法庭辩护
}

王二狗诉讼类型,实现ILawSuit接口

public class SecondDogWang implements ILawSuit {

    public void submit(String proof) {
        System.out.println(String.format("老板欠薪跑路,证据如下:%s", proof));
    }

    public void defend() {
        System.out.println(String.format("铁证如山,%s还钱", "马旭"));
    }
}

王二狗是不会上诉,于是请了个律师帮他搞

public class ProxyLawyer implements ILawSuit {

    ILawSuit plaintiff;//持有要代理的那个对象
    public ProxyLawyer(ILawSuit plaintiff) {
        this.plaintiff=plaintiff;
    }


    public void submit(String proof) {
        plaintiff.submit(proof);
    }


    public void defend() {
        plaintiff.defend();
    }
}

ok,律师请好了,组个队吧
如下代码,在编译期间王二狗就确定了要使用这个律师,而是不是上了法庭,一排律师中找一个。静态代理

public class ProxyFactory {

    public static ILawSuit getProxy(){
        return new ProxyLawyer(new SecondDogWang());
    }
}

开打了

public class StaticMain {

    public static void main(String[] args) {
        ProxyFactory.getProxy().submit("工资流水在此");
        ProxyFactory.getProxy().defend();
    }
}

三.动态代理

动态代理本质上仍然是代理,情况与上面介绍的完全一样,只是代理与被代理人的关系是动态确定的,例如王二狗的同事牛翠花开庭前没有确定她的代理律师,而是在开庭当天当庭选择了一个律师,映射到编程领域为这个关系是在运行时确定的。

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。

那么用代码看看牛翠花是怎么找律师的,如何在开庭当天找律师?

1.构建一个牛翠花诉讼类

public class CuiHuaNiu implements ILawSuit {

    public void submit(String proof) {
        System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
    }

    public void defend() {
        System.out.println(String.format("铁证如山,%s还牛翠花血汗钱","马旭"));
    }
}

2.构建一个动态代理类
动态的律师出现了,只要是有诉讼需求的人(只要是实现ILawSuit 接口的类)我都可以接

public class DynProxyLawyer implements InvocationHandler {

    private Object target;//被代理的对象

    public DynProxyLawyer(Object obj) {
        this.target = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("案件进展:" + method.getName());
        Object result = method.invoke(target, args);
        // 返回的是代理对象的执行函数的结果
        return result;
    }
}

public class DyncProxyFactory {

    public static Object getDynProxy(Object target) {

        InvocationHandler handler = new DynProxyLawyer(target);
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);

    }
}

3.开打

public class DyncMain {

    public static void main(String[] args) {

        ILawSuit proxy= (ILawSuit)DyncProxyFactory.getDynProxy(new CuiHuaNiu());

        proxy.submit("工资流水在此");
        proxy.defend();

    }

}

解释一下:

首先Jdk的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范。然后通过Proxy类产生的代理对象调用被代理对象的操作,而这个操作又被分发给InvocationHandler接口的 invoke方法具体执行

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
}

此方法的参数含义如下
proxy:代表动态代理对象
method:代表正在执行的方法
args:代表当前执行方法传入的实参
返回值:表示当前执行方法的返回值

例如上面牛翠花案例中,我们使用Proxy类的newProxyInstance()方法生成的代理对象proxy去调用了proxy.submit("工资流水在此");操作,那么系统就会将此方法分发给invoke().其中proxy对象的类是系统帮我们动态生产的,其实现了我们的业务接口ILawSuit。

三.Retrofit中的代理模式使用

Retrofit 中使用了很多的设计模式,代理模式,工厂模式等等,我们一个一个来,先看看代理模式用在哪里

使用Retrofit 我们通常会在一个接口中定义网络接口,如下

public interface ServiceApi {
   @GET("LoginServlet")// 登录接口 GET(相对路径)
    Call<UserLoginResult> userLogin(@Query("userName") String userName, 
    @Query("password") String userPwd);
}

但是我们使用的时候,肯定不是直接ServiceApi调用他的userLogin,通常,我们会创建一个RetrofitClient

public class RetrofitClient {
    private final static ServiceApi mServiceApi;
static {
        OkHttpClient okHttpClient = new OkHttpClient
                .Builder().addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                    @Override
                    public void log(String message) {
                        Log.e("TAG",message);
                    }
                }).setLevel(HttpLoggingInterceptor.Level.BODY))
                .build();
    
        Retrofit retrofit = new Retrofit.Builder()
                // 访问后台接口的主路径
                .baseUrl("http://192.168.8.169:8080/OkHttpServer/")
                // 添加 OkHttpClient,不添加默认就是 光杆 OkHttpClient
                .client(okHttpClient)
                .build();

        // 创建一个 实例对象
        mServiceApi = retrofit.create(ServiceApi.class);
    }

    public static ServiceApi getServiceApi() {
        return mServiceApi;
    }

}

注意看

.....
mServiceApi = retrofit.create(ServiceApi.class);
........

这句

那么源码中retrofit.create做了什么呢?我们源码简化下


public class Retrofit {
    final String baseUrl;
    final okhttp3.Call.Factory callFactory;
    private Map<Method,ServiceMethod> serviceMethodMapCache = new ConcurrentHashMap<>();

    public Retrofit(Builder builder) {
        this.baseUrl = builder.baseUrl;
        this.callFactory = builder.callFactory;
    }

    public <T> T create(Class<T> service) {
        // 检验,是不是一个接口 ,不能让他继承子接口

        // 重点
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 每执行一个方法都会来这里
                // 判断是不是 Object 的方法 ?
                if(method.getDeclaringClass() == Object.class){
                    return method.invoke(this,args);
                }

                // 解析参数注解
                ServiceMethod serviceMethod = loadServiceMethod(method);
                // 2. 封装 OkHttpCall
                OkHttpCall okHttpCall = new OkHttpCall(serviceMethod,args);

                // 返回代理对象的执行结果,此处是OkHttpCall
                return okHttpCall;
            }
        });
    }

怎么样?怎么样?看到了什么,是不是使用了动态代理,当retrofit.create以后,创建了Api也就是示例中ServiceApi代理实例,调用ServiceAp的任何方法,都会走进
public Object invoke(Object proxy, Method method, Object[] args) throw
这个代理方法中,这个方法的三个参数我们文章开头讲过了,拿到Method,我们就可以通过反射的知识拿到注解的方法值,和注解的参数值

其实后面的 ServiceMethod serviceMethod = loadServiceMethod(method);就是把method传进去解析注解了
具体怎么做的呢?请看下文

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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