深入淺出動態代理

  • 2019 年 11 月 6 日
  • 筆記

代理模式是為了提供額外或者不同的操作,而插入代替」實際對象」的對象,即代理類,針對代理類的調用操作,都會涉及到與」實際對象」的通訊,代理類起到中間人的作用。Java動態代理比代理的思想更進一步,它可以動態的創建代理類並處理對」實際對象」的調用,Java動態代理底層基於Proxy/InvocationHandler相關類反射技術

Java動態代理

Java動態代理底層基於Proxy/InvocationHandler相關類反射技術,注意,Java動態代理只能做介面的動態代理。下面是一個Java動態代理的示例:

public interface Hello {      String hello(String msg);  }  public static class HelloImpl implements Hello {      @Override      public String hello(String msg) {          return "hello " + msg;      }  }    public static class HelloProxy implements InvocationHandler {      private Object target = null;        public Object build(Object target) {          this.target = target;          return Proxy.newProxyInstance(                  target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);      }        @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          return method.invoke(target, args);      }  }    public static void main(String[] args) throws Exception {      // 設置此屬性,讓JVM自動生成的proxy類寫入文件      System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");        HelloImpl helloImpl = new HelloImpl();      Hello hello = (Hello) new HelloProxy().build(helloImpl);        System.out.println(hello.hello("world"));  }

Java動態代理為什麼只能做介面的動態代理呢,這與JDK動態生成代理類機制有關,JDK動態生成代理類邏輯如下:

/**   * var0:代理類名稱,比如com.sun.proxy.$Proxy0   * var1:代理介面   * var2:代理類訪問標識   */  public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {      ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);      final byte[] var4 = var3.generateClassFile();      if (saveGeneratedFiles) {  		// 如果開啟了保存proxy文件,則進行保存          AccessController.doPrivileged(new PrivilegedAction<Void>() {              public Void run() {                  try {                      int var1 = var0.lastIndexOf(46);                      Path var2;                      if (var1 > 0) {                          Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));                          Files.createDirectories(var3);                          var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");                      } else {                          var2 = Paths.get(var0 + ".class");                      }                        Files.write(var2, var4, new OpenOption[0]);                      return null;                  } catch (IOException var4x) {                      throw new InternalError("I/O exception saving generated file: " + var4x);                  }              }          });      }        return var4;  }

Java動態代理自動生成代理類的邏輯是生成一個代理介面的實現類(這也是只能做介面動態代理的原因),對該類中方法調用都指向到了我們自己定義的xxxInvocationHandler,在xxxInvocationHandler中再通過反射調用真正類的方法,這樣就完成了一次動態代理的方法調用。比如,示例中自動生成類com.sun.proxy.$Proxy0文件如下所示:

package com.sun.proxy;    import com.luo.demo.boot.dyn.DynMain.Hello;  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 Hello {      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})).booleanValue();          } 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 String hello(String var1) throws  {          try {              return (String)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)).intValue();          } 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.luo.demo.boot.dyn.DynMain$Hello").getMethod("hello", 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());          }      }  }

Java動態代理能滿足針對介面的場景,如果對沒有實現介面的動態代理該如何做呢?這個時候就該cglib上場了~

cglib動態代理

cglib是一個強大的高性能程式碼生成包,被廣泛的用於AOP框架中,比如Spring AOP就用到了cglib(Spring中如果代理類實現了介面,就使用jdk動態代理,否則使用cglib)。

CGLIB包的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。除了CGLIB包,腳本語言例如Groovy和BeanShell,也是使用ASM來生成java的位元組碼。當然不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。

cglib示例程式碼如下:

public static class HelloServiceImpl {      public String hello(String msg) {          return "hello " + msg;      }  }    // 利用Enhancer類生成代理類  public static class MyMethodInterceptor implements MethodInterceptor {      @Override      public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methodProxy)              throws Throwable {          return methodProxy.invokeSuper(obj, arg);      }  }    public static void main(String[] args) {      // 設置將cglib生成的代理類位元組碼生成到指定位置      System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\ideas2\cglib");        Enhancer enhancer = new Enhancer();      enhancer.setSuperclass(HelloServiceImpl.class);      enhancer.setCallback(new MyMethodInterceptor());        HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();      System.out.println(helloService.hello("world"));  }

cglib生成的代理類實際上是指定類的子類,比如上面例子cglib自動生成的代理類如下所示:

public class CglibMain$HelloServiceImpl$$EnhancerByCGLIB$$15d87b9c extends HelloServiceImpl implements Factory {  	final String CGLIB$hello$0(String var1) {          return super.hello(var1);      }        public final String hello(String var1) {          MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;          if (this.CGLIB$CALLBACK_0 == null) {              CGLIB$BIND_CALLBACKS(this);              var10000 = this.CGLIB$CALLBACK_0;          }            return var10000 != null ? (String)var10000.intercept(this, CGLIB$hello$0$Method, new Object[]{var1}, CGLIB$hello$0$Proxy) : super.hello(var1);      }        // 其他方法,比如equals、toString、hashCode、clone等  }

如果cglib要代理的指定類是final類型的,也就是上例中HelloServiceImpl如果是final的,則cglib在創建子類時報異常;如果指定類中方法是final的,則創建子類是OK的,但是針對該final方法的調用不會產生動態代理的效果。

小結

Java動態代理底層基於Proxy/InvocationHandler相關類反射技術,其自動生成代理類的邏輯是生成一個代理介面的實現類(這也是只能做介面動態代理的原因),對該類中方法調用都指向到了我們自己定義的xxxInvocationHandler,在xxxInvocationHandler中再通過反射調用真正類的方法。

cglib的動態代理底層通過asm生成要代理類的子類,在該子類中調用我們自定義的xxxMethodInterceptor,在xxxMethodInterceptor中調用子類中對應方法,對應方法內部直接通過supper.xxx()調用對應要代理類的方法。

參考資料:

1、深入淺出Java反射(https://topcoder.site/2018/08/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAJava%E5%8F%8D%E5%B0%84/)