zoukankan      html  css  js  c++  java
  • JDK 动态代理的实现

    JDK 动态代理的实现

    虽然在常用的 Java 框架(Spring、MyBaits 等)中,经常见到 JDK 动态代理的使用,也知道了怎么去写一个 JDK 动态代理的 Demo,但是并不清楚实现原理。

    之前略微知道是先生成一个代理对象,然后外部调用代理对象的接口方法,接着代理又会调用原始对象的方法,仅此而已。现在想稍微深入一丢,了解具体一点的实现方式。

    Java 程序是运行在 JVM 中的,源码首先编译成字节码,然后加载到虚拟机中,最后被虚拟机解析然后执行(注意这里的措辞不一定严谨准确,会其意即可)。

    JDK 动态代理实际上也就是在代码运行的过程中动态的生成了实现指定接口(被代理对象实现的接口)的类的字节码,对应的也就是代理类,然后创建这个代理类的对象,也就是代理对象。关于这一部分内容,看这篇博文更好,写的很详细。其实本文关注的点其实是 JDK 是如何创建代理类的,进而找到为什么调用代理对象的方法,就能增强原始对象的方法。

    先上简单的 Demo 代码:

    package jvmlearn;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     *
     * @author xi
     * @date 2018/08/23 12:04
     */
    public class DynamicProxyTest {
        interface IHello {
            void sayHello();
        }
    
        static class Hello implements IHello {
            @Override
            public void sayHello() {
                System.out.println("hello world");
            }
        }
    
        static class DynamicProxy implements InvocationHandler {
    
            Object originalObj;
    
            Object bind(Object originalObj) {
                this.originalObj = originalObj;
                return Proxy.newProxyInstance(
                        originalObj.getClass().getClassLoader(),
                        originalObj.getClass().getInterfaces(), this);
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("welcome");
                return method.invoke(originalObj, args);
            }
        }
    
        public static void main(String[] args) {
            IHello hello = (IHello) new DynamicProxy().bind(new Hello());
            hello.sayHello();
        }
    }
    

    这个例子是『深入理解 Java 虚拟机』书上的例子,感觉例子中的DynamicProxy取名不太好,容易误导像我这样的初学者,容易把它看成代理类,实则不然,它只是个InvocationHandler接口的实现类,不要搞混了。

    其实本次想要关注的重点是代理类和代理对象是如何产生的,所以要看看java.lang.reflect.Proxy#newProxyInstance,因为机器上用的是 JDK 1.8,代码与之前有点小区别,不过问题不大。

        @CallerSensitive// 这个注解不懂是啥,Google 一下,还是没懂,先看看文末的参考吧,有懂的同学,麻烦指点一二
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            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);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                // 创建代理对象
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                //...省略部分不看的内容
            }
        }
    

    主要想看的是java.lang.reflect.Proxy#getProxyClass0是如何生成代理类的:

        /**
         * Generate a proxy class.  Must call the checkProxyAccess method
         * to perform permission checks before calling this.
         */
        private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            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);
        }
    

    proxyClassCache 是一个静态变量,缓存了代理类:

        /**
         * a cache of proxy classes
         */
        private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    

    这里需要注意一下创建缓存的构造方法的两个参数,两个参数的类都实现了BiFunction接口,Java8 的新特性:函数式接口。简单理解就是接口中定义了一个方法,接收两个参数,一顿操作之后,返回一个结果。先看缓存的构造方法:

        public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                         BiFunction<K, P, V> valueFactory) {
            this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
            this.valueFactory = Objects.requireNonNull(valueFactory);
        }
    

    记住subKeyFactoryvalueFactory这两个变量是啥,马上就会用到。

    先看缓存的java.lang.reflect.WeakCache#get方法,方法比较长,不用全部看,关注几个注释的地方即可,具体细节可自行 Debug:

    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
    
        expungeStaleEntries();
    
        Object cacheKey = CacheKey.valueOf(key, refQueue);
    
        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
    
        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap 生成 key,subKeyFactory 就是前面提到的KeyFactory
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
    
        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                // 上面的注释都写明白了,获取出来的对象的类都实现了 Supplier 接口
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)
    
            // lazily construct a Factory
            if (factory == null) {// 创建工厂
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
    
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {//  用 factory 替换 supplier
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }
    

    生成 key 就不看了,直接看V value = supplier.get();这行代码,初次调用时,肯定没有缓存,所以先看java.lang.reflect.WeakCache.Factory#get

    @Override
    public synchronized V get() { // serialize access
        // re-check
        Supplier<V> supplier = valuesMap.get(subKey);
        if (supplier != this) {
            // something changed while we were waiting:
            // might be that we were replaced by a CacheValue
            // or were removed because of failure ->
            // return null to signal WeakCache.get() to retry
            // the loop
            return null;
        }
        // else still us (supplier == this)
    
        // create new value
        V value = null;
        try {// 这个地方的 valueFactory 就是前面说到的 ProxyClassFactory 对象
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            if (value == null) { // remove us on failure
                valuesMap.remove(subKey, this);
            }
        }
        // the only path to reach here is with non-null value
        assert value != null;
    
        // wrap value with CacheValue (WeakReference)
        CacheValue<V> cacheValue = new CacheValue<>(value);
    
        // put into reverseMap
        reverseMap.put(cacheValue, Boolean.TRUE);
    
        // try replacing us with CacheValue (this should always succeed)
        if (!valuesMap.replace(subKey, this, cacheValue)) {// 添加缓存
            throw new AssertionError("Should not reach here");
        }
    
        // successfully replaced us with new CacheValue -> return the value
        // wrapped by it
        return value;
    }
    

    接下来要看的是java.lang.reflect.Proxy.ProxyClassFactory#apply方法,主要这一行:

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    

    这行代码生成了代理类的字节码数组。

    proxyName是代理类接口的包名加上java.lang.reflect.Proxy.ProxyClassFactory#proxyClassNamePrefix属性的值$Proxy,再加上java.lang.reflect.Proxy.ProxyClassFactory#nextUniqueNumber自增一次的值。

    上面 Demo 中DynamicProxyTest的包名为jvmlearn,在main方法中追加以下代码,看一下输出的类的名称:

    System.out.println(DynamicProxyTest.class.getName());
    System.out.println(IHello.class.getName());
    System.out.println(hello.getClass().getName());
    

    输出如下:

    jvmlearn.DynamicProxyTest
    jvmlearn.DynamicProxyTest$IHello
    jvmlearn.$Proxy0
    

    ProxyGenerator是 JDK 提供的生成代理类的工具,源码可以看 OpenJDK 中的:ProxyGenerator.java,现截取部分关键代码如下:

    public static byte[] generateProxyClass(String name,
                                            Class<?>[] interfaces,
                                            int accessFlags)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        // 生成代理类的字节码数组,稍后再看
        final byte[] classFile = gen.generateClassFile();
    
        if (saveGeneratedFiles) {// 是否保存生成的字节码到文件中
            java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int i = name.lastIndexOf('.');
                        Path path;
                        if (i > 0) {// 创建字节码文件路径
                            Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
                            Files.createDirectories(dir);
                            path = dir.resolve(name.substring(i+1, name.length()) + ".class");
                        } else {
                            path = Paths.get(name + ".class");// 字节码文件
                        }
                        Files.write(path, classFile);// 写入到文件中
                        return null;
                    } catch (IOException e) {
                        throw new InternalError(
                            "I/O exception saving generated file: " + e);
                    }
                }
            });
        }
        return classFile;
    }
    

    注释基本说明了这个方法的作用,现在来看一下字节码是如何生成的。JDK 的这个工具是手工生成的字节码,代码虽然有点多,不过注释写的很清楚。但是因为不熟悉字节码,读起来仍旧很吃力,那就采用反推的方式,先看生成的代理类,再看生成字节码的代码。

    先生成代理类的 Class 文件,具体操作就是将sun.misc.ProxyGenerator.saveGeneratedFiles设置为true,两种方式:

    1. IDEA 设置 VM Options:
      IDEA 设置
      -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
      
    2. main方法开头添加如下代码:
      System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
      

    最后生成的 Class 文件路径,以本人使用的 IDEA 为例,在项目根目录下:jvmlearn/$Proxy0.class。先上反编译后的代码:

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package jvmlearn;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    import jvmlearn.DynamicProxyTest.IHello;
    
    final class $Proxy0 extends Proxy implements IHello {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final void sayHello() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        // 省略了 equals、toString、hashCode 方法
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("jvmlearn.DynamicProxyTest$IHello").getMethod("sayHello");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    从反编译的代码就已经可以看出来,代理类的接口方法调用的是InvocationHandlerinvoke方法。

    接下来用javap -v jvmlearn.$Proxy0来反汇编 Class 文件:

    Classfile /Users/xi/***/DEMO/jvmlearn/$Proxy0.class
      Last modified 2019-10-30; size 1939 bytes
      MD5 checksum 0191ebc19d7bb8b925d6d6a84d3b777a
    final class jvmlearn.$Proxy0 extends java.lang.reflect.Proxy implements jvmlearn.DynamicProxyTest$IHello
      minor version: 0
      major version: 49
      flags: ACC_FINAL
    Constant pool:
        // 常量池太长了,文末参考附了图
        // 同样省略了 equals、toString、hashCode 方法
    {
      public jvmlearn.$Proxy0(java.lang.reflect.InvocationHandler) throws ;
        descriptor: (Ljava/lang/reflect/InvocationHandler;)V
        flags: ACC_PUBLIC
        Code:
          stack=10, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: invokespecial #8                  // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
             5: return
        Exceptions:
          throws
    
      public final void sayHello() throws ;
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_FINAL
        Code:
          stack=10, locals=2, args_size=1
             0: aload_0
             1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
             4: aload_0
             5: getstatic     #50                 // Field m3:Ljava/lang/reflect/Method;
             8: aconst_null
             9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
            14: pop
            15: return
            16: athrow
            17: astore_1
            18: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
            21: dup
            22: aload_1
            23: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
            26: athrow
          Exception table:
             from    to  target type
                 0    16    16   Class java/lang/Error
                 0    16    16   Class java/lang/RuntimeException
                 0    16    17   Class java/lang/Throwable
        Exceptions:
          throws
    
    
      static {} throws ;
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=10, locals=2, args_size=0
             0: ldc           #70                 // String java.lang.Object
             2: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
             5: ldc           #77                 // String equals
             7: iconst_1
             8: anewarray     #72                 // class java/lang/Class
            11: dup
            12: iconst_0
            13: ldc           #70                 // String java.lang.Object
            15: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
            18: aastore
            19: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
            22: putstatic     #20                 // Field m1:Ljava/lang/reflect/Method;
            25: ldc           #83                 // String jvmlearn.DynamicProxyTest$IHello
            27: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
            30: ldc           #84                 // String sayHello
            32: iconst_0
            33: anewarray     #72                 // class java/lang/Class
            36: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
            39: putstatic     #50                 // Field m3:Ljava/lang/reflect/Method;
            42: ldc           #70                 // String java.lang.Object
            44: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
            47: ldc           #85                 // String toString
            49: iconst_0
            50: anewarray     #72                 // class java/lang/Class
            53: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
            56: putstatic     #55                 // Field m2:Ljava/lang/reflect/Method;
            59: ldc           #70                 // String java.lang.Object
            61: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
            64: ldc           #86                 // String hashCode
            66: iconst_0
            67: anewarray     #72                 // class java/lang/Class
            70: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
            73: putstatic     #62                 // Field m0:Ljava/lang/reflect/Method;
            76: return
            77: astore_1
            78: new           #90                 // class java/lang/NoSuchMethodError
            81: dup
            82: aload_1
            83: invokevirtual #93                 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
            86: invokespecial #96                 // Method java/lang/NoSuchMethodError."<init>":(Ljava/lang/String;)V
            89: athrow
            90: astore_1
            91: new           #100                // class java/lang/NoClassDefFoundError
            94: dup
            95: aload_1
            96: invokevirtual #93                 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
            99: invokespecial #101                // Method java/lang/NoClassDefFoundError."<init>":(Ljava/lang/String;)V
           102: athrow
          Exception table:
             from    to  target type
                 0    77    77   Class java/lang/NoSuchMethodException
                 0    77    90   Class java/lang/ClassNotFoundException
        Exceptions:
          throws
    }
    

    主要看代理类的反编译代码即可,对照以上两个文件,再看 JDK 生成字节码的代码,可能会好一点点,截取sun.misc.ProxyGenerator#generateClassFile方法中几个重要步骤:

    1. jvmlearn.ProxyGenerator#generateConstructor这是代理类的构造方法

       /**
        * Generate the constructor method for the proxy class.
        */
       private MethodInfo generateConstructor() throws IOException {
           MethodInfo minfo = new MethodInfo(// 方法信息
               "<init>", "(Ljava/lang/reflect/InvocationHandler;)V",
               ACC_PUBLIC);
      
           DataOutputStream out = new DataOutputStream(minfo.code);
      
           code_aload(0, out); // aload0 0x2a 
      
           code_aload(1, out); // aload1 0x2b
      
           out.writeByte(opc_invokespecial);// 调用超类构造方法 0xb7
           out.writeShort(cp.getMethodRef( // 方法参数就是 InvocationHandler 对象,对应常量池里面的 #8
               superclassName,
               "<init>", "(Ljava/lang/reflect/InvocationHandler;)V")); // L 表示对象
      
           out.writeByte(opc_return); //  0xb1 从当前方法返回 void 
      
           minfo.maxStack = 10;  // 操作数栈最大深度
           minfo.maxLocals = 2;  // 局部变量的存储空间 2 个 Slot
           minfo.declaredExceptions = new short[0]; 
      
           return minfo;
       }
      

      对照源码和字节码看这个构造方法的生成。

    2. jvmlearn.ProxyGenerator.ProxyMethod#generateMethod生成代理方法的字节码

      主要关注以下两个部分,对照字节码看:

      • 拿到代理类父类中的InvocationHandler对象h
      out.writeByte(opc_getfield);// 0xb4 获取指定类的实例域,并压入栈顶,这里就是拿到 handler
      out.writeShort(cp.getFieldRef(// #50 Field m3:Ljava/lang/reflect/Method;
              superclassName,
          handlerFieldName, "Ljava/lang/reflect/InvocationHandler;"));// handlerFieldName = 'h'
      
      code_aload(0, out);
      
      out.writeByte(opc_getstatic); // 拿到静态变量,这里是 Method 类型,主要指的是代理方法
      out.writeShort(cp.getFieldRef(
          dotToSlash(className),
          methodFieldName, "Ljava/lang/reflect/Method;"));
      
      • 调用接口方法,也就是invoke方法
      out.writeByte(opc_invokeinterface); 
      out.writeShort(cp.getInterfaceMethodRef(
          "java/lang/reflect/InvocationHandler",
          "invoke", // invoke 方法
          "(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
              "[Ljava/lang/Object;)Ljava/lang/Object;"));
      

    上面两个部分的作用其实就是super.h.invoke(this, m3, (Object[])null);这行代码的作用。

    至此,JDK 动态代理的原理也基本清楚了

    另外

    • 因为 JDK 动态代理是基于接口实现的,所以要求代理类和被代理类实现了相同接口

    • 缓存的使用,ConcurrentHashMap 正确的使用方式

    • 优点:
      相对于静态代理来说,动态代理的优点并不是省去了编写代理类的工作量,而是实现了可以在原始类(被代理类)和接口还未知的时候就可以确定代理类的代理行为,当代理类和原始类脱离直接联系之后,就可以很灵活的被重用于不同的应用场景中。
      对于 JDK 动态代理来说,就是可以先定义InvocationHandler来确定代理行为,创建代理对象时,再将其传入。

    参考:

  • 相关阅读:
    谷歌翻译python接口
    SRILM的安装方法
    语言模型srilm基本用法
    SRILM语言模型格式解读
    矩阵理解
    python生成器 协程
    python Queue模块使用
    scrapy 学习笔记2
    scrapy 学习笔记1
    xpath语法规则
  • 原文地址:https://www.cnblogs.com/magexi/p/11762577.html
Copyright © 2011-2022 走看看