打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
浅析JDK动态代理的源码

        最近查看了一下JDK实现动态代理的部分源码,这里做一个简单的记录。

1. JDK 动态代理的使用

       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~");	}}

          程序的输出如下

~~~ Before invoking Log ~~~	$Proxy0	printHey! I'm Joey~~~~ After invoking Log ~~~

      从上面的输出可以看到,我们实现了AOP的效果,在print方法调用前后都打印了日志的输出。需要注意的是,在InvocationHandler的invoke()方法中,第一个参数Object proxy其实是动态生成的代理对象,从输出结果也能看到名字是$Proxy0,而不是原来的HelloImpl对象,原来的HelloImpl对象的名字应该是HelloImpl才对,所以我们在调用真正的print方法时要传入原来的HelloImpl对象,即

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)语句来调用。

2. 浅析JDK 动态代理的源码

      上面简单介绍了JDK动态代理的使用,如果用过的小伙伴们应该都不需要更多解释了,下面进入正题,浅析JDK实现动态代理的源码。首先看一下Proxy的newInstance方法的签名:

public static Object newProxyInstance(ClassLoader loader,                                      Class<?>[] interfaces,                                      InvocationHandler h)

      这3个传入参数分别是ClassLoader,要实现的接口数组,实现了InvocationHandler接口的类,查看一下newInstance()方法的源码(部分):

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.WeakCachejava.lang.reflect.InvocationHandlerjava.lang.Object

       终于看到我传入的InvocationHandler了!说明之前的猜想基本得到了验证。

3. JDK如何构造一个代理类

      在上一个部分中我们知道在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 ProxyClassFactoryreturn 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这个方法的实现我还没有细看,因为太长了,细节也比较多,希望等以后看完了再来更新这篇博客。

 

4. 总结

       JDK实现动态代理的过程如下:

  1. 克隆传入的接口Class数组
  2. 查找或者生成实现了接口数组中所有接口的动态代理类的Class
  3. 利用动态代理类的Class获取Constructor对象
  4. Constructor对象利用我们传入的InvocationHandler实现类对象作为输入参数,生成动态代理类的对象

       JDK生成的动态代理类对象的一些性质:

  1. 实现了我们传入的接口Class数组中所有的接口
  2. 这些接口中如果含有非public的接口,则左右非public接口必须在同一个包下面
  3. 父类有一个InvocationHandler的成员变量,它的值是我们在Proxy.newInstance()方法中传入的,所以它和InvocationHandler的关系不是is-a,而是has-a
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Jdk动态代理原理解析
动态代理
jdk 动态代理源码分析
Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理
java SE静态代理和动态代理
Java 动态代理的底层原理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服