其实在日常生活中我们经常和代理模式打交道,比如当我们需要预定酒店时,我们(使用方)通常选择类似携程这样的平台来进行酒店和机票的预定,这里携程(代理)就代理了和酒店(服务提供方)之间的预订流程,使用方通过代理实现了和真实服务提供者之间的操作,那么为什么会使用代理而不是直接和服务提供方进行业务操作呢?可以通过以下两方面来考量:
- 代理提高了扩展性。以订酒店为例,乘客可以通过代理类(携程)调用不同类型的服务(不同酒店)实现。
- 代理降低了替换成本。实现了代理类提供的服务和实际业务服务的解耦,降低了服务替换的成本。还是以订酒店为例,让用户只需要关注到订酒店这件事情,和订酒店无关的其他事情都由代理商来做了。
什么是「代理模式」?
代理控制了对象的真实访问。代理模式是指,在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。主要解决在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
这里直接访问对象时带来的问题,可以类比上面生活中的例子,车站买票太远(对象单次访问成本高)选择用代买点买票。直接去酒店订房间太麻烦(实现功能的对象太多无法直接选择出最佳的对象)而选择在酒店平台预定酒店。
0x01 Binder中的静态代理
几乎所有安卓开发应该都应该知道,binder是android中实现进程间通信的方案,也都听说过所谓共享内存、Socket、管道、消息队列等等一系列其他的进程间通信(IPC)的方案,甚至听说过诸如dubbo,spring cloud之类的远程过程调用(RPC)。这里binder作为一个IPC方案同时在设计上又有很多RPC方案的影子,如果说想要理解RPC,这里一定绕不开代理模式。不管是Android的binder还是后端消息中间件dubbo都是使用代理模式来做设计的,在通讯的两端分别用代理隐藏实际的通信细节,让调用方像调用自己进程内对象方法一样实现对跨进程对象的调用。你比较熟悉组件化开发的话,也可以先从组件化的视角来类比binder通信,如果让你来实现一个进程间通信的架构方案,有哪些东西是必不可少的呢?
先类比组件化
- 面向接口的设计,组件间通信,通过接口暴露组件能力
- 通过一个manager查询接口对应的服务实例,组件间相互调用,意味着组件间能够通过某种方式获取定义的组件接口所对应的实例对象,实现调用其他组件的方法就像调用自己内部方法一样
- 服务的注册和查询,想要获取响应的接口实现,意味着有一个类似路由和路由表的东西,通过一个路由服务查询到具体的接口实现对象
- 其他高性能、安全性、稳定性的设计
再看Binder IPC,
- 开发者使用AIDL实现进程间通信接口的定义
1 | interface IMyAidlInterface { |
- 调用者client通过context bindService获取远程服务的代理对象,通过
queryLocalInterface
和Stub.DESCRIPTOR
查询到远程服务在client的代理对象
1 | public static com.android.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj) { |
- 对client来说,Binder代理对象
Stub.proxy
隐藏了和远程服务对象真实通信的细节,client不需要关心这个代理对象是不是真实的服务实现方,就像调用本地方法一样调用原生服务对象。而Stub.proxy
实现了调用参数的序列化和响应结果的序列化,帮助client拿到了远程调用的结果。
可以对照下图理解代理过程:
同样的角色分布我们可以再看看系统的ActivityManagerService。IActivityManager是一个服务接口,代表了服务能力。ActivityManagerNative代表系统本地服务,ActivityManagerService是它的具体实现。而ActivityMangerProxy代表在app中的Binder代理对象,实现client到service调用的代理转发。
0x02 Retrofit中的动态代理
相比较Binder的静态代理,retrofit使用了动态代理模式。所谓动态代理模式,是指并没有手动创建一个代理类,而是使用动态字节码的方式创建代理类(用class生成class),然后使用反射的方式创建代理类的对象,再使用反射方式调用被代理的方法。可以先看一个在java中最简单的动态代理写法:
1 | public class TestProxy { |
我们使用Proxy.newProxyInstance
和InvocationHandler
动态构造代理对象,通过获取invoke method注解对请求的描述信息,生成ServiceMethod
对象,并根据该对象执行相应的网络请求。
1 | public <T> T create(final Class<T> service) { |
0x03 总结
这里借用知乎大佬的一张图,同时对比下静态代理和动态代理的差异,主要在于代理类如何生成。
如果是编译或者编码过程中生成的代理类就是静态代理,所以静态代理的一个缺点就是会生成很多代理类。
如果在运行时或者编译时动态生成的代理类,一般就是动态代理。可以通过接口的Class对象,创建一个代理Class,通过代理Class创建代理对象。也就是所谓的用Class造Class。
参考
http://weishu.me/2016/01/12/binder-index-for-newer/
https://zhuanlan.zhihu.com/p/35519585
http://gityuan.com/2016/09/04/binder-start-service/
https://www.zhihu.com/question/20794107