細說JDK動態代理的實現原理

原文:http://blog.csdn.net/mhmyqn/article/details/48474815

關於JDK的動態代理,最爲人熟知的可能要數Spring AOP的實現,默認情況下,Spring AOP的實現對於接口來說就是使用的JDK的動態代理來實現的,而對於類的代理使用CGLIB來實現。那麼,什麼是JDK的動態代理呢?

JDK的動態代理,就是在程序運行的過程中,根據被代理的接口來動態生成代理類的class文件,並加載運行的過程。JDK從1.3開始支持動態代理。那麼JDK是如何生成動態代理的呢?JDK動態代理爲什麼不支持類的代理,只支持接口的代理?

首先來看一下如何使用JDK動態代理。JDK提供了java.lang.reflect.Proxy類來實現動態代理的,可通過它的newProxyInstance來獲得代理實現類。同時對於代理的接口的實際處理,是一個java.lang.reflect.InvocationHandler,它提供了一個invoke方法供實現者提供相應的代理邏輯的實現。可以對實際的實現進行一些特殊的處理,像Spring AOP中的各種advice。下面來看看如何使用。

被代理的接口

[java] view plain copy
  1. package com.mikan.proxy;  
  2.   
  3. /** 
  4.  * @author Mikan 
  5.  * @date 2015-09-15 18:00 
  6.  */  
  7. public interface HelloWorld {  
  8.   
  9.     void sayHello(String name);  
  10.   
  11. }  
接口的實現類:

[java] view plain copy
  1. package com.mikan.proxy;  
  2.   
  3. /** 
  4.  * @author Mikan 
  5.  * @date 2015-09-15 18:01 
  6.  */  
  7. public class HelloWorldImpl implements HelloWorld {  
  8.     @Override  
  9.     public void sayHello(String name) {  
  10.         System.out.println("Hello " + name);  
  11.     }  
  12. }  
實現一個java.lang.reflect.InvocationHandler:

[java] view plain copy
  1. package com.mikan.proxy;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5.   
  6. /** 
  7.  * @author Mikan 
  8.  * @date 2015-09-15 19:53 
  9.  */  
  10. public class CustomInvocationHandler implements InvocationHandler {  
  11.     private Object target;  
  12.   
  13.     public CustomInvocationHandler(Object target) {  
  14.         this.target = target;  
  15.     }  
  16.   
  17.     @Override  
  18.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  19.         System.out.println("Before invocation");  
  20.         Object retVal = method.invoke(target, args);  
  21.         System.out.println("After invocation");  
  22.         return retVal;  
  23.     }  
  24. }  
使用代理:

[java] view plain copy
  1. package com.mikan.proxy;  
  2.   
  3. import java.lang.reflect.Proxy;  
  4.   
  5. /** 
  6.  * @author Mikan 
  7.  * @date 2015-09-15 18:01 
  8.  */  
  9. public class ProxyTest {  
  10.   
  11.     public static void main(String[] args) throws Exception {  
  12.         System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");  
  13.   
  14.         CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());  
  15.         HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(  
  16.                 ProxyTest.class.getClassLoader(),  
  17.                 new Class[]{HelloWorld.class},  
  18.                 handler);  
  19.         proxy.sayHello("Mikan");  
  20.     }  
  21.   
  22. }  
運行的輸出結果:

[plain] view plain copy
  1. localhost:classes mikan$ java com/mikan/proxy/ProxyTest  
  2. Before invocation  
  3. Hello Mikan  
  4. After invocation  
從上面可以看出,JDK的動態代理使用起來非常簡單,但是隻知道如何使用是不夠的,知其然,還需知其所以然。所以要想搞清楚它的實現,那麼得從源碼入手。這裏的源碼是1.7.0_79。首先來看看它是如何生成代理類的:

[java] view plain copy
  1. public static Object newProxyInstance(ClassLoader loader,  
  2.                                       Class<?>[] interfaces,  
  3.                                       InvocationHandler h)  
  4.     throws IllegalArgumentException {  
  5.     if (h == null) {  
  6.         throw new NullPointerException();  
  7.     }  
  8.   
  9.     final Class<?>[] intfs = interfaces.clone();  
  10.     final SecurityManager sm = System.getSecurityManager();  
  11.     if (sm != null) {  
  12.         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
  13.     }  
  14.     // 這裏是生成class的地方  
  15.     Class<?> cl = getProxyClass0(loader, intfs);  
  16.     // 使用我們實現的InvocationHandler作爲參數調用構造方法來獲得代理類的實例  
  17.     try {  
  18.         final Constructor<?> cons = cl.getConstructor(constructorParams);  
  19.         final InvocationHandler ih = h;  
  20.         if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {  
  21.             return AccessController.doPrivileged(new PrivilegedAction<Object>() {  
  22.                 public Object run() {  
  23.                     return newInstance(cons, ih);  
  24.                 }  
  25.             });  
  26.         } else {  
  27.             return newInstance(cons, ih);  
  28.         }  
  29.     } catch (NoSuchMethodException e) {  
  30.         throw new InternalError(e.toString());  
  31.     }  
  32. }  
其中newInstance只是調用Constructor.newInstance來構造相應的代理類實例,這裏重點是看getProxyClass0這個方法的實現:

[java] view plain copy
  1. private static Class<?> getProxyClass0(ClassLoader loader,  
  2.                                        Class<?>... interfaces) {  
  3.     // 代理的接口數量不能超過65535(沒有這種變態吧)  
  4.     if (interfaces.length > 65535) {  
  5.         throw new IllegalArgumentException("interface limit exceeded");  
  6.     }  
  7.     // JDK對代理進行了緩存,如果已經存在相應的代理類,則直接返回,否則纔會通過ProxyClassFactory來創建代理  
  8.     return proxyClassCache.get(loader, interfaces);  
  9. }  
其中代理緩存是使用WeakCache實現的,如下

[java] view plain copy
  1. private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  
  2.     proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());  
具體的緩存邏輯這裏暫不關心,只需要關心ProxyClassFactory是如何生成代理類的,ProxyClassFactory是Proxy的一個靜態內部類,實現了WeakCache的內部接口BiFunction的apply方法:

[java] view plain copy
  1. private static final class ProxyClassFactory  
  2.     implements BiFunction<ClassLoader, Class<?>[], Class<?>> {  
  3.     // 所有代理類名字的前綴  
  4.     private static final String proxyClassNamePrefix = "$Proxy";  
  5.   
  6.     // 用於生成代理類名字的計數器  
  7.     private static final AtomicLong nextUniqueNumber = new AtomicLong();  
  8.   
  9.     @Override  
  10.     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  
  11.         // 省略驗證代理接口的代碼……  
  12.   
  13.         String proxyPkg = null;     // 生成的代理類的包名  
  14.         // 對於非公共接口,代理類的包名與接口的相同  
  15.         for (Class<?> intf : interfaces) {  
  16.             int flags = intf.getModifiers();  
  17.             if (!Modifier.isPublic(flags)) {  
  18.                 String name = intf.getName();  
  19.                 int n = name.lastIndexOf('.');  
  20.                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  
  21.                 if (proxyPkg == null) {  
  22.                     proxyPkg = pkg;  
  23.                 } else if (!pkg.equals(proxyPkg)) {  
  24.                     throw new IllegalArgumentException(  
  25.                         "non-public interfaces from different packages");  
  26.                 }  
  27.             }  
  28.         }  
  29.   
  30.         // 對於公共接口的包名,默認爲com.sun.proxy  
  31.         if (proxyPkg == null) {  
  32.             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
  33.         }  
  34.   
  35.         // 獲取計數  
  36.         long num = nextUniqueNumber.getAndIncrement();  
  37.         // 默認情況下,代理類的完全限定名爲:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次遞增  
  38.         String proxyName = proxyPkg + proxyClassNamePrefix + num;  
  39.   
  40.         // 這裏纔是真正的生成代理類的字節碼的地方  
  41.         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
  42.             proxyName, interfaces);  
  43.         try {  
  44.             // 根據二進制字節碼返回相應的Class實例  
  45.             return defineClass0(loader, proxyName,  
  46.                                 proxyClassFile, 0, proxyClassFile.length);  
  47.         } catch (ClassFormatError e) {  
  48.             throw new IllegalArgumentException(e.toString());  
  49.         }  
  50.     }  
  51. }  
ProxyGenerator是sun.misc包中的類,它沒有開源,但是可以反編譯來一探究竟:

[java] view plain copy
  1. public static byte[] generateProxyClass(final String var0, Class[] var1) {  
  2.     ProxyGenerator var2 = new ProxyGenerator(var0, var1);  
  3.     final byte[] var3 = var2.generateClassFile();  
  4.     // 這裏根據參數配置,決定是否把生成的字節碼(.class文件)保存到本地磁盤,我們可以通過把相應的class文件保存到本地,再反編譯來看看具體的實現,這樣更直觀  
  5.     if(saveGeneratedFiles) {  
  6.         AccessController.doPrivileged(new PrivilegedAction() {  
  7.             public Void run() {  
  8.                 try {  
  9.                     FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");  
  10.                     var1.write(var3);  
  11.                     var1.close();  
  12.                     return null;  
  13.                 } catch (IOException var2) {  
  14.                     throw new InternalError("I/O exception saving generated file: " + var2);  
  15.                 }  
  16.             }  
  17.         });  
  18.     }  
  19.     return var3;  
  20. }  
saveGeneratedFiles這個屬性的值從哪裏來呢:

[java] view plain copy
  1. private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();  
GetBooleanAction實際上是調用Boolean.getBoolean(propName)來獲得的,而Boolean.getBoolean(propName)調用了System.getProperty(name),所以我們可以設置sun.misc.ProxyGenerator.saveGeneratedFiles這個系統屬性爲true來把生成的class保存到本地文件來查看。

這裏要注意,當把這個屬性設置爲true時,生成的class文件及其所在的路徑都需要提前創建,否則會拋出FileNotFoundException異常。如:

[java] view plain copy
  1. Exception in thread "main" java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException: com/sun/proxy/$Proxy0.class (No such file or directory)  
  2. at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336)  
  3. at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327)  
  4. at java.security.AccessController.doPrivileged(Native Method)  
  5. at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326)  
  6. at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:672)  
  7. at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592)  
  8. at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244)  
  9. at java.lang.reflect.WeakCache.get(WeakCache.java:141)  
  10. at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:455)  
  11. at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738)  
  12. at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15)  
  13. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  14. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)  
  15. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  
  16. at java.lang.reflect.Method.invoke(Method.java:606)  
  17. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)  
即我們要在運行當前main方法的路徑下創建com/sun/proxy目錄,並創建一個$Proxy0.class文件,才能夠正常運行並保存class文件內容。

反編譯$Proxy0.class文件,如下所示:

[java] view plain copy
  1. package com.sun.proxy;  
  2.   
  3. import com.mikan.proxy.HelloWorld;  
  4. import java.lang.reflect.InvocationHandler;  
  5. import java.lang.reflect.Method;  
  6. import java.lang.reflect.Proxy;  
  7. import java.lang.reflect.UndeclaredThrowableException;  
  8.   
  9. public final class $Proxy0 extends Proxy implements HelloWorld {  
  10.   private static Method m1;  
  11.   private static Method m3;  
  12.   private static Method m0;  
  13.   private static Method m2;  
  14.   
  15.   public $Proxy0(InvocationHandler paramInvocationHandler) {  
  16.     super(paramInvocationHandler);  
  17.   }  
  18.   
  19.   public final boolean equals(Object paramObject) {  
  20.     try {  
  21.       return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
  22.     }  
  23.     catch (Error|RuntimeException localError) {  
  24.       throw localError;  
  25.     }  
  26.     catch (Throwable localThrowable) {  
  27.       throw new UndeclaredThrowableException(localThrowable);  
  28.     }  
  29.   }  
  30.   
  31.   public final void sayHello(String paramString) {  
  32.     try {  
  33.       this.h.invoke(this, m3, new Object[] { paramString });  
  34.       return;  
  35.     }  
  36.     catch (Error|RuntimeException localError) {  
  37.       throw localError;  
  38.     }  
  39.     catch (Throwable localThrowable) {  
  40.       throw new UndeclaredThrowableException(localThrowable);  
  41.     }  
  42.   }  
  43.   
  44.   public final int hashCode() {  
  45.     try {  
  46.       return ((Integer)this.h.invoke(this, m0, null)).intValue();  
  47.     }  
  48.     catch (Error|RuntimeException localError) {  
  49.       throw localError;  
  50.     }  
  51.     catch (Throwable localThrowable) {  
  52.       throw new UndeclaredThrowableException(localThrowable);  
  53.     }  
  54.   }  
  55.   
  56.   public final String toString() {  
  57.     try {  
  58.       return (String)this.h.invoke(this, m2, null);  
  59.     }  
  60.     catch (Error|RuntimeException localError) {  
  61.       throw localError;  
  62.     }  
  63.     catch (Throwable localThrowable) {  
  64.       throw new UndeclaredThrowableException(localThrowable);  
  65.     }  
  66.   }  
  67.   
  68.   static {  
  69.     try {  
  70.       m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] { Class.forName("java.lang.Object") });  
  71.       m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello"new Class[] { Class.forName("java.lang.String") });  
  72.       m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);  
  73.       m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);  
  74.       return;  
  75.     }  
  76.     catch (NoSuchMethodException localNoSuchMethodException) {  
  77.       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
  78.     }  
  79.     catch (ClassNotFoundException localClassNotFoundException) {  
  80.       throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
  81.     }  
  82.   }  
  83. }  
可以看到,動態生成的代理類有如下特性:

  1. 繼承了Proxy類,實現了代理的接口,由於java不能多繼承,這裏已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。
  2. 提供了一個使用InvocationHandler作爲參數的構造方法。
  3. 生成靜態代碼塊來初始化接口中方法的Method對象,以及Object類的equals、hashCode、toString方法。
  4. 重寫了Object類的equals、hashCode、toString,它們都只是簡單的調用了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
  5. 代理類實現代理接口的sayHello方法中,只是簡單的調用了InvocationHandler的invoke方法,我們可以在invoke方法中進行一些特殊操作,甚至不調用實現的方法,直接返回。

至此JDK動態代理的實現原理就分析的差不多了。同時我們可以想像一下Spring AOP提供的各種攔截該如何實現,就已經很明瞭了,如下所示:

[java] view plain copy
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  2.     // BeforeAdvice  
  3.     Object retVal = null;  
  4.     try {  
  5.         // AroundAdvice  
  6.         retVal = method.invoke(target, args);  
  7.         // AroundAdvice  
  8.         // AfterReturningAdvice  
  9.     }  
  10.     catch (Throwable e) {  
  11.         // AfterThrowingAdvice  
  12.     }  
  13.     finally {  
  14.         // AfterAdvice  
  15.     }  
  16.     return retVal;  
  17. }  
上面是對於Spring AOP使用JDK動態代理實現的基本框架代碼,當然具體的實現肯定比這個複雜得多,但是基本原理不外乎如是。所以理解基本原理對於理解其他的代碼也是很有好處的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章