流行框架源码分析(14)-Proxy代理设计模式

主目录见:Android高级进阶知识(这是总目录索引)
 今天要讲这个代理设计模式,真的是印象深刻,在好多年前在学校接触java的时候就对这个模式了解过,当初还想着Spring里面的AOP机制是不是就是用代理实现的呀。到现在插件化的hook大量用到代理设计模式。包括等会要讲的retrofit也是用到了代理设计模式。而且Framework的AIDL里面也是用到了代理模式。这些个例子足以证明了你学这个设计模式的理由,不需要我去调动大家,我们先来看看代理模式的定义:

代理模式:为其他对象提供一种代理以控制这个对象的访问。

这个定义简单明显呀,就是说不直接访问对象,而是建一个代理或者说是中间人去访问他,而且这个代理可以选择多带礼物,说祝福等等增强的行为和服务。接着我们看下UML类图:


代理设计模式

角色介绍:

  • Subject:抽象主题,声明代理主题和真实主题的接口;
  • Proxy:代理主题,因为Proxy引用了RealSubject,并且实现了跟RealSubject一样的接口,所以ProxySubject可以操作RealSubject,还可以提供一些附加操作,例如before & after;
  • RealSubject:真实主题,真实的对象,需要被代理主题引用。

一.目标

今天讲这个设计模式也是为了后面插件化和热更新做个准备,同时我们还能在必要时候派上用场,特别是retrofit的源码和使用,这个待会就会讲了。所以今天的目标是:
1.学会几种代理设计模式怎么使用;
2.学会在不同的场景中应用代理模式。

二.模式讲解

今天我们也来假设一个场景,比如你现在要去租房,但是呢,又不是自己去一个地方一个地方找,所以你就拜托了中介帮你找,告诉了中介你的要求,然后中介就将你的要求发布在网上,联系了房东,最后你去看了房子。首先我们看下抽象主题:

public abstract class Subject {
    public abstract String houseHunting();
}

我们看到抽象主题里面有个找房子的动作。这个是给后面代理主题和真实主题使用的,我们现在来看下真实主题想干嘛:

public class Hunter extends Subject{
    @Override
    public String houseHunting() {
        String info = "我要找一间房子,房子的价格在2000-3000之间三室一厅";
        return info;
    }
}

我们看到我们的的真实主题是想要找一间房子,然后说了自己的价格。接着我们代理就出现了:

public class Proxy extends Subject{
    private Subject subject;
    
    public void setHunter(Subject subject){
        this.subject = subject;
    }
    
    @Override
    public String houseHunting() {
        String proxyFare = "我要额外收中介费500块";
        String infoFromHunter = subject.houseHunting();
        
        return infoFromHunter +", "+ proxyFare;
    }
}

我们看到这里的代理也就是这个中介除了帮你找房子,还要额外再收中介费用。所以代理可以拦截原来的方法然后再增加额外的功能。我们看下这个是怎么使用:

        Subject hunter = new Hunter();
        Proxy houseAgency = new Proxy();
        houseAgency.setHunter(hunter);
        
        String info = houseAgency.houseHunting();
        System.out.println(info);

除了这个静态代理,我们java还提供了动态代理机制,我们现在来看看用动态代理是怎么实现代理的。

1.动态代理

同静态代理不同的是,动态代理是通过反射在运行时候生成代理对象的,要实现动态代理,首先我们要注意这里的动态代理有要求就是必须被代理的对象必须是实现一个接口。而且代理对象必须实现InvocationHandler接口。我们先来看看代理对象:

public class DynamicProxy implements  InvocationHandler{
    
    private Subject subject;
    
    public DynamicProxy(Subject subject) {
        this.subject = subject;
    }

    @Override
    public String invoke(Object arg0, Method method, Object[] arg2)
            throws Throwable {
        String proxyFare = "我要额外收中介费500块";
        String result = (String) method.invoke(subject,arg2);
         
        return result +", "+ proxyFare;
    }

}

我们看到这里实现了InvocationHandler,然后必须实现invoke方法。invoke方法这里包括方法和方法参数。然后我们可以往在原本方法增加附加功能了,比如这里的中介费。调用变成了这样:

Subject hunter = new Hunter();
        Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class<?>[]{Subject.class}, new DynamicProxy(hunter));
        System.out.println(subject.houseHunting());

当然这里的Subject必须是一个接口,不然程序会出错。当然了前面的都是要实现一个接口的,但是有没有不需要被代理对象实现一个接口呢?答案是有的大家可以使用这个库Cglib代理。具体不会介绍这个库,大家有兴趣可以看看。

2.retrofit中的代理设计模式

如果想要了解retrofit的具体源码可以查看[Retrofit2源码解析],我们知道我们使用retrofit的时候如下:

 PhoneService service=retrofit.create(PhoneService.class);
 Call<PhoneResult>call=service.getResult(etPhone.getText().toString());

我们会获取到要调用的服务的对象PhoneService,然后调用里面的方法getResult,因为我们说到了动态代理必须要实现一个接口,这里的PhoneService 就是一个接口,然后调用create方法具体源码如下:

 @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  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, @Nullable 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);
          }
        });
  }

我们看到这里应用了动态代理,首先service就是接口的class对象。然后最后new InvocationHandler就是实现了代理对象。具体做了什么我们的源码分析有说明这里就不重复说明。

3.retrofit使用过程中使用到的代理设计模式

我们知道,我们在访问网络的时候,会出现当前网络环境较差,当前没有网络,服务器出错等等情况。所以在create返回访问接口之后我们可以再加一个代理来处理这些情况。我们先来看看我们的代理对象:

public class ResponseErrorProxy implements InvocationHandler {

    public static final String TAG = ResponseErrorProxy.class.getSimpleName();

    private Object mProxyObject;

    public ResponseErrorProxy(Object proxyObject) {
        mProxyObject = proxyObject;
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) {
        return Observable.just(null)
                .flatMap(new Func1<Object, Observable<?>>() {
                    @Override
                    public Observable<?> call(Object o) {
                        try {
                            return (Observable<?>) method.invoke(mProxyObject, args);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                })
                .retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                    @Override
                    public Observable<?> call(Observable<? extends Throwable> observable) {
                        return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                            @Override
                            public Observable<?> call(Throwable throwable) {
                                ResponseError error = null;
                                if (throwable instanceof ConnectTimeoutException
                                        || throwable instanceof SocketTimeoutException
                                        || throwable instanceof UnknownHostException
                                        || throwable instanceof ConnectException) {
                                    error = new ResponseError(HTTP_NETWORK_ERROR,
                                            StringFetcher.getString(R.string.toast_error_network));
                                } else if (throwable instanceof HttpException) {
                                    HttpException exception = (HttpException) throwable;
                                    try {
                                        error = GsonHelper.builderGson().fromJson(
                                                exception.response().errorBody().string(), ResponseError.class);
                                    } catch (Exception e) {
                                        if (e instanceof JsonParseException) {
                                            error = new ResponseError(HTTP_SERVER_ERROR,
                                                    StringFetcher.getString(R.string.toast_error_server));
                                        } else {
                                            error = new ResponseError(HTTP_UNKNOWN_ERROR,
                                                    StringFetcher.getString(R.string.toast_error_unknown));
                                        }
                                    }
                                } else if (throwable instanceof JsonParseException) {
                                    error = new ResponseError(HTTP_SERVER_ERROR,
                                            StringFetcher.getString(R.string.toast_error_server));
                                } else {
                                    error = new ResponseError(HTTP_UNKNOWN_ERROR,
                                            StringFetcher.getString(R.string.toast_error_unknown));
                                }

                                if (error.getStatus() == HTTP_UNAUTHORIZED) {
                                    return refreshTokenWhenTokenInvalid();
                                } else {
                                    return Observable.error(error);
                                }
                            }
                        });
                    }
                });
    }

    private Observable<?> refreshTokenWhenTokenInvalid() {
        synchronized (ResponseErrorProxy.class) {
            //TODO
            return null;
        }
    }
}

这里用了rxjava的知识,如果看不清楚只要看到动态代理结构就行。然后我们这个在获取接口的时候返回代理对象:

 @Provides
    @Singleton
    ApiService provideApiService(Retrofit retrofit){
        return getByProxy(ApiService.class,retrofit);
    }

    ApiService getByProxy(Class<? extends ApiService> apiService, Retrofit retrofit){
        ApiService api = retrofit.create(apiService);
        return (ApiService) Proxy.newProxyInstance(apiService.getClassLoader(),new Class<?>[] { apiService },new ResponseErrorProxy(api));
    }

我们看到在create返回ApiService 接口对象的时候我们又加了一层的代理,最终provideApiService()方法是返回我们的代理对象。这个方法是不是非常巧妙地处理了这些情况。想要看这段代码的话也可以看github上面:[HospitalGroup项目]。可以看到如果我们一个类实现了一个接口,然后我们又想增加这个类的功能就可以考虑用动态代理。
总结:静态代理还是动态代理,在使用的过程中都有其适用的场景,静态代理在之后的插件化里面用的还是比较多的,以后会见到。动态代理我们这里举了两个例子,这两个都是拦截原有的方法然后来增加额外功能或者解析原来请求方法来做一个动作。这里也希望大家遇到这种情况能想到这个模式。

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

推荐阅读更多精彩内容