最近查看了一下JDK实现动态代理的部分源码,这里作一个简单的记录。java
JDK为咱们实现了动态代理,它是基于接口的实现,也就是说要为某个类动态地生成一个代理类的话,这个类必需要实现一个或多个接口,若是没有实现接口,JDK动态代理就无能为力了,只能使用CGlib来实现动态代理。首先看一个简单使用的例子:数组
// 定义一个接口 interface SayHello { public void print(String msg); } // 实现一个接口,HelloImpl就是咱们要生成动态代理的类 class HelloImpl implements SayHello { @Override public void print(String msg) { System.out.println(msg); } }
// 定义一个实现了InvocationHandler接口的类 class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 调用目标方法前须要作的操做,例如打印日志 System.out.println("~~~ Before invoking Log ~~~"); System.out.println("\t" + proxy.getClass().getCanonicalName()); System.out.println("\t" + method.getName()); // 真正地调用想要实际须要调用的方法 Object result = method.invoke(target, args); // 调用目标方法以后须要作的操做,例如输出日志等 System.out.println("~~~ After invoking Log ~~~"); // 返回方法执行的结果 return result; } }
public class ProxyTest { public static void main(String[] args) { SayHello hello = new HelloImpl(); SayHello proxyHello = (SayHello) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), new DynamicProxy(hello)); proxyHello.print("Hey! I'm Joey~"); } }
程序的输出以下app
~~~ Before invoking Log ~~~ $Proxy0 print Hey! I'm Joey~ ~~~ After invoking Log ~~~
从上面的输出能够看到,咱们实现了AOP的效果,在print方法调用先后都打印了日志的输出。须要注意的是,在InvocationHandler的invoke()方法中,第一个参数Object proxy实际上是动态生成的代理对象,从输出结果也能看到名字是$Proxy0,而不是原来的HelloImpl对象,原来的HelloImpl对象的名字应该是HelloImpl才对,因此咱们在调用真正的print方法时要传入原来的HelloImpl对象,即ide
method.invoke(target, args)
若是使用下面的调用则会产生无线循环的异常:函数
method.invoke(proxy, args)
这是一个很tricky的bug,由于proxy是代理对象,它也有method,也就是咱们这里的print方法,原本代理对象在执行print方法的时候就是调用InvocationHandler的invoke方法去间接地执行print方法的,那么在invoke方法中又执行method方法(即print方法),它又会调用InvocationHandler的invoke方法,这样就是无限递归永远出不来了,因此咱们在初始化DynamicProxy的时候要传入一个对象做为真正要调用方法的原生对象,而后在真正须要调用这个方法时候使用method.invoke(target, args)语句来调用。ui
上面简单介绍了JDK动态代理的使用,若是用过的小伙伴们应该都不须要更多解释了,下面进入正题,浅析JDK实现动态代理的源码。首先看一下Proxy的newInstance方法的签名:this
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
这3个传入参数分别是ClassLoader,要实现的接口数组,实现了InvocationHandler接口的类,查看一下newInstance()方法的源码(部分):spa
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);
首先克隆一个接口数组,暂时还不清楚为何要这样作,若是有知道的恳请赐教。接着作一些检查,最后根据ClassLoader和接口数组获得代理类,getProxyClass0()方法会查找一个cache中是否已经保存了这样一个代理类,若是有就直接返回,不然生成一个。至因而如何产生这样一个代理类,请看下一个部分,省得栈太深,最后都忘了要干吗了.....再看接下来的代码:代理
try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); }
cl是以前获取到的代理类的Class,根据参数获得构造函数,constructorParams其实就是{InvocationHandler.class}数组,也就是说生成的代理类含有一个构造函数,这个构造函数须要接收一个InvocationHandler的参数。最后把Constructor和InvocationHandler传给newInstance方法,这个方法也很简单,就是让Constructor利用InvocationHandler参数生成一个对象,最后把这个对象返回,也就是上面例子中的proxyHello对象。这个对象实现了接口数组中所有的接口,所以能够转型为SayHello类型。利用下面的代码能够获得proxyHello实现的全部接口:日志
Class<?>[] interfaces = proxyHello.getClass().getInterfaces(); for(Class<?> face : interfaces) { System.out.println(face.getCanonicalName()); }
输出结果为: SayHello
一开始我看到这个结果很奇怪,我觉得JDK除了实现传入的接口数组里的接口之外,也会实现InvocationHandler接口,而后在invoke方法中调用咱们传给它的InvocationHandler实现类对象(即DynamicProxy)去真正地调用invoke方法,就如同咱们实现静态代理那样作。那么既然动态生成的代理类没有实现InvocationHandler接口,它的构造函数又须要接收一个InvocationHandler对象的目的是什么呢?个人猜想是把传入的对象做为它的成员变量,而后在咱们调用接口里的方法的时候就用这个成员对象去调用invoke方法。利用以下的语句查看动态代理类的成员变量:
Field[] declaredFields = proxyHello.getClass().getDeclaredFields(); for(Field field : declaredFields) { field.setAccessible(true); System.out.println(field.getType().getName()); }
打印的结果竟然全是java.lang.reflect.Method,那么咱们传给它的DynamicProxy对象去了哪里呢?估计是在父类里吧,换个语句:
Field[] declaredFields = proxyHello.getClass().getSuperclass().getDeclaredFields();
如今打印的结果以下:
long [Ljava.lang.Class; java.lang.reflect.WeakCache java.lang.reflect.InvocationHandler java.lang.Object
终于看到我传入的InvocationHandler了!说明以前的猜测基本获得了验证。
在上一个部分中咱们知道在getProxyClass0方法中的代码会从一个cache中查找或者生成一个代理类对象,这个方法的代码以下:
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);
首先这个存放接口Class的数组长度不能超过65535,至于为何是这个数字我也不知道。。而后就是利用get方法来获取代理类对象,这个方法的代码我也查看了,不过没太看懂,隐约感受就是用一些工厂来生产cache中不存在的对象。看不懂暂时不要紧,还好这里JDK的注释也说得很清楚了,若是不存在的话就经过ProxyClassFactory来创造代理类,在get方法中也确实是调用了ProxyClassFactory的apply方法去生产对象(还好这一步看懂了,汗),那么就直奔重点吧,ProxyClassFactory是java.lang.reflect.Proxy类的一个private static final class,它的apply方法签名以下:
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces)
从头至尾来看一下代码(一些不过重要的就暂时过滤):
for (Class<?> intf : interfaces) { ...... // 这里判断数组里的每个Class是否是接口,不是接口的话抛出异常,因此说JDK的动态代理只能实现接口 /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } ...... }
// 定义生成的动态代理类所在包的包名 String proxyPkg = null; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } }
这里的代码和注释都说得很清楚了,若是接口Class数组中有的接口的访问属性不是public的,例如是包访问权限的,那么全部这些接口都必须在一个包里面,不然的话要抛出异常。之因此这么作,理由也很简单,举个例子,你本身定义一个类Test去实现这些接口,若是有两个接口不是public的,并且它们分别放在了不一样的包里面,那么这个Test类不管放在哪一个包下面都必然会致使有一个接口是无权访问的,所以若是有的接口不是public的,那么它们必须在一个包下面,同时实现类也必须跟它们在同一个包下,这样才能够访问嘛。
if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num;
这里就是给生成的动态代理类取名,全部代理类的名字都是$Proxy开头、而且后面跟一个数字的形式,另外,若是没有非public的接口,那么就把生成的代理类放在com.sun.proxy包下面。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
这一步最关键,就是传入代理类的名字和它要实现的接口,从而生成class文件并存放到byte数组中,最后调用defineClass0这个native方法去定义这个动态代理类的Class对象。关于generateProxyClass这个方法的实现我尚未细看,由于太长了,细节也比较多,但愿等之后看完了再来更新这篇博客。
JDK实现动态代理的过程以下:
JDK生成的动态代理类对象的一些性质: