zoukankan      html  css  js  c++  java
  • JDK动态代理源码解析

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

        关于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 
  • 相关阅读:
    高级开发必须理解的Java中SPI机制
    希尔排序--python
    SpringContextAware使用详解
    visio professional 2013 密钥
    二分查找--python
    [Oracle]单行字符函数
    [Oracle]sqlplus调整列宽
    [Oracle]MacOS sqlplus上下选择命令
    [Oracle]开启SCOTT账户
    [Oracle]Macos 安装Oracle Client 11g 11.2.0.4
  • 原文地址:https://www.cnblogs.com/zhaosq/p/10020648.html
Copyright © 2011-2022 走看看