文章目录

  • 1. 前言
  • 2. 代理模式(Proxy Pattern)
    • 2.1 静态代理模式
    • 2.2 动态代理模式
  • 3. Android 中的代理模式
    • 3.1 Retrofit中的代理模式(没有被代理者)
  • 4. 后记

1. 前言

首先看下百度百科对代理模式的介绍:

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

其实也就是中介模式或者委托模式。在日常生活中代理模式很多,比如叫同事带饭、打官司等。使用代理模式的主要意图为:为其他对象提供一种代理以控制对这个对象的访问。主要的使用场景为:当无法或不想直接访问某个对象时,可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口

在Spring-AOP简介一文中曾提到过两种代理方式,即:静态代理和动态代理。

  • 静态代理:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

这里继续围绕这两种方式进行回顾和展开。

2. 代理模式(Proxy Pattern)

2.1 静态代理模式

比如下面的案例:

interface ISubject {void doSomething();
}// 真正的实现类
class RealSubject_ZhangSan implements ISubject{@Overridepublic void doSomething() {System.out.println("买手机");}
}class ProxySubject_Shop implements ISubject{private RealSubject_ZhangSan mSubject;  // 真正做这件事的对象public ProxySubject_Shop(RealSubject_ZhangSan subject){this.mSubject = subject;}@Overridepublic void doSomething() {  // 只做代理mSubject.doSomething();}
}public class Client {public static void main(String[] args) {RealSubject_ZhangSan subject = new RealSubject_ZhangSan();// 构造代理对象ProxySubject_Shop proxySubject = new ProxySubject_Shop(subject);// 调用代理的方法proxySubject.doSomething();}
}

对应的类图为:

Client通过接口知道需要调用什么方法,而因为在代理模式中不能直接访问真正的目标对象,而需要通过代理对象来进行访问。所以在Client中需要使用代理对象,由于代理模式规定了需要保证客户端使用的透明性,所以被代理对象和代理对象都需要事项相同的接口,即这里的doSomething方法。

值得注意的是,按照上面的写法对于每个具体的代理类,我们都需要在其中指定维护(代理)的具体Subject类,那么当有很多具体的RealSubject的时候,就需要定义很多的代理类,当然这是不合理的。所以在代理类中只有的应该是接口,比如:

class ProxySubject_Shop implements ISubject{private ISubject mSubject;  // 真正做这件事的对象,面向接口编程而不要面向具体的实现public ProxySubject_Shop(ISubject subject){this.mSubject = subject;}@Overridepublic void doSomething() {  // 只做代理mSubject.doSomething();}
}

虽然上面这种方式确实解决了存在多个真实主题类的情况,但是如果代理类需要在原有的真实Subject前或者后做一些自己独有的操作的时候,就需要根据代理类的种类,来定义多个代理类。这显然也是不合理的。所以就需要动态代理。

从前面我们知道使用静态代理其实也就是在源码中直接指定。而动态代理方式相反,是通过反射的机制来动态地生成代理者的对象,也就是说代理谁将会在执行阶段决定。

2.2 动态代理模式

动态代理中所使用的技术也就是反射,在Java中提供了一个便捷的动态代理接口InvocationHandler以及相关的代理类Proxy

  • InvocationHandler:调用处理程序,并返回一个结果的;
  • Proxy:提供了创建动态代理类和实例的静态方法,用于生成动态代理的这个实例;
interface ISubject {void doSomething();
}// 真正的实现类
class RealSubject_ZhangSan implements ISubject{@Overridepublic void doSomething() {System.out.println("买手机");}
}public class Client {public static void main(String[] args) {RealSubject_ZhangSan realSubject = new RealSubject_ZhangSan();// 动态构造一个代理者 ==> 代理realSubjectISubject subject = (ISubject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),new Class[]{ISubject.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(realSubject, args);}});// 得到类RealSubject_ZhangSan的实现接口Class对象// new Class[]{ISubject.class} 或者:RealSubject_ZhangSan.class.getInterfaces()  // 调用代理的方法subject.doSomething();}
}

当然这里可以抽离出来一个动态代理类:

class DynamicProxy<T extends ISubject> implements InvocationHandler {private T mObj;public DynamicProxy(T obj){this.mObj = obj;}public ISubject getProxy(){return (ISubject) Proxy.newProxyInstance(mObj.getClass().getClassLoader(),mObj.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(mObj, args);}
}public class Client {public static void main(String[] args) {RealSubject_ZhangSan realSubject = new RealSubject_ZhangSan();// 动态代理ISubject subject = new DynamicProxy<RealSubject_ZhangSan>(realSubject).getProxy();// 调用代理的方法subject.doSomething();}
}

对应的类图:

观察上面类图,可以直观的看到就是动态代理类和具体的实现类就没有直接关联关系了。而需要做的是在Client代理类中进行动态确定。可以看到就是这种方式的代码耦合度更低。也就是完成了代理者和被代理者之间的解耦,使得这两者之间没有直接的耦合关系。当然,静态代理类的方式更加符合面向对象原则,开发时具体使用哪种代理方式,就没有什么规定了。

3. Android 中的代理模式

Android中有ActivityManagerProxyActivityManagerService以及Retrofit等均有代理模式的身影。这里以Retrofit中的代理模式为例。

3.1 Retrofit中的代理模式(没有被代理者)

为了了解Retrofit中的代理模式,首先需要先引入相关依赖到项目中:

// https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'

对于怎么使用在Retrofit的使用案例中曾简单的使用过这个框架。这里做一个简单的回顾:

  • 定义指定具体的URL地址,来构造Retrofit对象;
  • 使用第一步得到的Retrofit实例对象调用create方法,当然这个方法中传入一个自定义的接口。在这个接口中完成链接返回数据转换为Java实例对象。
  • 类似的将请求加入到请求队列中,然后接收返回结果即可。

而在Retrofit类中的create方法中就可以看见动态代理模式:

public <T> T create(final Class<T> service) {validateServiceInterface(service);return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},new InvocationHandler() {private final Platform platform = Platform.get();private final Object[] emptyArgs = new Object[0];@Overridepublic @NullableObject 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);}return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);}});
}

比如此时的请求接口还是像Retrofit的使用案例文中定义的一样,即:

public interface RequestInterface {@GET(value = "/test/1.0/users")Call<List<User>> listUsers();   // retrofit2.Call;@GET(value = "/test/1.0/users/{userid}")Call<User> getUserById(@Path(value = "userid") char userId);@FormUrlEncoded@POST(value = "/test/1.0/users")Call<Void> addUser(@Field(value = "name") String name);
}

那么在构造Retrofit中的请求对象的时候,就在create方法中传入这个接口的Class对象:

RequestInterface request = retrofit.create(RequestInterface.class);

继续回到Retrofit类的create方法,可以发现生成的代理对象其实就是代理这个RequestInterface接口。但是值得注意的是,RequestInterface接口只是代理模式中的Subject,而我们这里需要找到真正的被代理对象类。但是很遗憾确实这里没有找到,那么不妨来仿写一下:

interface ISubject {void doSomething();
}public class Client {public static void main(String[] args) {ISubject subject = (ISubject) Proxy.newProxyInstance(ISubject.class.getClassLoader(),new Class[]{ISubject.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("invoke");return 0;}});System.out.println("package: \n\t" + subject.getClass().getName());System.out.println("implemented interface: ");Class<?>[] interfaces = subject.getClass().getInterfaces();for (Class<?> anInterface : interfaces) {System.out.println("\t"+anInterface.getName());}System.out.println("fields: ");Field[] declaredFields = subject.getClass().getDeclaredFields();for (Field declaredField : declaredFields) {System.out.println("\t"+declaredField.getName());}System.out.println("methods: ");Method[] declaredMethods = subject.getClass().getDeclaredMethods();for (Method declaredMethod : declaredMethods) {System.out.println("\t"+declaredMethod.getName());}subject.doSomething();}
}

输出结果为:

在Retrofit2 源码解析之动态代理一文中给出了得到生成的动态代理类的字节码数组并存储为.class文件的方式,即:

private static void storageClassFile(ISubject subject){// subject 为上面生成的动态代理对象String proxyName = subject.getClass().getName() + ".class";byte[] clazz = ProxyGenerator.generateProxyClass(proxyName, new Class[]{ISubject.class});try {OutputStream out = new FileOutputStream(proxyName);InputStream is = new ByteArrayInputStream(clazz);byte[] buff = new byte[1024];int len;while ((len = is.read(buff)) != -1) {out.write(buff, 0, len);}is.close();out.close();} catch (IOException e) {e.printStackTrace();}
}

那么在这个项目的跟目录中可以看到生成的文件:


直接使用IDEA来进行打开:

public final class class extends Proxy implements ISubject {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public class(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 void doSomething() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}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 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"));m3 = Class.forName("com.example.scan.kaoshi.ISubject").getMethod("doSomething");m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

可以看到这个类确实是实现类ISubject接口的类,且实现了基本的hashCodetoStringequals方法。但是因为在ProxyGenerator.generateProxyClass方法存在于sun.misc.ProxyGenerator包中,而Android中并不支持这个包所以有必要继续看Retrofit中的源码:


可以看到其实首先通过loadServiceMethod(method)这个方法将调用的实际方法传递进去,然后在loadServiceMethod方法中进行Map<Method, ServiceMethod<?>>查找看是否已经存在这个方法,存在就直接返回,否则就通过ServiceMethod.parseAnnotations(this, method)方法来对声明接口中的这个方法的注解进行解析,在这个方法中会继续解析注解,最终会使用HttpServiceMethod.parseAnnotations方法来进行解析,由于代码比较多这里就不贴出来了。在这个方法中,首先得到这个方法的返回类型,然后使用Okhttp3来进行请求的封装,并且得到这个方法的所有注解,并进行对应的解析。最终会返回一个HttpServiceMethod对象到Retrofit.javacreate方法。我们之后后续的使用和Okhttp类似,也就是构建Call对象以及请求加入到队列中。

所以在Retrofit中不完全是代理模式,很容易理解,因为在这个过程中确实没有RealSubject对象,而只存在接口和动态代理Proxy的对象。也比较好理解,因为在网络请求过程中,我们定义RESTful风格来进行请求链接和返回参数的定义,中间其实不需要一个真实的被代理类来实现这个请求,而是需要将其注解进行解析,得到实际有意义的请求参数和返回值类型,最终可以动态添加到Okhttp请求和满足动态封装Bean即可。

在这个过程中由程序自己来处理注解,以及完成请求API的解析工作,进而可以在底层使用Okhttp进行网络请求的时候可以对参数以及返回值进行相关的数据封装。所以说其实在Retrofit中不完全是代理模式,因为没有(也不需要)实际上的被代理者。

4. 后记

在《Android源码设计模式》一书中提到:不少设计模式中都存在代理模式的影子,且在日常生活中无处不在。作者花了很大的篇幅来介绍这个模式,且在Binder中也存在代理模式的影子,之后需要再仔细读读。


References

  • Spring-AOP简介
  • Retrofit的使用案例
  • Retrofit2 源码解析之动态代理

Android常见设计模式——代理模式(Proxy Pattern)相关推荐

  1. Android常见设计模式——代理模式(Proxy Pattern)(二)

    文章目录 1. 前言 2. 远程代理(Remote Proxy) 3. 后记 1. 前言 在上篇Android常见设计模式--代理模式(Proxy Pattern)中基本上知道了什么是代理模式,以及对 ...

  2. 设计模式-代理模式(Proxy Pattern)

    设计模式-代理模式(Proxy Pattern) 文章目录 设计模式-代理模式(Proxy Pattern) 一.定义 二.概念解释 三.场景 四.实现 1.类图 2.代码实现 五.小结 六.动态代理 ...

  3. 设计模式——代理模式(Proxy Pattern)之为别人做嫁衣

    代理模式Proxy Pattern 代理模式 1.背景 2.定义 3.特征 4.应用场景 5.实验案例 参考 代理模式 1.背景 假如说我现在想租一间房子,虽然我可以自己去找房源,做卫生检测等一系列的 ...

  4. Java24种设计模式(第二种)--代理模式(Proxy Pattern)

    Java24种设计模式 (第二种) 一.代理模式(Proxy Pattern) 模式逻辑: 什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道 被代理人能做哪些事 ...

  5. 二十四种设计模式:代理模式(Proxy Pattern)

    代理模式(Proxy Pattern) 介绍 为其他对象提供一个代理以控制对这个对象的访问. 示例 有一个Message实体类,某对象对它的操作有Insert()和Get()方法,用一个代理来控制对这 ...

  6. 代理模式(Proxy Pattern)

    设计模式 - 吕震宇 .NET设计模式系列文章 薛敬明的专栏 乐在其中设计模式(C#) C#设计模式(13)-Proxy Pattern 一. 代理(Proxy)模式 代理(Proxy)模式给某一个对 ...

  7. js设计模式——代理模式proxy

    什么是代理模式 代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问. (可以想象一下明星与经纪人的关系,明星是请求的本体,经纪人就是代理proxy) 如何实现代理模式 代理对象内部含有对本 ...

  8. 设计模式(结构型)之代理模式(Proxy Pattern)

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

  9. JAVA设计模式Design Pattern→单例模式Singleton Pattern、工厂模式Factory Pattern、代理模式Proxy Pattern

    私有化构造函数的类可以提供相应的 "接口"(一般就是静态方法)来返回自己的唯一实例供外部调用,像这样的确保只生成一个实例的模式被称作单例模式. 工厂模式,一个模型,用来大规模的生产 ...

最新文章

  1. 使用python制作神经网络——搭建框架
  2. 05传智_jbpm与OA项目_部门模块中增加部门的jsp页面增加一个在线编辑器功能
  3. php jquery grid,jQuery Grid
  4. python tkinter entry默认值_Python Tkinter Entry和Text的添加与使用详解
  5. 启动和退出mysql的三种方法_Oracle数据库几种启动和关闭方式
  6. mapreduce介绍_MapReduce:简单介绍
  7. 嵌入式计算机的特点和应用,以下描述中,()不是嵌入式操作系统的特点。A.面向应用,可以进行裁剪和移植B.用 - 信管网...
  8. Membership Inference Attacks Against Recommender Systems论文解读
  9. 可读性代码:为什么、怎样以及什么时候
  10. 在构造函数中使用new时应注意的事项
  11. java设计模式面试,深入分析
  12. 入门Sysmac Studio,白菜妹子是这样做的。
  13. w ndows10图标,Win10桌面图标没了怎么办?Win10桌面快捷方式消失了解决方法
  14. AutoCAD Civil 3D-部件-部件编辑器自定义边坡与材质
  15. 如何用Python量化“相似K线”实现形态选股?
  16. 邮件撤回怎么操作?个人邮箱Outlook登录入口在哪?
  17. freemarker模板生成word文档踩坑记录
  18. 钉钉小程序开发 (企业内部应用)
  19. Microsoft office 2007 word PPT 转pdf的插件
  20. Python图像处理之透视变换

热门文章

  1. java中的Arrays.sort()的几种用法
  2. MySQL删除表数据 MySQL清空表命令 3种方法
  3. ColorUI组件库简易教程之扩展插件
  4. 在服务器中配置pytorch
  5. CentOS查看CUDA版本
  6. 从传统劳务行业转型SaaS工具,叮叮劳务帮助解决建筑工人薪资支付问题
  7. tcp最大连接数限制
  8. switch 计算器?!
  9. java 乐观锁和悲观锁,Threadlocal
  10. 华为平板电脑哪些支持鸿蒙,华为鸿蒙2.0手机版要来了,支持机型有这些