Retrofit解析5之代理設計模式

整體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動態代理。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容