JDK动态代理
关于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。下面来看看如何使用。
被代理的接口
1 package com.mikan.proxy; 2 /** 3 * @author Mikan 4 * @date 2015-09-15 18:00 5 */ 6 public interface HelloWorld { 7 void sayHello(String name); 8 }
接口的实现类:
1 package com.mikan.proxy; 2 /** 3 * @author Mikan 4 * @date 2015-09-15 18:01 5 */ 6 public class HelloWorldImpl implements HelloWorld { 7 @Override 8 public void sayHello(String name) { 9 System.out.println("Hello " + name); 10 } 11 }
实现一个java.lang.reflect.InvocationHandler
1 package com.mikan.proxy; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.Method; 4 /** 5 * @author Mikan 6 * @date 2015-09-15 19:53 7 */ 8 public class CustomInvocationHandler implements InvocationHandler { 9 private Object target; 10 public CustomInvocationHandler(Object target) { 11 this.target = target; 12 } 13 @Override 14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 15 System.out.println("Before invocation"); 16 Object retVal = method.invoke(target, args); 17 System.out.println("After invocation"); 18 return retVal; 19 } 20 }
使用代理:
1 package com.mikan.proxy; 2 import java.lang.reflect.Proxy; 3 /** 4 * @author Mikan 5 * @date 2015-09-15 18:01 6 */ 7 public class ProxyTest { 8 public static void main(String[] args) throws Exception { 9 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 10 CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl()); 11 HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance( 12 ProxyTest.class.getClassLoader(), 13 new Class[]{HelloWorld.class}, 14 handler); 15 proxy.sayHello("Mikan"); 16 } 17 }
运行的输出结果:
localhost:classes mikan$ java com/mikan/proxy/ProxyTest
Before invocation
Hello Mikan
After invocation
从上面可以看出,JDK的动态代理使用起来非常简单,但是只知道如何使用是不够的,知其然,还需知其所以然。所以要想搞清楚它的实现,那么得从源码入手。这里的源码是1.7.0_79。首先来看看它是如何生成代理类的:
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这个方法的实现:
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实现的,如下
1 private static final WeakCache<ClassLoader, Class<?>[], Class<?>> 2 proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的apply方法:
1 private static final class ProxyClassFactory 2 implements BiFunction<ClassLoader, Class<?>[], Class<?>> { 3 // 所有代理类名字的前缀 4 private static final String proxyClassNamePrefix = "$Proxy"; 5 // 用于生成代理类名字的计数器 6 private static final AtomicLong nextUniqueNumber = new AtomicLong(); 7 @Override 8 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { 9 // 省略验证代理接口的代码…… 10 String proxyPkg = null; // 生成的代理类的包名 11 // 对于非公共接口,代理类的包名与接口的相同 12 for (Class<?> intf : interfaces) { 13 int flags = intf.getModifiers(); 14 if (!Modifier.isPublic(flags)) { 15 String name = intf.getName(); 16 int n = name.lastIndexOf('.'); 17 String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); 18 if (proxyPkg == null) { 19 proxyPkg = pkg; 20 } else if (!pkg.equals(proxyPkg)) { 21 throw new IllegalArgumentException( 22 "non-public interfaces from different packages"); 23 } 24 } 25 } 26 27 // 对于公共接口的包名,默认为com.sun.proxy 28 if (proxyPkg == null) { 29 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; 30 } 31 32 // 获取计数 33 long num = nextUniqueNumber.getAndIncrement(); 34 // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增 35 String proxyName = proxyPkg + proxyClassNamePrefix + num; 36 37 // 这里才是真正的生成代理类的字节码的地方 38 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 39 proxyName, interfaces); 40 try { 41 // 根据二进制字节码返回相应的Class实例 42 return defineClass0(loader, proxyName, 43 proxyClassFile, 0, proxyClassFile.length); 44 } catch (ClassFormatError e) { 45 throw new IllegalArgumentException(e.toString()); 46 } 47 } 48 }
ProxyGenerator是sun.misc包中的类,它没有开源,但是可以反编译来一探究竟:
public static byte[] generateProxyClass(final String var0, Class[] var1) { ProxyGenerator var2 = new ProxyGenerator(var0, var1); final byte[] var3 = var2.generateClassFile(); // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观 if(saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { try { FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class"); var1.write(var3); var1.close(); return null; } catch (IOException var2) { throw new InternalError("I/O exception saving generated file: " + var2); } } }); } return var3; }
saveGeneratedFiles这个属性的值从哪里来呢:
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异常。如:
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) at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336) at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327) at java.security.AccessController.doPrivileged(Native Method) at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326) at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:672) at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592) at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244) at java.lang.reflect.WeakCache.get(WeakCache.java:141) at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:455) at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738) at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。
反编译$Proxy0.class文件,如下所示:
1 package com.sun.proxy; 2 import com.mikan.proxy.HelloWorld; 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 import java.lang.reflect.UndeclaredThrowableException; 7 8 public final class $Proxy0 extends Proxy implements HelloWorld { 9 private static Method m1; 10 private static Method m3; 11 private static Method m0; 12 private static Method m2; 13 14 public $Proxy0(InvocationHandler paramInvocationHandler) { 15 super(paramInvocationHandler); 16 } 17 18 public final boolean equals(Object paramObject) { 19 try { 20 return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); 21 } 22 catch (Error|RuntimeException localError) { 23 throw localError; 24 } 25 catch (Throwable localThrowable) { 26 throw new UndeclaredThrowableException(localThrowable); 27 } 28 } 29 30 public final void sayHello(String paramString) { 31 try { 32 this.h.invoke(this, m3, new Object[] { paramString }); 33 return; 34 } 35 catch (Error|RuntimeException localError) { 36 throw localError; 37 } 38 catch (Throwable localThrowable) { 39 throw new UndeclaredThrowableException(localThrowable); 40 } 41 } 42 43 public final int hashCode() { 44 try { 45 return ((Integer)this.h.invoke(this, m0, null)).intValue(); 46 } 47 catch (Error|RuntimeException localError) { 48 throw localError; 49 } 50 catch (Throwable localThrowable) { 51 throw new UndeclaredThrowableException(localThrowable); 52 } 53 } 54 55 public final String toString() { 56 try { 57 return (String)this.h.invoke(this, m2, null); 58 } 59 catch (Error|RuntimeException localError) { 60 throw localError; 61 } 62 catch (Throwable localThrowable) { 63 throw new UndeclaredThrowableException(localThrowable); 64 } 65 } 66 67 static { 68 try { 69 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); 70 m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") }); 71 m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); 72 m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); 73 return; 74 } 75 catch (NoSuchMethodException localNoSuchMethodException) { 76 throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); 77 } 78 catch (ClassNotFoundException localClassNotFoundException) { 79 throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); 80 } 81 } 82 }
可以看到,动态生成的代理类有如下特性:
-
继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
-
提供了一个使用InvocationHandler作为参数的构造方法。
-
生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。
-
重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
-
代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。
至此JDK动态代理的实现原理就分析的差不多了。同时我们可以想像一下Spring AOP提供的各种拦截该如何实现,就已经很明了了,如下所示:
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动态代理实现的基本框架代码,当然具体的实现肯定比这个复杂得多,但是基本原理不外乎如是。所以理解基本原理对于理解其他的代码也是很有好处的。
转载:微信公众号:IT哈哈
浙公网安备 33010602011771号