設計模式 – 動態代理原理及模仿JDK Proxy 寫一個屬於自己的動態代理

  • 2019 年 10 月 15 日
  • 筆記

本篇文章程式碼內容較多,講的可能會有些粗糙,大家可以選擇性閱讀。

本篇文章的目的是簡單的分析動態代理的原理及模仿JDK Proxy手寫一個動態代理以及對幾種代理做一個總結。

對於代理模式的介紹和講解,網上已經有很多優質的文章,我這裡就不會再過多的介紹了,這裡推薦幾篇優質的文章作為參考:

  1. 給女朋友講解什麼是代理模式
  2. 輕鬆學,Java 中的代理模式及動態代理

另外,我的 github 倉庫對應目錄中也有相關的基礎示例程式碼:https://github.com/eamonzzz/java-advanced…

JDK Proxy 動態代理

動態代理的概念這裡就不再闡述了;動態代理相對於靜態代理來說,它的功能更加強大,隨著業務的擴展,適應性更強。

在說動態代理原理之前,我們還是來看看動態代理的一般使用。

使用

本篇文章的使用示例,是以一個最為簡單的代理模式的程式碼為例,相信大家在學習或了解代理模式的時候都有看到或者接觸過這些程式碼。

  1. 先創建一個Subject主體抽象介面:
/**   * @author eamon.zhang   * @date 2019-10-09 下午4:06   */  public interface Subject {      void request();  }
  1. 再創建一個真實的主體RealSubject來處理我們的真實的邏輯:
/**   * @author eamon.zhang   * @date 2019-10-09 下午4:06   */  public class RealSubject implements Subject {      @Override      public void request() {          System.out.println("真實處理邏輯!");      }  }
  1. 在不修改RealSubject類的情況下,如果我們要實現在執行RealSubject類中request()方法之前或之後執行一段邏輯的話,該怎麼實現呢?這就得創建一個代理類,來達到增強原有程式碼的目的。所以現在創建一個 JDK 動態代理類 RealSubjectJDKDynamicProxy
/**   * @author eamon.zhang   * @date 2019-10-09 下午4:08   */  public class RealSubjectJDKDynamicProxy implements InvocationHandler {      // 被代理對象的引用      private Object target;      // 通過構造器傳入對象引用      public RealSubjectJDKDynamicProxy(Object target) {          this.target = target;      }      // 獲得 JDK 動態代理創建的代理對象      public Object getInstance() {          Class<?> clazz = target.getClass();          return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);      }        @Override      public Object invoke(Object o, Method method, Object[] objects) throws Throwable {          before();          // 代理執行被代理對象的相應方法          Object invoke = method.invoke(target, objects);          after();          return invoke;      }        private void before() {          System.out.println("前置增強!");      }        private void after() {          System.out.println("後置增強!");      }  }
  1. 測試程式碼:
@Test  public void test(){      Subject realSubject = new RealSubject();      RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);      Subject instance = (Subject) proxy.getInstance();      instance.request();      System.out.println(realSubject.getClass());      System.out.println(instance.getClass());  }
  1. 測試結果
前置增強!  真實處理邏輯!  後置增強!  class com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject  class com.sun.proxy.$Proxy8

從結果來看,上面的程式碼已經達到了我們的增強的目的。

原理分析

不知道大家有沒有注意到上面的測試程式碼中,最後兩行我將代理之前和代理之後的class對象給列印了出來;並且發現,這兩個對象並非同一個,最重要的是,經過代理之後的對象的Subjectcom.sun.proxy.$Proxy8而不是com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject或者com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject,那麼這個instance到底是從哪裡來?帶著這個疑問,我們來通過 JDK Proxy 源碼來分析一下:

我們跟進RealSubjectJDKDynamicProxy類中的Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);方法:

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 {          ...          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);      }     ...  }

發現在newProxyInstance方法中調用了getProxyClass0(loader, intfs)方法,我們跟進去這個方法看一下:

/**   * Generate a proxy class.  Must call the checkProxyAccess method   * to perform permission checks before calling this.   */  private static Class<?> getProxyClass0(ClassLoader loader,                                         Class<?>... interfaces) {      if (interfaces.length > 65535) {          throw new IllegalArgumentException("interface limit exceeded");      }        // If the proxy class defined by the given loader implementing      // the given interfaces exists, this will simply return the cached copy;      // otherwise, it will create the proxy class via the ProxyClassFactory      return proxyClassCache.get(loader, interfaces);  }

程式碼邏輯很簡單,做了兩個事情:

  1. 檢查類的介面數量是否超過65535,介面個數用 2 個 byte 存儲,最大支援 65535 個。
  2. proxyClassCache 快取中去取,從注釋中可知,如果快取沒有就會調用ProxyClassFactory去創建。

我們現在就來簡單分析一下proxyClassCache.get(loader, interfaces)裡面的邏輯:

public V get(K key, P parameter) {      Objects.requireNonNull(parameter);        expungeStaleEntries();        Object cacheKey = CacheKey.valueOf(key, refQueue);        // lazily install the 2nd level valuesMap for the particular cacheKey      ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);      if (valuesMap == null) {          ConcurrentMap<Object, Supplier<V>> oldValuesMap              = map.putIfAbsent(cacheKey,                                valuesMap = new ConcurrentHashMap<>());          if (oldValuesMap != null) {              valuesMap = oldValuesMap;          }      }        // create subKey and retrieve the possible Supplier<V> stored by that      // subKey from valuesMap      Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));      Supplier<V> supplier = valuesMap.get(subKey);      Factory factory = null;      // 這裡是一個 while(true)      while (true) {          // 如果創建 factory(這裡指ProxyClassFactory) 成功,就調用 factory.get()方法          if (supplier != null) {              // supplier might be a Factory or a CacheValue<V> instance              //              V value = supplier.get();              if (value != null) {                  return value;              }          }          // else no supplier in cache          // or a supplier that returned null (could be a cleared CacheValue          // or a Factory that wasn't successful in installing the CacheValue)            // lazily construct a Factory          if (factory == null) {              factory = new Factory(key, parameter, subKey, valuesMap);          }            if (supplier == null) {              supplier = valuesMap.putIfAbsent(subKey, factory);              if (supplier == null) {                  // successfully installed Factory                  supplier = factory;              }              // else retry with winning supplier          } else {              if (valuesMap.replace(subKey, supplier, factory)) {                  // successfully replaced                  // cleared CacheEntry / unsuccessful Factory                  // with our Factory                  supplier = factory;              } else {                  // retry with current supplier                  supplier = valuesMap.get(subKey);              }          }      }  }

程式碼可能有點長,其實邏輯就是為了調用ProxyClassFactory.apply()去生成代理類。我們從while(true)處將程式碼分割成兩個部分來看:

  1. 前半部分,是從快取中去取ProxyClassFactory,如果創建成功了,則可以取到(快取中的 key 這裡不分析了)
  2. 然後看 while(true) 程式碼塊中的邏輯,if (supplier != null)這個判斷,如果快取中創建了ProxyClassFactory就會執行supplier.get()並且終止循環;如果沒有,則會執行new Factory(key, parameter, subKey, valuesMap);去創建factory,然後將其放入快取supplier中,然後繼續循環,這個時候就會執行if (supplier != null)程式碼塊中的邏輯,我們再來分析一下這個程式碼塊裡面的程式碼:
if (supplier != null) {      // supplier might be a Factory or a CacheValue<V> instance      V value = supplier.get();      if (value != null) {          return value;      }  }

跟進 supplier.get()方法去看一下,我們從上面的分析可以知道這裡的supplier其實就是一個Factory,所以我們看Factory的實現,重點看get()方法:

private final class Factory implements Supplier<V> {         ...          @Override          public synchronized V get() { // serialize access              ...              // create new value              V value = null;              try {                  value = Objects.requireNonNull(valueFactory.apply(key, parameter));              } finally {                  if (value == null) { // remove us on failure                      valuesMap.remove(subKey, this);                  }              }              // the only path to reach here is with non-null value              assert value != null;                // wrap value with CacheValue (WeakReference)              CacheValue<V> cacheValue = new CacheValue<>(value);                // put into reverseMap              reverseMap.put(cacheValue, Boolean.TRUE);                // try replacing us with CacheValue (this should always succeed)              if (!valuesMap.replace(subKey, this, cacheValue)) {                  throw new AssertionError("Should not reach here");              }                // successfully replaced us with new CacheValue -> return the value              // wrapped by it              return value;          }      }

我們注意到,程式碼中的重點是在Objects.requireNonNull(valueFactory.apply(key, parameter));,那這個程式碼中的valueFactory是什麼呢?我們在Proxy中,來看一下proxyClassCache的定義

 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>          proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

WeakCache中第二個參數是new ProxyClassFactory() ,再來看一下對應的構造器:

public WeakCache(BiFunction<K, P, ?> subKeyFactory,                   BiFunction<K, P, V> valueFactory) {      this.subKeyFactory = Objects.requireNonNull(subKeyFactory);      this.valueFactory = Objects.requireNonNull(valueFactory);  }

這時候明白了嗎?其實 valueFactory就是ProxyClassFactory()

明白了這一點,就來分析一下valueFactory.apply(key, parameter)到底執行了什麼?我們直接看ProxyClassFactory的程式碼

private static final class ProxyClassFactory      implements BiFunction<ClassLoader, Class<?>[], Class<?>>  {      // prefix for all proxy class names      private static final String proxyClassNamePrefix = "$Proxy";        // next number to use for generation of unique proxy class names      private static final AtomicLong nextUniqueNumber = new AtomicLong();        @Override      public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {            ...            /*           * Generate the specified proxy class.           */          byte[] proxyClassFile = ProxyGenerator.generateProxyClass(              proxyName, interfaces, accessFlags);          try {              return defineClass0(loader, proxyName,                                  proxyClassFile, 0, proxyClassFile.length);          } catch (ClassFormatError e) {              throw new IllegalArgumentException(e.toString());          }      }  }

縱觀全覽,不難分析,程式碼中其實就是在創建$Proxy這個中間代理類,其中byte[] proxyClassFile是程式碼塊中組裝完成之後的類的位元組碼文件數據,通過ProxyGenerator.generateProxyClass()生成;然後通過classloader動態載入位元組碼,並生成動態代理類的Class實例,並返回。

我們再跟進ProxyGenerator.generateProxyClass()方法,來看看在生成代理類過程中的處理邏輯,看重點程式碼:。

 public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {      ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);      final byte[] var4 = var3.generateClassFile();      ...        return var4;  }

可以發現其程式碼調用了var3.generateClassFile()去生成Class文件,所以我們跟進generateClassFile()方法,看重點內容:

private byte[] generateClassFile() {      this.addProxyMethod(hashCodeMethod, Object.class);      this.addProxyMethod(equalsMethod, Object.class);      this.addProxyMethod(toStringMethod, Object.class);      Class[] var1 = this.interfaces;      int var2 = var1.length;        int var3;      Class var4;      for(var3 = 0; var3 < var2; ++var3) {          var4 = var1[var3];          Method[] var5 = var4.getMethods();          int var6 = var5.length;            for(int var7 = 0; var7 < var6; ++var7) {              Method var8 = var5[var7];              this.addProxyMethod(var8, var4);          }      }      ...  }

程式碼有點長,這裡就不全部展開了,有興趣的朋友可以跟進去詳細看一下。從程式碼中我們大致可以看出來,在生成代理類的過程中,還添加了hashCode、equals、toString這三個方法,然後後面的邏輯就是將代理對象中的所有介面進行迭代,將其所有的方法都重新生成代理方法;然後生成位元組碼。

最後再將代理類載入到JVM中。

看一下JDK Proxy生成的代理類$Proxy

我們通過下面這段程式碼,將$Proxy文件輸出到文件:

@Test  public void test1(){      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");      RealSubject realSubject = new RealSubject();        RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);      Subject instance = (Subject) proxy.getInstance();      try {          byte[] proxychar=  ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Subject.class});          OutputStream outputStream = new FileOutputStream("/Users/eamon.zhang/IdeaProjects/own/java-advanced/01.DesignPatterns/design-patterns/"+instance.getClass().getSimpleName()+".class");          outputStream.write(proxychar);          outputStream.flush();          outputStream.close();      } catch (IOException e) {          e.printStackTrace();      }      instance.request();      System.out.println(instance.getClass());    }

通過IDEA工具查看$Proxy0,印證一下我們之前的分析:

public final class $Proxy0 extends Proxy implements Subject {      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 request() throws  {          try {              super.h.invoke(this, m3, (Object[])null);          } catch (RuntimeException | Error var2) {              throw var2;          } catch (Throwable var3) {              throw new UndeclaredThrowableException(var3);          }      }        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("com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject").getMethod("request");              m0 = Class.forName("java.lang.Object").getMethod("hashCode");          } catch (NoSuchMethodException var2) {              throw new NoSuchMethodError(var2.getMessage());          } catch (ClassNotFoundException var3) {              throw new NoClassDefFoundError(var3.getMessage());          }      }  }

總結

總結一下JDK Proxy的實現步驟:

  1. 拿到被代理對象的引用,並獲取它的所有介面(通過反射)
  2. JDK Proxy 類重新生成一個新的類,同時新的類要實現被代理類的所有實現的介面,還有hashCode、equals、toString這三個方法
  3. 動態生成Java程式碼,把新加的業務邏輯方法由一定的邏輯程式碼去調用(在程式碼中體現)
  4. 編譯新生成的Java程式碼的 .class文件
  5. 重新載入到JVM中運行

模擬手寫 JDK Proxy

在明白了上面的原理之後,其實我們就可以嘗試手動來實現一個JDK Proxy

我們參照JDK Proxy實現原理分析一下需要動手編寫哪些內容:

  • 首先我們需要有一個代理類MimeProxy
  • 然後從代理類出發,需要有newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)這一個方法,方法參數為:(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h),所以我們需要創建一個ClassLoaderInvocationHandler;

下面來一步一步創建:

  1. 先創建MimeClassLoader類,繼承自ClassLoader,並重寫findClass()方法:
/**   * @author eamon.zhang   * @date 2019-10-10 下午2:47   */  public class MimeClassLoader extends ClassLoader {      private Object target;        public MimeClassLoader(Object target) {          this.target = target;      }        @Override      protected Class<?> findClass(String name) throws ClassNotFoundException {          String classname = target.getClass().getPackage().getName() + "." + name;          String filePath = MimeClassLoader.class.getResource("").getPath() + name + ".class";          try {              URI uri = new URI("file:///" + filePath);              Path path = Paths.get(uri);              File file = path.toFile();              if (file.exists()) {                  byte[] fileBytes = Files.readAllBytes(path);                  return defineClass(classname, fileBytes, 0, fileBytes.length);              }            } catch (Exception e) {              e.printStackTrace();          }            return null;      }  }
  1. 創建 MimeInvocationHandler 類:
/**   * @author eamon.zhang   * @date 2019-10-10 下午2:46   */  public interface MimeInvocationHandler {      public Object invoke(Object proxy, Method method, Object[] args)              throws Throwable;  }
  1. 創建MimeProxy類,這個類就是用來組裝成代理類,並載入到JVM,然後返回這個代理對象:
/**   * @author eamon.zhang   * @date 2019-10-10 下午3:08   */  public class MimeProxy {      private static final String ln = "rn";      private static final String semi = ";";        private static Map<Class, Class> mappings = new HashMap<Class, Class>();        static {          mappings.put(int.class, Integer.class);      }        public static Object newProxyInstance(MimeClassLoader loader, Class<?>[] interfaces, MimeInvocationHandler h)              throws IllegalArgumentException {          try {              // 1. 動態生成 .java 文件              String src = generateSrc(interfaces);  //            System.out.println(src);              // 2. java 文件輸出到磁碟              String filePath = MimeProxy.class.getResource("").getPath();  //            System.out.println(filePath);              File f = new File(filePath + "$Proxy8.java");  //            f.deleteOnExit();              FileWriter fw = new FileWriter(f);              fw.write(src);              fw.flush();              fw.close();              // 3. 把 java 文件編譯成 .class 文件              JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();              StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);              Iterable<? extends JavaFileObject> iterable = sjfm.getJavaFileObjects(f);              JavaCompiler.CompilationTask task = compiler.getTask(null, sjfm, null, null, null, iterable);              task.call();              sjfm.close();              // 4. 把.class 文件載入到jvm              Class<?> proxyClass = loader.findClass("$Proxy8");              Constructor<?> c = proxyClass.getConstructor(MimeInvocationHandler.class);              f.delete();                // 5. 返回位元組碼重組以後的新的代理對象              return c.newInstance(h);          } catch (Exception e) {              e.printStackTrace();          }            return null;        }        /**       * 生成 代理類       *       * @param interfaces       * @return       */      private static String generateSrc(Class<?>[] interfaces) {          // 這裡使用 StringBuffer 執行緒安全          StringBuffer sb = new StringBuffer();          sb.append("package ").append(interfaces[0].getPackage().getName()).append(semi).append(ln);          sb.append("import ").append(interfaces[0].getName()).append(semi).append(ln);          sb.append("import java.lang.reflect.*;").append(ln);          sb.append("import ").append(interfaces[0].getPackage().getName()).append(".mimeproxy.MimeInvocationHandler;").append(ln);          sb.append("public class $Proxy8 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln);          sb.append("MimeInvocationHandler h;" + ln);          sb.append("public $Proxy8(MimeInvocationHandler h) {").append(ln);          sb.append("this.h = h;").append(ln);          sb.append("}").append(ln);            for (Method method : interfaces[0].getMethods()) {              Class<?>[] params = method.getParameterTypes();                StringBuffer paramNames = new StringBuffer();              StringBuffer paramValues = new StringBuffer();              StringBuffer paramClasses = new StringBuffer();                for (Class<?> clazz : params) {                  String type = clazz.getName();                  String paramName = toLowerFirstCase(clazz.getSimpleName());                    paramNames.append(type).append(" ").append(paramName);                    paramValues.append(paramName);                  paramClasses.append(clazz.getName()).append(".class");                    for (int i = 0; i < params.length; i++) {                      paramNames.append(",");                      paramValues.append(",");                      paramClasses.append(",");                  }              }                sb.append("public ").append(method.getReturnType().getName()).append(" ").append(method.getName())                      .append("(").append(paramNames.toString()).append(") {").append(ln);              sb.append("try {").append(ln);              // Method m = interfaces[0].getName().class.getMethod(method.getName()),new Class[]{paramClasses.toString()});              sb.append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod("")                      .append(method.getName()).append("", new Class[]{").append(paramClasses.toString()).append("});")                      .append(ln);              // return this.h.invoke(this, m, new Object[]{paramValues}, method.getReturnType());              sb.append(hasReturnValue(method.getReturnType()) ? "return " : "")                      .append(getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", method.getReturnType()))                      .append(";")                      .append(ln);              sb.append("} catch (Error _ex) {}").append(ln);              sb.append("catch (Throwable e) {").append(ln);              sb.append("throw new UndeclaredThrowableException(e);").append(ln);              sb.append("}");              sb.append(getReturnEmptyCode(method.getReturnType())).append(ln);              sb.append("}");            }          sb.append("}").append(ln);            return sb.toString();      }        /**       * 獲取返回值類型       *       * @param returnClass       * @return       */      private static String getReturnEmptyCode(Class<?> returnClass) {          if (mappings.containsKey(returnClass)) {              return "return 0;";          } else if (returnClass == void.class) {              return "";          } else {              return "return null;";          }      }        /**       * 拼接 invocationHandler 執行程式碼       *       * @param code       * @param returnClass       * @return       */      private static String getCaseCode(String code, Class<?> returnClass) {          if (mappings.containsKey(returnClass)) {              return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";          }          return code;      }        /**       * 判斷是否有返回值       *       * @param clazz       * @return       */      private static boolean hasReturnValue(Class<?> clazz) {          return clazz != void.class;      }        /**       * 首字母轉換為小寫       *       * @param src       * @return       */      private static String toLowerFirstCase(String src) {          char[] chars = src.toCharArray();          chars[0] += 32;          return String.valueOf(chars);      }  }

這樣子就編寫了一個屬於自己的動態代理,當然,代理方法還不完善,只是針對本示例進行了編寫,有興趣的朋友可以試試將其改為更通用的程式碼。

CGlib 動態代理

下面來看一下 CGlib 的動態代理的使用

使用

先創建RealSubject類,注意,這個類不用實現任何介面:

/**   * @author eamon.zhang   * @date 2019-10-09 下午4:22   */  public class RealSubject {      public void request(){          System.out.println("真實處理邏輯!");      }  }

然後創建RealSubjectCglibDynamicProxy 代理類,它必須實現MethodInterceptor介面:

/**   * @author eamon.zhang   * @date 2019-10-09 下午4:23   */  public class RealSubjectCglibDynamicProxy implements MethodInterceptor {        public Object getInstance(Class<?> clazz) {          // 通過CGLIB動態代理獲取代理對象的過程          Enhancer enhancer = new Enhancer();          // 要把哪個設置為即將生成的新類父類          enhancer.setSuperclass(clazz);          // 設置回調對象          enhancer.setCallback(this);          // 創建代理對象          return enhancer.create();      }        @Override      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {          before();          Object invokeSuper = proxy.invokeSuper(obj, args);          after();          return invokeSuper;      }        private void before() {          System.out.println("前置增強!");      }        private void after() {          System.out.println("後置增強!");      }  }

這樣,一個簡單的CGlib動態代理實現就完成了,我們現在來創建測試程式碼:

@Test  public void test(){      RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();      RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);      instance.request();  }

測試結果:

前置增強!  真實處理邏輯!  後置增強!

原理分析

不管是JDK Proxy還是CGlib,他們的核心內容都是去創建代理類,所以我們只要去了解其創建代理類的過程就 OK 了。

從上面簡單的使用示例可以知道,要使用 CGlib 動態代理,代理類必須要實現MethodInterceptor(方法攔截器),MethodInterceptor介面源碼如下:

/**   * General-purpose {@link Enhancer} callback which provides for "around advice".   * @author Juozas Baliuka <a href="mailto:[email protected]">[email protected]</a>   * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $   */  public interface MethodInterceptor  extends Callback  {      /**       * All generated proxied methods call this method instead of the original method.       * The original method may either be invoked by normal reflection using the Method object,       * or by using the MethodProxy (faster).       * @param obj "this", the enhanced object       * @param method intercepted Method       * @param args argument array; primitive types are wrapped       * @param proxy used to invoke super (non-intercepted method); may be called       * as many times as needed       * @throws Throwable any exception may be thrown; if so, super method will not be invoked       * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.       * @see MethodProxy       */      public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,                                 MethodProxy proxy) throws Throwable;    }

介面中只有一個intercept方法,其中傳入的參數:

  1. obj 表示增強的對象,即實現這個介面類的一個對象;
  2. method 表示要被攔截的方法;
  3. args 表示方法參數;
  4. proxy 表示要觸發父類的方法對象;

在創建代理對象的邏輯getInstance(Class<?> clazz)中,調用了enhancer.create()方法,我們跟進源碼看一下:

/**   * Generate a new class if necessary and uses the specified   * callbacks (if any) to create a new object instance.   * Uses the no-arg constructor of the superclass.   * @return a new instance   */  public Object create() {      classOnly = false;      argumentTypes = null;      return createHelper();  }

源碼注釋內容翻譯:如有必要,生成一個新類,並使用指定的回調(如果有)來創建一個新的對象實例。 使用的父類的參數的構造方法來實例化父類。

它的核心內容是在createHelper();方法中:

private Object createHelper() {      preValidate();      Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,              ReflectUtils.getNames(interfaces),              filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),              callbackTypes,              useFactory,              interceptDuringConstruction,              serialVersionUID);      this.currentKey = key;      Object result = super.create(key);      return result;  }

preValidate()方法的作用是,前置校驗,校驗callbackTypes、filter是否為空,以及為空時的處理。

然後通過KEY_FACTORY.newInstance()方法創建EnhancerKey對象,並將其作為super.create(key)方法的參數傳入,我們來看一下這個create()方法,發現它是Enhancer類的父類AbstractClassGenerator中的一個方法:

protected Object create(Object key) {      try {          ClassLoader loader = getClassLoader();          Map<ClassLoader, ClassLoaderData> cache = CACHE;          ClassLoaderData data = cache.get(loader);          if (data == null) {              synchronized (AbstractClassGenerator.class) {                  cache = CACHE;                  data = cache.get(loader);                  if (data == null) {                      Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);                      data = new ClassLoaderData(loader);                      newCache.put(loader, data);                      CACHE = newCache;                  }              }          }          this.key = key;          Object obj = data.get(this, getUseCache());          if (obj instanceof Class) {              return firstInstance((Class) obj);          }          return nextInstance(obj);      } catch (RuntimeException e) {          throw e;      } catch (Error e) {          throw e;      } catch (Exception e) {          throw new CodeGenerationException(e);      }  }

這個方法在最後調用了 nextInstance(obj) 方法,它對應的實現,是在Enhancer類中:

protected Object nextInstance(Object instance) {      EnhancerFactoryData data = (EnhancerFactoryData) instance;        if (classOnly) {          return data.generatedClass;      }        Class[] argumentTypes = this.argumentTypes;      Object[] arguments = this.arguments;      if (argumentTypes == null) {          argumentTypes = Constants.EMPTY_CLASS_ARRAY;          arguments = null;      }      return data.newInstance(argumentTypes, arguments, callbacks);  }

這裡又調用了data.newInstance(argumentTypes, arguments, callbacks)方法,第一個參數為代理對象的構造器類型,第二個為代理對象構造方法參數,第三個為對應回調對象。源碼如下:

public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {      setThreadCallbacks(callbacks);      try {          // Explicit reference equality is added here just in case Arrays.equals does not have one          if (primaryConstructorArgTypes == argumentTypes ||                  Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {              // If we have relevant Constructor instance at hand, just call it              // This skips "get constructors" machinery              return ReflectUtils.newInstance(primaryConstructor, arguments);          }          // Take a slow path if observing unexpected argument types          return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);      } finally {          // clear thread callbacks to allow them to be gc'd          setThreadCallbacks(null);      }    }

我們發現這裡面的邏輯的意思就是,根據傳進來的參數,通過反射來生成對象,我們可以利用cglib的代理類可以將記憶體中的 class 文件寫入本地磁碟:

@Test  public void test1(){      //利用 cglib 的代理類可以將記憶體中的 class 文件寫入本地磁碟      System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/eamon.zhang/Documents/cglib");      RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();      RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);      instance.request();  }

執行之後,在對應的目錄中可以看到生成了下圖中這三個.class文件:

通過調試跟蹤,我們發現 RealSubject$$EnhancerByCGLIB$$5389cdca 就是 CGLib生成的代理類,繼承了 RealSubject 類。通過IDEA查看該源碼:

public class RealSubject$$EnhancerByCGLIB$$5389cdca extends RealSubject implements Factory {      ...      static void CGLIB$STATICHOOK1() {          CGLIB$THREAD_CALLBACKS = new ThreadLocal();          CGLIB$emptyArgs = new Object[0];          Class var0 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject$$EnhancerByCGLIB$$5389cdca");          Class var1;          CGLIB$request$0$Method = ReflectUtils.findMethods(new String[]{"request", "()V"}, (var1 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject")).getDeclaredMethods())[0];          CGLIB$request$0$Proxy = MethodProxy.create(var1, var0, "()V", "request", "CGLIB$request$0");          Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());          CGLIB$equals$1$Method = var10000[0];          CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");          CGLIB$toString$2$Method = var10000[1];          CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");          CGLIB$hashCode$3$Method = var10000[2];          CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");          CGLIB$clone$4$Method = var10000[3];          CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");      }        final void CGLIB$request$0() {          super.request();      }        public final void request() {          MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;          if (var10000 == null) {              CGLIB$BIND_CALLBACKS(this);              var10000 = this.CGLIB$CALLBACK_0;          }            if (var10000 != null) {              var10000.intercept(this, CGLIB$request$0$Method, CGLIB$emptyArgs, CGLIB$request$0$Proxy);          } else {              super.request();          }      }      ...  }

我們通過代理類的源碼可以看到,代理類會獲得所有在父類繼承來的方法,並且會有 MethodProxy 與之對應,比如 Method CGLIB$request$0$MethodMethodProxy CGLIB$request$0$Proxy這些方法在代理類的 reuqest()中都有調用。

調用過程: 代理對象調用 this.request()方法 -> 調用攔截器 -> methodProxy.invokeSuper -> CGLIB$request$0() -> 被代理對象 request()方法。 此時,我們發現攔截器 MethodInterceptor 中就是由 MethodProxyinvokeSuper 方法調用代理方法的。

MethodProxy 非常關鍵,我們分析一下它具體做了什麼:

public class MethodProxy {      private Signature sig1;      private Signature sig2;      private CreateInfo createInfo;        private final Object initLock = new Object();      private volatile FastClassInfo fastClassInfo;        /**       * For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class       * for similar functionality.       */      public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {          MethodProxy proxy = new MethodProxy();          proxy.sig1 = new Signature(name1, desc);          proxy.sig2 = new Signature(name2, desc);          proxy.createInfo = new CreateInfo(c1, c2);          return proxy;      }      ...        private static class CreateInfo      {          Class c1;          Class c2;          NamingPolicy namingPolicy;          GeneratorStrategy strategy;          boolean attemptLoad;            public CreateInfo(Class c1, Class c2)          {              this.c1 = c1;              this.c2 = c2;              AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();              if (fromEnhancer != null) {                  namingPolicy = fromEnhancer.getNamingPolicy();                  strategy = fromEnhancer.getStrategy();                  attemptLoad = fromEnhancer.getAttemptLoad();              }          }      }      ...

繼續看invokeSuper()方法:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {      try {          init();          FastClassInfo fci = fastClassInfo;          return fci.f2.invoke(fci.i2, obj, args);      } catch (InvocationTargetException e) {          throw e.getTargetException();      }  }    private static class FastClassInfo  {      FastClass f1;      FastClass f2;      int i1;      int i2;  }

上面程式碼調用過程就是獲取到代理類對應的 FastClass,並執行了代理方法。還記得之前生成三個 class 文件嗎?RealSubject$$EnhancerByCGLIB$$5389cdca$$FastClassByCGLIB$$57b94d72.class就是代理類的 FastClassRealSubject$$FastClassByCGLIB$$ed23432.class就是被代理類的FastClass

CGLib 動態代理執行代理方法效率之所以比 JDK 的高是因為 Cglib 採用了 FastClass 機 制,它的原理簡單來說就是:

  • 為代理類和被代理類各生成一個 Class,這個 Class 會為代理類或被代理類的方法分配一個 index(int 類型)。這個 index 當做一個入參,FastClass就可以直接定位要調用的方法直接進行調用,這樣省去了反射調用,所以調用效率比 JDK動態代理通過反射調用高。

至此,Cglib 動態代理的原理我們就基本搞清楚了,如果對程式碼細節有興趣的小夥伴可以再自行深入研究。

JDK Proxy 與 CGlib 比較

  1. JDK 動態代理是實現了被代理對象的介面,CGLib繼承了被代理對象。
  2. JDKCGLib 都是在運行期生成位元組碼,JDK 是直接寫 Class 位元組碼,CGLib 使用 ASM 框架寫 Class 位元組碼,Cglib 代理實現更複雜,生成代理類JDK 效率低。
  3. JDK 調用代理方法,是通過反射機制調用,CGLib 是通過 FastClass 機制直接調用方法, CGLib 執行效率 更高

代理模式與 Spring

Spring 中的代理選擇原則

  1. Bean 有實現介面時,Spring 就會用 JDK 的動態代理
  2. Bean 沒有實現介面時,Spring 選擇 CGLib
  3. Spring 可以通過配置強制使用 CGLib,只需在 Spring 的配置文件中加入如下程式碼:
<aop:aspectj-autoproxy proxy-target-class="true"/>

參考資料:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html

總結

靜態代理和動態的本質區別

  1. 靜態代理只能通過手動完成代理操作,如果被代理類增加新的方法,代理類需要同步新增違背開閉原則
  2. 動態代理採用在運行時動態生成程式碼的方式,取消了對被代理類的擴展限制,遵循開閉原則
  3. 若動態代理要對目標類的增強邏輯擴展,結合策略模式,只需要新增策略類便可完成,無需修改代理類的程式碼。

代理模式的優缺點

優點

  1. 代理模式能將代理對象與真實被調用的目標對象分離。
  2. 一定程度上降低了系統的耦合度,擴展性好。
  3. 可以起到保護目標對象的作用。
  4. 可以對目標對象的功能增強

缺點

  1. 代理模式會造成系統設計中類的數量增加。
  2. 在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢。
  3. 增加了系統的複雜度。

本篇文章的源碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/main/java/com/eamon/javadesignpatterns/proxy

測試類源碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/test/java/com/eamon/javadesignpatterns/proxy


歡迎大家 star 源碼,共同進步,我會按照 git 上的大綱在學習的同時,記錄文章與源碼~

部落客剛開始寫部落格不久,文中若有錯誤或者有任何的建議,請在留言中指出,向大家學習~

本文由部落格一文多發平台 OpenWrite 發布!