1. 程式人生 > >動態代理之 JDK 動態代理

動態代理之 JDK 動態代理

動態代理

動態代理源於設計模式中的代理模式,代理模式的主要作用就是使代理物件完成使用者的請求,遮蔽使用者對真實物件的訪問。通過代理物件去訪問目標物件來控制原物件的訪問。

代理模式的最典型的應用就是 Spring AOP。

靜態代理

代理模式的實現有兩種,靜態代理和動態代理,靜態代理的代理類是需要程式設計師去寫的,而動態代理的代理類是自動生成的。

靜態代理需要持有被代理物件的引用,通過這個引用去呼叫被代理物件的方法。

我們來看一個靜態代理的例項:

首先定義一個介面,代理物件和被代理物件都需要實現這個介面。

public interface IService{
  void sayHello();
}

被代理的物件:

public class RealClass implements IService{
  @Override
  public void sayHello(){
    System.out.println("hello world.....");
  }
  
  public void doService(){
    System.out.println("doing service.....");
  }
}

真正的代理類需要做的事情:

public class ProxyClass implements IService{
  //被代理物件的引用
  private RealClass realClass;
  
  public ProxyClass(RealClass realClass){
    this.realClass = realClass;
  }
  
  @Override
  public void sayHello(){
    System.out.println("i am proxy, prepare for saying hello...");
    realClass.sayHello();
    System.out.println("i am proxy, saying hello ending...");
  }
  
  public void doService(){
    System.out.println("i am proxy, prepare for saying hello...");
    realClass.doService();
    System.out.println("i am proxy, saying hello ending...");
  }
}

一般來說,代理類會選擇直接繼承被代理類所有的介面和父類以便於實現所有被代理類的方法。

到這裡其實靜態代理就講完了,也沒有什麼難點。但是動態代理不同於靜態代理的點在於,代理類不用我們自己寫。

JDK 動態代理

動態代理區別於靜態代理的一點是,動態代理的代理類由虛擬機器在執行時動態建立並於虛擬機器解除安裝時清除。

我們還用上面的 RealClass 類,看看 JDK 的動態代理是如何實現的。

首先定義一個 Handler 類,它繼承自 InvocationHandler。

public class MyHandler implements InvocationHandler{
  //同樣需要傳入被代理類的引用,這裡我們直接使用 Object 型別
  private Object realObject;
  
  public MyHandler(Object realObj){
    this.realObject = realObj;
  }
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
        System.out.println("proxy begainning ... ");
            // 通過反射的方式,呼叫被代理物件的方法
            Object result = method.invoke(realObj,args);
            System.out.println("proxy ending....");
            return result;
  }
}

代理類的生成以及呼叫過程:

public static void main(String[] args){
  RealClass rc = new RealClass();
  MyHandler handler = new MyHandler(rc);
  Object obj = Proxy.newProxyInstance(rc.getClass().getClassLoader(),new Class[]{IService.class},handler);
}
  1. 首先我們定義處理類,它繼承了 InvocationHandler 並實現了 invoke 方法。這裡面還是需要傳入被代理物件的引用。
  2. InvocationHandler 的實現類裡面通過反射來呼叫被代理兌現的方法,還可以加上一些新的功能。
  3. 通過 JDK 提供的 Proxy 類的 newProxyInstance 方法來構建代理類,裡面需要傳入三個引數,類載入器,被代理類的所有介面,第三個是我們自己定義的 InvocationHandler 的實現類。

我們通過反編譯來看看這個代理類的 Class 到底有什麼不同:

  1. 生成的代理類的名字是很隨意的,一個程式中如果有多個代理類藥生成,【$PRroxy + 數字】就是它們的類名。
  2. 這個代理類繼承了 Proxy 類並且實現了 IService 介面(之前如果指定多個,這裡就會繼承多個)。
  3. 代理類的構造器需要傳入 InvocationHandler 型別的引數。並且將這個引數傳遞給了父類。這也是為什麼所有代理類都必須使用 Proxy 作為父類的一個原因。

我們繼續看代理類下面的內容:

  1. 包裹在 static 裡面的靜態程式碼塊很重要,通過反射獲取了四個 Method。其中有三個是 Object 類的常用方法,也就是說代理類還會代理被代理物件從 Object 繼承來的方法,還有一個是被代理類的介面的方法。

  1. 最後一部分我們看到的是,虛擬機器根據靜態初始化程式碼塊所反射出來的所有代理方法,為他們生成代理的方法。
  2. 呼叫時需要從父類 Proxy 中取出構造例項化時儲存的處理器類,並呼叫它的 invoke 方法。
  3. 第一個引數是當前代理類的例項(事實證明這個引數並沒有什麼用),第二個引數是 Method 方法例項,第三個引數是方法的形式引數集合,如果沒有就是 null。

總結

  1. 一個處理器類的定義是必不可少的,它的內部必須關聯一個真實物件,即被代理類例項。
  2. 我們從外部呼叫代理類的任意一個方法,從反編譯的原始碼我們知道,代理類方法會轉而去呼叫處理器的 invoke 方法並傳入方法簽名和引數。

缺陷

我們需要注意到,虛擬機器生成的代理類為了共用 Proxy 類中的 InvocationHandler 欄位來儲存自己的處理器類例項而繼承了 Proxy 類。

這裡有個問題,說明代理類不能在繼承其他類了。那麼被代理類父類的方法自然就無法獲取了,即代理類無法代理被代理類中父類的任何方法,只能代理介面中的方法,這也是我們常說的 JDK 動態代理必須實現介面的原因。

RealClass 自己的方法 doService 也沒有被代理,只有介面中的方法被代理了。所以說,JDK 的動態代理機制是單一的,它只能代理被代理類的介面集合中的方法。。

類中的非介面方法和父類中的方法是無法被代理的。

不友好的返回值。

public static void main(String[] args){
  RealClass rc = new RealClass();
  MyHandler handler = new MyHandler(rc);
  Object obj = Proxy.newProxyInstance(rc.getClass().getClassLoader(),new Class[]{IService.class},handler);
}

newProxyInstance 返回的是代理類 【Proxy0】的一個例項,但是它是以 Object 型別進行返回的,而你又不能把它強轉成 【Proxy0】型別。因為編譯期是不存在這個【Proxy0】型別的,所以一般只會強轉為該代理類實現的介面之一。

IService obj = (IService)Proxy.newProxyInstance(
        rc.getClass().getClassLoader(),
        new Class[]{IService.class},
        hanlder);
obj.sayHello();

現在問題又來了,加入我們被代理類實現了多個介面,那麼你該強轉為哪個介面型別哪?可能需要你多次進行轉換,這樣的設計相當不友好。

下一篇我們將介紹一個廣為各類框架使用的 CGLIB 動態代理庫,它的底層基於位元組碼操作框架 ASM,不再依賴繼承來實現,完美的解決了 JDK 的單一代理的不足