Java代理

代理模式

代理模式,顧名思義,即一個客戶不想或者不能直接訪問一個對象,需要通過一個稱為代理的第三方對象來實現(xiàn)間接引用。代理對象的作用就是客戶端和目標對象
之間的一個中介,通過代理對象可以隱藏不讓用戶看到的內容或實現(xiàn)額外的服務。

代理機制應用的場景有很多:比如在代理對象中實現(xiàn)緩存,驗證,權限控制等功能,真正的業(yè)務邏輯封裝在真實對象中。RMI遠程方法調用也用到了代理。當你調用一個遠程方法的時候,相當于調用這個方法的代理對象,
在代理對象中封裝了網絡請求等部分,真實對象存在于另一個進程上。重構老舊代碼的時候也常常會用到代理模式。

代理分兩種:靜態(tài)代理和動態(tài)代理

靜態(tài)代理

靜態(tài)代理即在代碼中手動實現(xiàn)代理模式。代理模式涉及到三個角色:

真實對象RealSubject、抽象主題Subject、代理對象Proxy

image.png
image.png
public class ProxyTest {
    public static void main(String[] args) {
        new Proxy("hello").request();
    }
}
interface Subject {
    void request();
}
class Proxy implements Subject{
    String str;
    RealSubject subject;
    public Proxy(String string) {
        str = string;
        subject = new RealSubject(str);
    }
    @Override
    public void request() {
        System.out.println("代理對象驗證機制....");
        subject.request();
    }
    
}

class RealSubject implements Subject{
    String str;
    public RealSubject(String string) {
        str = string;
    }
    @Override
    public void request() {
        System.out.println("真實對象打印str: " + str);
    }
    
}

輸出:

代理對象驗證機制....
真實對象打印str: hello

上面的代碼模擬了一個代理對象實現(xiàn)驗證機制的過程??梢钥吹剑a很簡單,代理模式也很好理解。
(我們在真實生活中不也有代理么,,比如黃牛,幫你買到你買不到的火車票)

JDK動態(tài)代理實現(xiàn)

動態(tài)代理時較為高級的一種代理模式。典型的應用有Spring AOP,RMI。

在上面的靜態(tài)代理模式中,真實對象是事先存在的,并且作為代理對象的內部成員屬性。一個真實的對象必須對應一個代理對象,如果真實對象很多的話會導致類膨脹。

另外,如何在事先不知道真實對象的情況下使用代理代理對象,這都是動態(tài)代理需要解決的問題。

比如有n個類需要在執(zhí)行前打印幾行日志,而這n個類是無法通過源代碼修改的(從jar包中引入的)。通過靜態(tài)代理實現(xiàn)的話將會有n個新的代理類產生,而使用動態(tài)代理的話,只需一個類即可。

動態(tài)代理的實現(xiàn)方式有很多,我們只討論JDK中的動態(tài)代理實現(xiàn)。

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

public class DynamicProxyTest {
    public static void main(String[] args) {
        Subject subject = (Subject) new DynamicProxy().bind(new RealSubject("hello"));
        subject.request();
    }
}

interface Subject {
    void request();
}

class RealSubject implements Subject{
    String str;
    public RealSubject(String string) {
        str = string;
    }
    @Override
    public void request() {
        System.out.println("真實對象打印str: " + str);
    }
}

class DynamicProxy implements InvocationHandler {
    Object object;
    public Object bind(Object object) {
        this.object = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                object.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("代理對象驗證...");
        return method.invoke(object, args);
    }
}

輸出:

代理對象驗證...
真實對象打印str: hello

可以看到,動態(tài)代理實現(xiàn)了與靜態(tài)代理一樣的功能,但他的優(yōu)點在于代理的真實對象不是確定的,可以在運行時指定,增大了靈活性。如果我們有很多的真實對象需要代理訪問,并且他們代理對象中的內容
都實現(xiàn)了相同的功能,那么我們只需要一個動態(tài)代理類即可。

動態(tài)代理原理

我們通過觀察java.lang.reflect.Proxy的源碼來了解動態(tài)代理的原理。下面的代碼截取自openjdk7-b147 (安利一個不錯的搜索java源碼的網站:http://grepcode.com)

image.png

上面的方法截取自Proxy.newProxyInstance,可以看到,調用getProxyClass方法獲取到一個代理類class對象,然后使用該class對象通過反射方法實例化一個對象返回。

接下來觀察getProxyClass方法。

image.png

這部分代碼截取自getProxyClass,先從緩存中查詢是否已經生成過對應的class,若有,則直接返回該對象,沒有,則繼續(xù)下一步生成class

image.png

這部分代碼是代理類class對象的生成過程。其中:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);這行代碼調用ProxyGenerator.generateProxyClass返回了代理類class對象的字節(jié)碼byte序列,
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);這一行則進行了類加載的工作,最終生成了代理類class對象。

image.png

generateProxyClass,其中的gen.generateClassFile()方法實現(xiàn)了字節(jié)碼的生成。

image.png

generateClassFile方法的實現(xiàn)。開頭調用的三個addProxyMethod方法將object類中的hashcode、equals、toString方法重寫,故對這三個方法的調用會傳遞到InvocationHandler.invoke方法當中。
注意,除了上述三個方法之外,調用代理類中Object定義的其他方法不會傳遞到invoke方法當中,也就是說,調用這些方法會執(zhí)行Object中的默認實現(xiàn)。

如果想要查看ProxyGenerator.generateProxyClass這個方法在運行時產生的代理類中寫了些什么,可以在main方法中加入:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

運行時會將生成的class文件保存到硬盤當中:$Proxy0.class

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

public final class $Proxy0
  extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;
  
  public $Proxy0(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 void request()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    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);
    }
  }
  
  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);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("Subject").getMethod("request", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

上面的代碼很好理解??梢钥吹絜quals、hashCode、toString以及我們Subject接口request方法的實現(xiàn)中都是調用了InvocationHandler.invoke方法,而這個InvocationHandler實例就是我們在Proxy.newProxyInstance中傳入的對象。

綜上,可以看到實現(xiàn)動態(tài)代理的幾個步驟:

1.實現(xiàn)InvocationHandler

2.獲得動態(tài)代理類,這一步又涉及到運行時代理類字節(jié)碼的生成和類加載

3.通過反射機制(getConstructor(InvocationHandler.class))獲取代理類的實例并返回該對象

4.調用代理對象的目標方法(也就是request方法,代理類也實現(xiàn)了Subject這個接口),調用轉發(fā)到InvocationHandler.invoke方法當中,執(zhí)行invoke的邏輯(我們自己的InvocationHandler實現(xiàn))

至此,我們就了解了動態(tài)代理的運行原理。動態(tài)代理的機制也有一些缺陷,比如他代理的必須是接口方法。看一下我們上面生成的$Proxy0.class,可知這個代理類已經默認繼承了類Proxy,所以,他只能通過實現(xiàn)我們提供的接口來代理我們的方法。在invoke方法中,我們可以通過對傳入的代理類、方法和參數(shù)來進行判斷,對不同的方法實現(xiàn)不同的業(yè)務邏輯。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Java代理和動態(tài)代理機制分析和應用 概述 代理是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個...
    丸_子閱讀 3,164評論 6 57
  • 版權聲明:本文為博主原創(chuàng)文章,未經博主允許不得轉載 前言 Java 代理模式在 Android 中有很多的應用。比...
    cc榮宣閱讀 946評論 0 7
  • 事例 小張是一個普普通通的碼農,每天勤勤懇懇地碼代碼。某天中午小張剛要去吃飯,一個電話打到了他的手機上?!笆荴X公...
    余平的余_余平的平閱讀 515評論 0 0
  • 動機 學習動機來源于RxCache,在研究這個庫的源碼時,被這個庫的設計思路吸引了,該庫的原理就是通過動態(tài)代理和D...
    卻把清梅嗅閱讀 435評論 0 1
  • 代理模式(Proxy Pattern)的定義:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象...
    Whyn閱讀 856評論 0 1

友情鏈接更多精彩內容