×
广告

Retrofit解析5之代理设计模式

96
隔壁老李头
2017.06.27 22:17* 字数 4900

整体Retrofit内容如下:

  • 1代理设计模式
  • 2Java代理
  • 3静态代理
  • 4动态代理

一、代理设计模式:

(一)、代理模设计模式简介:

即Proxy Pattern,23种常用的面向对象软件设计模式之一。(设计模式的说法源自<设计模式>一书,原名<Design Patterns:Elements of Resuable Object-Oriented Software>,1995年出版,出版社:Addison Wesly Longman.Inc。 该书提出了23中基本设计模式)
代理模式的定义:为其他对象提供一种"代理"在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间提到中介的作用。

代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、远程代理、缓存代理、静态代理和动态代理,本文主要讲解动态代理。

(二)、代理模式概述

近年来,代购已逐步成为电子商务的一个重要分支。何谓代购,简单来说就是找人帮忙购买所需要的商品,当然你可能需要向实施代购的人支付一定的费用。代购通常分为两种类型:一种是因为在当地买不到某件商品,又或者是因为当地这件商品的价格比其他地区的贵,因此托人在其他地区甚至国外购买该商品,然后通过快递发货或者直接携带回来;还有一种代购,由于消费者对想要购买的商品相关信息的缺乏,自已无法确定其实际价值而又不想被商家宰,只好委托中介机构帮其讲价或为其代买。代购网站为此应运而生,它为消费者提供在线的代购服务,如果看中某国外购物网站上的商品,可以登录代购网站填写代购单并付款,代购网站会帮助进行购买然后通过快递公司将商品发送给消费者。商品代购过程如图所示:

代沟.png

在软件开发中,也有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想活着不能直接访问一个对象,此时可以通过一个称之为"代理"的第三者来实现间接访问,该方案对应的设计某事被称为代理模式。

所以总结下代理模式的定义如下:

代理模式:给一个对象提供一个代理或者占位符,并由代理对象来控制原对象的访问。
Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it.

代理模式是一种对象结构结构模式。在代理模式中银瑞一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者添加客户需要的额外的新服务。

更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

(二)、结构模式

代理模式的结构比较简单,其核心就是代理类,为了让客户能够一致性地对待真是对象和代理对象,在代理模式中引入了抽象层,代理模式结构如下:

代理结构模式.png

由上图可知,代理模式包含如下三个角色:

  • 1 Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程
  • 2 Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的的真实主题操作之前或之后还需要执行其他操作,而不是仅仅是单纯调用真实主题对象中的操作。
  • 3 RealSubject(真实主题角色):它定义了代理橘色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

(三)、代理模式的优点

  • 1、职责清晰:真实角色就是实现实际的业务逻辑,不用关心其他非本职责的食物,通过后期的代理完成一件事物,附带的结果就是编程的简洁清晰。
  • 2、代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护目标对象的作用。
  • 3、高扩展性

二、Java代理

Java代理模式就是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给为委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真实实现服务,而是通过调用委托类的对象关联,代理类的对象本身并不真正的实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期,代理类可以分为两种。

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
  • 动态代理:在程序运行时,运用反射机制动态创建而成。

代理模式的应用场景:

代理的使用场景很多,struts2中的action调用,hibernate的懒加载,spring的AOP无一不用到代理,当然还有咱们的主题Retrofit也用到了,总结起来就是可以分为以下几类:

  • 在原方法执行之前和之后做一些操作,可以用代理来实现(比如记录Log,做事物控制等)。
  • 封装真实的主题类,将真实的业务逻辑隐藏,只暴露给调用者公共的主题接口。
  • 在延迟加载上的应用。

三、静态代理

(一)静态代理的实现:

接口UserManager

/*** 
 * 用户控制接口 
 * @author Administrator 
 * 
 */  
public interface UserManager {  
 
    public void addUser(String userId,String userName);  
    public void modifyUser(String userId,String userName);  
    public void delUser(String userId);  
    public String findUser(String userId);  
}  

实现类

/**** 
 * 用户管理真正的实现类 
 * @author Administrator 
 * 
 */  
public class UserManagerImpl implements UserManager {  
  
    /***** 
     * 添加用户 
     */  
    public void addUser(String userId, String userName) {  
            System.out.println("正在添加用户,用户为:"+userId+userName+"……");  
    }  
    /***** 
     * 删除用户 
     */  
    public void delUser(String userId) {  
        System.out.println("delUser,userId="+userId);  
    }  
    /*** 
     * 查找用户 
     */  
    public String findUser(String userId) {  
        System.out.println("findUser,userId="+userId);  
        return userId;  
    }  
  
    public void modifyUser(String userId, String userName) {  
        System.out.println("modifyUser,userId="+userId);  
    }  
}  

代理类Proxy

/*** 
 * 代理类,提供用户实现类的访问控制 
 * @author Administrator 
 * 
 */  
public class Proxy implements UserManager{  
    private UserManager userManager;  
    public Proxy(UserManagerImpl ul)  
    {  
        userManager=ul;  
    }  
    public void addUser(String userId, String userName) {  
        System.out.println("正在进行添加用户前的准备工作,用户id为:"+userId+"……");  
        try {  
            userManager.addUser(userId, userName);  
            System.out.println("成功添加用户"+userId+",正在进行确认处理……");  
        } catch (Exception e) {  
            System.out.println("添加,userId="+userId+"失败!");  
        }  
          
          
    }  
  
    public void delUser(String userId) {  
        // TODO Auto-generated method stub  
          
    }  
  
    public String findUser(String userId) {  
        // TODO Auto-generated method stub  
        return null;  
    }  
 
    public void modifyUser(String userId, String userName) {  
        // TODO Auto-generated method stub  
    }  
}  

客户端Client

/**** 
 * 客户端 
 * @author Administrator 
 * 
 */  
public class client {  
  
        public static void main(String []args)  
        {  
            UserManager userManager=new Proxy( new UserManagerImpl());  
            userManager.addUser("0001", "张三");  
        }  
}  

运行结果


结果.png
时序图.png

(二)静态代理的优缺点

1、优点:

代理是客户端不需要知道实现类是什么,怎么做的,而客户端只需要知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。

2、缺点:
  • 1、代理类和委托类实现了相同的接口,代理通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有
    实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
  • 2、代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必腰围每一种对象都进行代理,静态代理在程序的访问提供了代理,但是如果要为其他类Department类提供代理的话,就需要我们再次添加代理Department的代理类。

为了解决上述问题,所以诞生了动态代理

三 动态代理

(一)动态代理的由来:

我们来说一下动态代理,静态代理之所以扩展和维护比较困难,是因为代码写的太死,没有可替换的余地,针对代码写的死能想到什么解决办法?对,就是反射。使用反射就可以解决决定加载那个代理类的问题,避免了每个代理类都要重复写的问题。这里主要说一下Java动态代理的实现。

(二)class文件简介及加载

Java编译好Java文件之后,产生.class文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载内存中,解析.class文件内的信息,生成对应的Class对象
如下图:

class加载.png
(三)在运行期的代码中生成二进制字节码

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力。

生成2进制字节码.png

(四)、Java动态代理中两个重要的类InvocationHandler与Proxy

Java中有很多的框架可以在运行时根据JVM规范动态的生成对应的.class字节码,比如ASM和Javassist等,这里就不详细介绍了,感兴趣的就可以去查询相关的资料。在Java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

1、InvocationHandler接口

首先我们先来看看Java的API帮助怎么对这个类进行描述:

/**
 * {@code InvocationHandler} is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * <p>Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the {@code invoke}
 * method of its invocation handler.
 *
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */

注释翻译一下就是:

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的
invoke 方法来进行调用。

我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

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

我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  • proxy:指的是我们所代理的那个真实对象
  • method:指的是我们所要调用真实对象的某个方法的Method对象
  • args:指的是调用真是对象某个方法时接受的参数
2、Proxy类
public class Proxy implements Serializable {
    protected InvocationHandler h;

    protected Proxy(InvocationHandler h) {
        throw new RuntimeException("Stub!");
    }

    //根据指定的类加载器和接口来获取代理类
    public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    //根据指定的类加载器和接口生成动态代理类的对象
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    //判断指定的对象是否是一个动态代理类
    public static boolean isProxyClass(Class<?> cl) {
        throw new RuntimeException("Stub!");
    }
     //获取指定代理对象关联的调用处理器
    public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
}

Proxy

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

翻译一下就是:

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法。但是我们用的最多的就是 newProxyInstance 这个方法:

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.  This method is equivalent to:
     * <pre>
     *     Proxy.getProxyClass(loader, interfaces).
     *         getConstructor(new Class[] { InvocationHandler.class }).
     *         newInstance(new Object[] { handler });
     * </pre>
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // 检查 h 不为空,否则抛异常
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Look up or generate the designated proxy class.
         */
         //获得与指定类装载器和接口相关的代理类类型对象
        Class<?> cl = getProxyClass0(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try { 
             // 通过反射获取构造函数对象并生成代理类实例
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            return newInstance(cons, h);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

看下注解大家就知道了newProxyInstance(ClassLoader,Class<?>[] ,InvocationHandler )这个方法的作用就是得到一个动态代理的对象,其接受三个参数,我们来看看这三个参数所代表的含义。

  • loader:一个ClassLoader对象,定义了由那个ClassLoader对象来对生成的代理对象进行加载
  • interfaces:一个Interface对象的数据,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我们就能调动这组接口中的方法了。
  • h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

它还有一个方法也是我们经常会用到的就是

   public static Class<?> getProxyClass(ClassLoader loader,
                                        Class<?>... interfaces)
       throws IllegalArgumentException
  {
       return getProxyClass0(loader, interfaces);
   }

用来产生代理类
来说下对应的两个参数

  • 1、ClassLoader loader:指定产生这个分字节码的类加载器。我们知道每一份Class字节码,都有getClassLoader()方法得到加载它本身的类加载器,而动态代理的字节码不能平白无故的在内存中创建的,所以为他指定在加载器,通常为实现接口的同个加载器。
  • Class<?>... interfaces:指定字节码实现的接口。指定内存中生成的这份字节码,实现的那个接口,可以指定实现多个接口。

由于篇幅有限,Proxy类中的代码还算简单,这里就不详解介绍Proxy类。

(五)动态代理与静态代理的区别

静态代理.png

上面就是静态代理模式的类图,当在代码阶段规定这个代理关系是,ProxySubject类通过编译器生成了.class字节码文件,当系统运行之前,这个.class文件就已经存在了。动态代理模式的结构和上面的静态代理模式的接口狗稍微有所不同,它引入了一个InvocationHandler接口和Proxy类。在静态代理模式中,代理类ProxySubject中的方法,都指定地调用了特定ReadSubject对应的方法;动态代理工作的基本模式就是讲自己方法功能的实现交给InvocationHandler角色,外界对Proxy角色中每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用了RealSubject的方法,如下所示:

动态代理.png

(六) 流程

1、流程
  • 第一步,获取RealSubject上的所有接口列表
  • 第二步,确定要生成的代理类的类名,系统默认生成的名字为:com.sun.proxy$ProxyXXX
  • 第三步,根据需要实现的接口信息,在代码中动态创建该ProxySubject类的字节码
  • 第四步,将对应的字节码转换为对应的Class对象
  • 第五步,创建InvocationHandler 的实例对象h,来处理Proxy角色的所有方法调用
  • 第六步,以创建的h对象为参数,实例化一个Proxy角色对象

代码如下:
Subject.java

public interface Subject {
    String operation();
}

RealSubject.java

public class RealSubject implements Subject{
    @Override
    public String operation() {
        return "operation by subject";
    }
}

ProxySubject.java

public class ProxySubject implements InvocationHandler{
     protected Subject subject;
    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //do something before
        return method.invoke(subject, args);
    }
}

测试代码

Subject subject = new RealSubject();
ProxySubject proxy = new ProxySubject(subject);
Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);
sub.operation();

以上就是动态代理模式的最简单实现代码,JDK 通过使用 java.lang.reflect.Proxy 包来支持动态代理

2、生成源码分析

那么通过Proxy类的newProxyInstance方法动态生成的类是什么样子?JDK为我们提供了一个方法ProxyGenerator.enerateProxyClass(String proxyName,class[] interfaces) 来产生动态代理类的字节码,这个类位于sun.misc包中,是属于特殊的jar包,于是问题又来了,androdi studio创建的android工程是没有办法找到ProxxyGenerator这个类的,这个类在jre下,最后废了半天的力气,终于使用下面这段代码就可以将生成的类导出在制定路径下:

public static void generateClassFile(Class clazz,String proxyName)
{
    //根据类信息和提供的代理类名称,生成字节码  
    byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
    String paths = "D:\\"; // 这里写死路径为 D 盘,可以根据实际需要去修改
    System.out.println(paths);
    FileOutputStream out = null;

    try {
        //保留到硬盘中  
        out = new FileOutputStream(paths+proxyName+".class");
        out.write(classFile);
        out.flush();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

调用方式如下

generateClassFile(ProxySubject.class, "ProxySubject");

最后就会在 D 盘(如果没有修改路径)的根目录下面生成一个 ProxySubject.class 的文件,使用 jd-gui 就可以打开该文件:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxySubject
  extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;

  public ProxySubject(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String operation()
  {
    try
    {
      return (String)this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("Subject").getMethod("operation", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

可以观察到这个生成的类继承自java.lang.reflect.Proxy,实现了Subject接口,我们再看看生成动态类的代码:

Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);

可见这个动态生成的类实现了subject.getClass().getInterfaces()中的所有接口,并且还有一点是类中所有方法都是final,而且该类也是final,所以该类不可继承,最后就是所有方法都会调用到
InvocationHandler对象的h的invoke()方法,这就是为什么最后调用到ProxySubject类的invoke()方法了,画一下他们的简单类图如下:

类图.png

从这个类图可以很清楚的看明白,动态生辰的类ProxySubject(同名,所以后面加上了Dynamic)持有了实现InvocationHandler接口的ProxySubject类的对象h,然后调用代理对象的operation方法时,就会调用到对象h的invoke方法中,invoke方法中根据operation方法时,就会调用到对象的h的invoke方法中,invoke方法中根据method的名字来区分到底是什么方法,然后通过methode.invoke()方法来调用具体对象的对应方法。

(七)、动态代理类优缺点

有点:

  • 动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
  • 动态代理类不仅简化了编程工作,而且提高了软件系统的扩展性,因为Java反射机制可以生成任意类型的动态代理类。

缺点:

  • JDK的动态代理机制只能代理实现实现了接口类,而不能实现接口的类就不能实现JDK动态代理。
Web note ad 1