基於JDK的動態代理原理分析

韓·某某發表於2022-03-28

基於JDK的動態代理原理分析

這篇文章解決三個問題:

  1. What 動態代理是什麼

  2. How 動態代理怎麼用

  3. Why 動態代理的原理

動態代理是什麼?

動態代理是代理模式的一種具體實現,是指在程式執行期間,動態的生成目標物件的代理類(直接載入在記憶體中的位元組碼檔案),實現對目標物件所有方法的增強。通過這種方式,我們可以在不改變(或無法改變)目標物件原始碼的情況下,對目標物件的方法執行前後進行干預。

動態代理怎麼用?

首先,準備好我們需要代理的類和介面,因為JDK的動態代理是基於介面實現的,所以被代理的物件必須要有介面

/**
* SaySomething介面
*/
public interface SaySomething {

   public void sayHello();

   public void sayBye();
}
/**
* SaySomething的實現類
*/
public class SaySomethingImpl implements SaySomething {
   @Override
   public void sayHello() {
       System.out.println("Hello World");
  }

   @Override
   public void sayBye() {
       System.out.println("Bye Bye");
  }
}

按照動態代理的用法,需要自定義一個處理器,用來編寫自定義邏輯,實現對被代理物件的增強。

自定義的處理器需要滿足以下要求:

  • 需要實現InvocationHandler,重寫invoke方法,在invoke方法中通過加入自定義邏輯,實現對目標物件的增強。

  • 需要持有一個成員變數,成員變數的是被代理物件的例項,通過構造引數傳入。(用來支援反射呼叫被代理物件的方法)

  • 需要提供一個引數為被代理物件介面類的有參構造。(用來支援反射呼叫被代理物件的方法)

/**
* 自定義的處理器,用來編寫自定義邏輯,實現對被代理物件的增強
*/
public class CustomHandler implements InvocationHandler {

   //需要有一個成員變數,成員變數為被代理物件,通過構造引數傳入,用來支援方法的反射呼叫。
   private SaySomething obj;
   
   //需要有一個有參構造,通過建構函式將被代理物件的例項傳入,用來支援方法的反射呼叫
   public CustomHandler(SaySomething obj) {
       this.obj = obj;
  }

   /**
    * proxy:動態生成的代理類物件com.sun.proxy.$Proxy0
    * method:被代理物件的真實的方法的Method物件
    * args:呼叫方法時的入參
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //目標方法執行前的自定義邏輯處理
       System.out.println("-----before------");

       //執行目標物件的方法,使用反射來執行方法,反射需要傳入目標物件,此時用到了成員變數obj。
       Object result = method.invoke(obj, args);

       //目標方法執行後的自定義邏輯處理
       System.out.println("-----after------");
       return result;
  }
}

這樣我們就完成了自定義處理器的編寫,同時在invoke方法中實現對了代理物件方法的增強,被代理類的所有方法的執行都會執行我們自定義的邏輯。

接下來,需要通過Proxy,newProxyInstance()方法來生成代理物件的例項,並進行方法呼叫測試。

public class JdkProxyTest {
   public static void main(String[] args) {
       //將生成的代理物件的位元組碼檔案 儲存到硬碟
       System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

       //被代理物件的例項
       SaySomething obj = new SaySomethingImpl();
       //通過建構函式,傳入被代理物件的例項,生成處理器的例項
       InvocationHandler handler = new CustomHandler(obj);
       //通過Proxy.newProxyInstance方法,傳入被代理物件Class物件、處理器例項,生成代理物件例項
       SaySomething proxyInstance = (SaySomething) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                                                                          new Class[]{SaySomething.class}, handler);
       //呼叫生成的代理物件的sayHello方法
       proxyInstance.sayHello();
       System.out.println("===================分割線==================");
       //呼叫生成的代理物件的sayBye方法
       proxyInstance.sayBye();
  }
}

 

 

執行main方法,檢視控制檯,大功告成。至此,我們已經完整的完成了一次動態代理的使用。

 

動態代理的原理

生成的proxyInstance物件到底是什麼,為什麼呼叫它的sayHello方法會執行CustomerHandler的invoke方法呢?

直接貼上proxyInstance的位元組碼檔案,我們就會恍然大悟了...

//$Proxy0是SaySomething的實現類,重寫了sayHello和sayBye方法
public final class $Proxy0 extends Proxy implements SaySomething {
   private static Method m1;
   private static Method m3;
   private static Method m2;
   private static Method m4;
   private static Method m0;

   public $Proxy0(InvocationHandler var1) throws {
       super(var1);
  }

   static {
       try {
           m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
           m3 = Class.forName("com.example.demo.hanmc.proxy.jdk.SaySomething").getMethod("sayHello");
           m2 = Class.forName("java.lang.Object").getMethod("toString");
           m4 = Class.forName("com.example.demo.hanmc.proxy.jdk.SaySomething").getMethod("sayBye");
           m0 = Class.forName("java.lang.Object").getMethod("hashCode");
      } catch (NoSuchMethodException var2) {
           throw new NoSuchMethodError(var2.getMessage());
      } catch (ClassNotFoundException var3) {
           throw new NoClassDefFoundError(var3.getMessage());
      }
  }
 
   //實現了介面的sayHello方法,在方法內部呼叫了CustomerHandler的invoke方法,同時傳入了Method物件,
   //所以在CustomerHandler物件中可以通過mathod.invovke方法呼叫SyaSomthing的sayHello方法
   public final void sayHello() throws {
       try {
           //h是父類Proxy中的InvocationHandler物件,其實就是我們自定義的CustomHandler物件
           super.h.invoke(this, m3, (Object[])null);
      } catch (RuntimeException | Error var2) {
           throw var2;
      } catch (Throwable var3) {
           throw new UndeclaredThrowableException(var3);
      }
  }

   public final void sayBye() throws {
       try {
           super.h.invoke(this, m4, (Object[])null);
      } catch (RuntimeException | Error var2) {
           throw var2;
      } catch (Throwable var3) {
           throw new UndeclaredThrowableException(var3);
      }
  }
   public final int hashCode() throws {
      //忽略內容
  }
   public final boolean equals(Object var1) throws {
      //忽略內容
  }
   public final String toString() throws {
      //忽略內容
  }
}

看到了生成的代理物件的位元組碼檔案,是不是一切都明白你了,原理竟然如此簡單^_^

 

本文為個人學習整理,如有描述錯誤或者對相關內容感興趣,歡迎評論或私信交流,一起討論、共同進步。

相關文章