JDK动态代理源码解析

2018-12-03 09:26:08来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

动态代理、静态代理优缺点

    关于JDK的动态代理,最为人熟知的可能要数Spring AOP的实现,默认情况下,Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。那么,什么是JDK的动态代理呢?

   JDK的动态代理:就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程。

   代理过程:JDK提供Proxy类来实现动态代理--->newProxyInstance来获得代理实现类--->InvocationHandler接口--->invoke方法

    优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。动态代理只有在用到被代理对象的时候才会对被代理类进行类加载。    而静态代理在编译器编译时就已经开始占内存了。。 

   缺点
   1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
   2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

弱引用

     被弱引用关联的对象只能生存到下一次垃圾收集发生之前。 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 在JDK 1.2之后,提供了WeakReference类来实现弱引用。jdk动态代理使用WeakCache 类 对代理类对象进行缓存, 代理类的引用就是采用的虚引用key 继承自WeakReference 。

借助JDK实现一个动态代理

JDK动态代理步骤

步骤一、编写代理接口

package com.cbam.demo.dynamicProxy;

/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public interface HelloService {
   void sayHello(String name);
}

步骤二、编写接口实现类

package com.cbam.demo.dynamicProxy;

/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class HelloServiceImpl implements HelloService {
  @Override
  public void sayHello(String name) {
     System.out.println("Hello " + name);
  }
}

步骤三、编写InvocationHandler 实现类

package com.cbam.demo.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class ProxyInvocationHandler implements InvocationHandler {
  //要代理的对象
  private Object target;

  public ProxyInvocationHandler(Object target) {
   this.target = target;
 }

  public Object getProxy() {
    return Proxy.newProxyInstance(Thread.currentThread()
     .getContextClassLoader(), target.getClass().getInterfaces(),
      this);
   }

   /**public Object getProxy2(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 {
    System.out.println("----- before -----");

    checkJurisdiction();
    Object rtn = method.invoke(target, args);
    System.out.println("----- after -----");
    return rtn;
  }

  //模拟检测权限

  private void checkJurisdiction(){

     System.out.println("----- 模拟检测权限操作 -----");

  }
}

步骤四、编写InvocationHandler 实现类

package com.cbam.demo.dynamicProxy;

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class Main {

   public static void main(String[] args) {
    HelloService helloService = new HelloServiceImpl();
    ProxyInvocationHandler proxyInovationHandler = new ProxyInvocationHandler(helloService);
     HelloService proxy = (HelloService) proxyInovationHandler.getProxy();
      proxy.sayHello("梁舒");

   }
}

输出:

----- before -----
Hello 梁舒
----- after -----

前面代码示例展示了jdk动态代理怎么玩儿。 下面我们来分析一下, jdk底层到底搞了啥就生成了代理类:

首先我们从InvocationHandler接口入手:

package java.lang.reflect;
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
public interface InvocationHandler {

   /**
  * Processes a method invocation on a proxy instance and returns
  * @see UndeclaredThrowableException
  */
   public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
  }

然后我们从获取代理类的方法中的Proxy.newProxyInstance

public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), target.getClass().getInterfaces(),
this);
}

点进去, 可以在Proxy 中看到该静态方法的签名:

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

该方法的注释:

 Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.返回一个特定接口的代理类的实例, 代理类对象可以分发method调用到专门的调用处理器。

待会儿就能理解这句话的含义了。根据javaDoc 得出:
第一个参数为一个类加载器, 就是指定一个类加载器来加载所生成的代理类的字节码而已。
第二个参数为代理类要去实现的接口
第三个参数就是我们所定义的invocation handler 我们毕竟刚才传递的是this 嘛
再看整个方法里面的核心逻辑(删除部分校验逻辑):

首先来看看它是如何生成代理类的:

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

    if (h == null) {
     throw new NullPointerException();
    }

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
     checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
   }
    // 这里是生成class的地方
   Class<?> cl = getProxyClass0(loader, intfs);
   // 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例
    try {
      final Constructor<?> cons = cl.getConstructor(constructorParams);
      final InvocationHandler ih = h;
      if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
       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());
    }
  }


其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:

private static Class<?> getProxyClass0(ClassLoader loader,
    Class<?>... interfaces) {

  // 代理的接口数量不能超过65535(没有这种变态吧)  
  if (interfaces.length > 65535) {
      throw new IllegalArgumentException("interface limit exceeded");
  }

  return proxyClassCache.get(loader, interfaces);
}

其中代理缓存是使用WeakCache实现的,如下

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

观察WeakCache 构造参数, 里面传入了两个工厂实例, 两个工厂均实现了BiFunction 函数接口, KeyFactory 用来生产 缓存 key ,ProxyClassFactory 用来生产字节码 。 
这里写图片描述

看看WeakCache 类用什么来进行的缓存: 


这里写图片描述


  可以看到使用两层ConcurrentHashMap来存的:(key, sub-key) -> value} 其中key 和 value是弱引用, sub-key 是强引用。其中的key 就是我们指定的类加载器。 sub-key 是通过subKeyFactory 工厂方法产生, value 是通过valueFactory 工厂产生, 在上图WeakCache 中都能找到。
 具体的缓存逻辑这里暂不关心,只需要关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的apply方法:

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
   // 所有代理类名字的前缀
   private static final String proxyClassNamePrefix = "$Proxy";

   // 用于生成代理类名字的计数器
   private static final AtomicLong nextUniqueNumber = new AtomicLong();

   @Override
   public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
   // 省略验证代理接口的代码……

   String proxyPkg = null; // 生成的代理类的包名
   // 对于非公共接口,代理类的包名与接口的相同
   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");
      }
    }
  }

   // 对于公共接口的包名,默认为com.sun.proxy
   if (proxyPkg == null) {
   proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  }

   // 获取计数
   long num = nextUniqueNumber.getAndIncrement();
   // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增
   String proxyName = proxyPkg + proxyClassNamePrefix + num;

    // 这里才是真正的生成代理类的字节码的地方
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces);
    try {
      // 根据二进制字节码返回相应的Class实例
      return defineClass0(loader, proxyName,
              proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
             throw new IllegalArgumentException(e.toString());
    }
  }
}

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这个属性的值从哪里来呢:

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异常。如:

即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。

反编译$Proxy0.class文件,如下所示:


package com.sun.proxy;

import com.mikan.proxy.HelloWorld;
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 HelloWorld {
    private static Method m1;
    private static Method m3;
    private static Method m0;
    private static Method m2;

    public $Proxy0(InvocationHandler paramInvocationHandler) {
      super(paramInvocationHandler);
     }

    public final boolean equals(Object paramObject) {
      try {
            return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
          }
     catch (Error|RuntimeException localError) {
            throw localError;
         }
     catch (Throwable localThrowable) {
           throw new UndeclaredThrowableException(localThrowable);
        }
     }

      public final void sayHello(String paramString) {
      try {
        this.h.invoke(this, m3, new Object[] { paramString });
         return;
       }
      catch (Error|RuntimeException localError) {
         throw localError;
       }
      catch (Throwable localThrowable) {
         throw new UndeclaredThrowableException(localThrowable);
       }
     }

     public final int hashCode() {
     try {
         return ((Integer)this.h.invoke(this, m0, null)).intValue();
       }
     catch (Error|RuntimeException localError) {
        throw localError;
       }
     catch (Throwable localThrowable) {
        throw new UndeclaredThrowableException(localThrowable);
      }
    }

   public final String toString() {
    try {
       return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError) {
       throw localError;
      }
    catch (Throwable localThrowable) {
      throw new UndeclaredThrowableException(localThrowable);
      }
   }

   static {
     try {
       m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
       m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
       m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
       m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
       return;
      }
    catch (NoSuchMethodException localNoSuchMethodException) {
       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
      }
    catch (ClassNotFoundException localClassNotFoundException) {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
     }
   }
 } 

可以看到,动态生成的代理类有如下特性:

  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提供的各种拦截该如何实现,就已经很明了了,如下所示:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   // BeforeAdvice
   Object retVal = null;
   try {
   // AroundAdvice
   retVal = method.invoke(target, args);
   // AroundAdvice
    // AfterReturningAdvice
   }
   catch (Throwable e) {
    // AfterThrowingAdvice
   }
   finally {
     // AfterAdvice
    }
    return retVal;
  }

上面是对于Spring AOP使用JDK动态代理实现的基本框架代码,当然具体的实现肯定比这个复杂得多,但是基本原理不外乎如是。所以理解基本原理对于理解其他的代码也是很有好处的。

 到了这里我们捋一捋调用过程:

首先ProxyInvocationHandler --->InvocationHandler--->getProxy--->Proxy.newProxyInstance() -> getProxyClass0(loader, intfs); -> proxyClassCache.get(loader, interfaces) ->
WeakCache--->ProxyClassFactory--->$Proxy0 

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:HTTPClient实现免登陆请求(带cookie请求)

下一篇:Springboot 01 HelloWorld