深入動態代理源碼

  • 2019 年 10 月 3 日
  • 筆記

前言:  早期學習了動態代理在實際開發中的使用場景和使用方法,我們也知道了最經典的mybatis的mapper就是採用動態代理來實現的,那麼動態代理的背後是怎樣的原理?為什麼能實現動態代理?為什麼動態代理只可以代理介面,而無法代理普通類?為什麼動態代理需要傳入類的classLoder和介面?帶著這些疑問,我們來開啟本期的主題:探究動態代理的內部原理。

本篇部落格的目錄

一:動態代理的基本使用方法

二:動態代理的內部運行過程

三:幾個相關的問題

四:總結

一:動態代理的基本使用方法

 1.1:簡單例子

首先我們來模擬一個簡單的動態代理的過程:某歌手去參加一個晚會,需要唱歌,他在演奏的過程中需要別人來報幕:演奏開始、演奏結束,每個歌手都遵循這樣的過程,在歌手進行表演的過程,穿插著主持人的開場白和結語,我們來用程式碼模擬這個場景:

1.2:代理介面

首先我們來定義一個singer介面表示我們將要代理的介面:

public interface Singer {      /**       * 表演       * @param soonName       */      public void perform(String soonName);  }

1.3:介面的具體實現類

public class Jay implements Singer {        public void perform(String soonName) {          System.out.println("接下來我為大家唱一首"+soonName);      }  }

1.4:輔助類,用來模擬註冊人的前後台詞

public class Presenter  {        public void before(){          System.out.println("請開始你的表演!");      }        public void after(){          System.out.println("表演結束,大家鼓掌!");      }  }

1.5:具體的代理類

  這裡用proxy.newProxyInstance來創建一個代理類,傳入原始類的類載入器和介面與介面InvocationHandler,同時插入Presenter類的before與after方法,用於前置和後置處理

public class SingerProxy {       private Presenter presenter;       public SingerProxy(Presenter presenter){         this.presenter = presenter;     }      /**       * 獲取代理對象       * @return       */      public Singer getProxy(){            final Singer jay = new Jay();          Singer singerProxy = (Singer)Proxy.newProxyInstance(jay.getClass().getClassLoader(), jay.getClass().getInterfaces(), new InvocationHandler() {」              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                  presenter.before();                  method.invoke(jay, args);                  presenter.after();                  return null;              }          });          return singerProxy;      }  }

1.6:測試類

public class Test {      public static void main(String[] args) {          SingerProxy singerProxy = new SingerProxy(new Presenter());          Singer proxy = singerProxy.getProxy();          proxy.perform("《夜曲》");      }  }

輸出:

 二:動態代理的內部探究

從上面的例子可以看出我們首先生成了一個代理類,然後用代理類來調用原始介面的方法,就可以實現我們的預設的邏輯,在原始介面的前後(或者出現異常的時候)插入我們想要的邏輯,那麼究竟是為什麼呢?

2.1:找到生成的代理類

我們如果需要打開生成的類,首先需要在測試類中添加這行程式碼,設置系統屬性來保存生成的代理類的class文件:

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

2.2:singerProxy類

通過動態代理生成的代理類名為:$Proxy0.class然後通過intelj idea反編譯之後源程式碼是這樣的,這裡主要看到有4個方法,method的m1m2m3m0;分別由反射獲取的equals()、toString()、perform()、hashcode()方法,同時代理類繼承了proxy並且實現了原始Singer介面,重寫了perform()方法,所以這就解釋了為什麼代理類可以調用perform()方法,在perform方法中,又調用了父類中的InvoationHander的invoke方法,並且傳入原始介面中的方法,而invoke方法在我們在創建代理類的時候重寫過,所以就會按照我們自定義的邏輯調用invoke方法,按照順序執行

import java.lang.reflect.InvocationHandler;  import java.lang.reflect.Method;  import java.lang.reflect.Proxy;  import java.lang.reflect.UndeclaredThrowableException;  import main.learn.proxy.Singer;    public final class $Proxy0 extends Proxy implements Singer {      private static Method m1;      private static Method m2;      private static Method m3;      private static Method m0;        public $Proxy0(InvocationHandler var1) throws  {          super(var1);      }        public final boolean equals(Object var1) throws  {          try {              return (Boolean)super.h.invoke(this, m1, new Object[]{var1});          } catch (RuntimeException | Error var3) {              throw var3;          } catch (Throwable var4) {              throw new UndeclaredThrowableException(var4);          }      }        public final String toString() throws  {          try {              return (String)super.h.invoke(this, m2, (Object[])null);          } catch (RuntimeException | Error var2) {              throw var2;          } catch (Throwable var3) {              throw new UndeclaredThrowableException(var3);          }      }        public final void perform(String var1) throws  {          try {              super.h.invoke(this, m3, new Object[]{var1});          } catch (RuntimeException | Error var3) {              throw var3;          } catch (Throwable var4) {              throw new UndeclaredThrowableException(var4);          }      }        public final int hashCode() throws  {          try {              return (Integer)super.h.invoke(this, m0, (Object[])null);          } catch (RuntimeException | Error var2) {              throw var2;          } catch (Throwable var3) {              throw new UndeclaredThrowableException(var3);          }      }        static {          try {              m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));              m2 = Class.forName("java.lang.Object").getMethod("toString");              m3 = Class.forName("main.learn.proxy.Singer").getMethod("perform", Class.forName("java.lang.String"));              m0 = Class.forName("java.lang.Object").getMethod("hashCode");          } catch (NoSuchMethodException var2) {              throw new NoSuchMethodError(var2.getMessage());          } catch (ClassNotFoundException var3) {              throw new NoClassDefFoundError(var3.getMessage());          }      }  }

 2.3:生成代理類的過程

上面我們弄明白了,在代理類中會自動繼承原始介面類並且會調用InvocationHandler將介面類中的方法傳入進去,那麼這個類是如何生成的呢?這就要翻生成代理類的源碼了:

 public static Object newProxyInstance(ClassLoader loader,                                            Class<?>[] interfaces,                                            InvocationHandler h)          throws IllegalArgumentException      {          Objects.requireNonNull(h);            final Class<?>[] intfs = interfaces.clone();          final SecurityManager sm = System.getSecurityManager();          if (sm != null) {              checkProxyAccess(Reflection.getCallerClass(), loader, intfs);          }            /*           * Look up or generate the designated proxy class.           */          Class<?> cl = getProxyClass0(loader, intfs);            /*           * Invoke its constructor with the designated invocation handler.           */          try {              if (sm != null) {                  checkNewProxyPermission(Reflection.getCallerClass(), cl);              }                final Constructor<?> cons = cl.getConstructor(constructorParams);              final InvocationHandler ih = h;              if (!Modifier.isPublic(cl.getModifiers())) {                  AccessController.doPrivileged(new PrivilegedAction<Void>() {                      public Void run() {                          cons.setAccessible(true);                          return null;                      }                  });              }              return cons.newInstance(new Object[]{h});          } catch (IllegalAccessException|InstantiationException e) {              throw new InternalError(e.toString(), e);          } catch (InvocationTargetException e) {              Throwable t = e.getCause();              if (t instanceof RuntimeException) {                  throw (RuntimeException) t;              } else {                  throw new InternalError(t.toString(), t);              }          } catch (NoSuchMethodException e) {              throw new InternalError(e.toString(), e);          }      }

上面的程式碼由下來解釋:

2.3.1:安全管理器檢查與代理許可權檢查

2.3.2:判斷介面的長度是否大於65535,大於不可往下進行。然後從WeakCache快取中獲取代理類,如果找不到則通過proxyClassFactory生成代理類

2.3.2.1:生成代理類的class過程如下:

1⃣️驗證傳入的interface是否可被傳入的classloader裝載

2⃣️驗證傳入的是否是一個介面,如果不是一個介面,直接拋出IllegalArgumentException異常

3⃣️判斷傳入的是否重複,這裡是通過一個IdentityHashMap往裡面put介面的class類,如果返回值不為null表示這個介面已經註冊過了(如果第一次put會返回null,按照傳統的做法是先get是否為null,如果不為null再put,這行程式碼很妙省去了這個步驟),IdentityHashMap也是map的一種,不過它與我們普通的HashMap最大的不同在於它不會通過equals方法和hashCode方法去判斷key是否重複,而是通過==運算符

4⃣️拼接代理類的名字固定為:com.sun.proxy.$Proxy+原子自增序號,為了防止並發調用,在生成代理類名字的時候,採用了AtomicLong的getAndIncrement方法去原子遞增代理類的序列號,這個方法是原子的,所以不會產生並發問題。這裡也就是我們為什麼看到最後的代理類是$Proxy0的原因(生成的代理類的序號是從0開始的)

5⃣️調用ProxyGenerator.generateProxyClass方法來生成代理的class類(過程較為複雜、通過一些jvm指令去生成位元組碼,包括遍歷方法類型、返回值、參數類型等)

6⃣️通過defineClass將上一步產生的class位元組碼生成class文件,該方法也是一個native方法,需要傳入類的classloader來進行裝載生成的類位元組碼進而生成class

2.3.3: 通過反射獲取構造器constractor創建一個反射實例,這個過程進行了強制構造器的private私有化反射

三:幾個相關的問題

 3.1:為什麼動態代理需要傳入classLoader?

  主要原因有以下幾個:

1⃣️需要校驗傳入的介面是否可被當前的類載入器載入,假如無法載入,證明這個介面與類載入器不是同一個,按照雙親委派模型,那麼類載入層次就被破壞了

2⃣️需要類載入器去根據生成的類的位元組碼去通過defineClass方法生成類的class文件,也就是說沒有類載入的話是無法生成代理類的

3.2:為什麼動態代理需要傳入介面和只能代理介面?

 需要介面去通過ProxyGenerator類來生成代理類的位元組碼,在生成的過程中,需要遍歷介面中的方法,包括方法簽名、參數類型、返回類型從而生成新的代理類,而代理類也需要繼承原始介面的方法,所以介面必須要傳

3.3:如果同一個介面創建多次代理會怎麼辦?

在獲取代理對象的時候首先會從快取(WeakCache)裡面取,如果取不到才會通過代理工廠去創建,所以如果創建多個代理類的話,最終只會產生一個代理類

四:總結

    本篇部落格通過一個動態代理的實際例子來分析了具體創建動態代理的過程,分析了動態代理的內部運行原理,以及分析了生成的代理類的源碼,動態代理在我們的開發過程中可謂是非常常見,比如最典型的mybatis的mapper代理原理、spring的aop實現原理,進行前置增強、後置增強等就是藉助了動態代理。理解了動態代理能幫助我們進一步理解一些源碼,或許在以後的某些特定場景我們也可以採用動態代理來造出合適的輪子。