zoukankan      html  css  js  c++  java
  • JDK 动态代理分析

    Java的代理有两种:静态代理和动态代理,动态代理又分为 基于jdk的动态代理 和 基于cglib的动态代理 ,两者都是通过动态生成代理类的方法实现的,但是基于jdk的动态代理需要委托类实现接口,基于cglib的动态代理不要求委托类实现接口。

    接下来主要分析一下基于jdk的动态代理的实现原理。

    一 动态代理例子

    首先来看一个动态代理的例子:

    # 测试类,主要功能是生成代理类并调用代理方法 TargetFactory.java
    public
    class TargetFactory { public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); TargetFactory tf = new TargetFactory(); Target tt = new Target(); Display dy = (Display) tf.getInstance(tt, new InvokerHandler(tt)); try { dy.f(); } catch (Exception e) { e.printStackTrace(); } } public Object getInstance(Object target, InvocationHandler handler){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{Display.class},handler); } } # 接口 Display.java interface Display { public void f(); public void g(); } # 实现了接口的目标类 Target.java public class Target implements Display{ @Override public void f() { System.out.println("Targer f() method"); } @Override public void g() { System.out.println("Targer g() method");; } }
    # 实现了InvocationHandler接口的代理类的调用处理类 InvokerHandler.java
    public class InvokerHandler implements InvocationHandler { private Object target; public InvokerHandler(Object t){ target = t; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("beforem invoke method"); method.invoke(target, args); System.out.println("after invoke method"); return null; } }  

    运行上面的例子,结果为:

    beforem invoke method
    Targer f() method
    after invoke method
    

     

    二 代理类分析

    我们从生成的代理类入手来进行分析,代理类默认是只存在于内存中的,我们可以通过添加如下代码来将代理类存储在磁盘上:

    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    FAQ1:添加此代码后程序有时会抛出 java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException : testjavadynamicProxy$Proxy0.class (系统找不到指定的路径。)。这个问题不得不说一下代理类的生成路径。系统是根据接口的描述符来选择生成路径的,如果有一个接口的描述符都为public的,那么代理类就被放置在用户目录下面,可以通过System.getProperty("user.dir")来获取到。接口中只要有一个是非public的,那么代理类的放置路径就为System.getProperty("user.dir")+File.separator+该接口的包路径。谈到这里,我们可以想象一下如果有两个接口是非public的,而它们属于不同的包,那么将会抛出IllegalArgumentException的异常。

    代理类的命名是 “$Proxy”(由Proxy类中的proxyClassNamePrefix字段指定的)+代理类的序号(Proxy类中的nextUniqueNumber字段,从0开始),考虑到多线程的问题在操作nextUniqueNumber时先要获取到nextUniqueNumberLock的对象锁。

    获得了代理类的class文件后我们使用jd-gui(free for no commercial)来进行反编译获取到源码,本文的$Proxy0.class 反编译的结果如下

    //代理类都继承 Proxy 类 并且实现代理接口Display
    public final class $Proxy0 extends Proxy implements Display
    {
        //构造函数的入参为 例子中InvokerHandler的实例
        //也就是 Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{Display.class},handler); 的入参 handler
        public $Proxy0(InvocationHandler paramInvocationHandler) throws 
        {
            /*接着调用Proxy的构造函数,把handler赋值给Proxy类的h字段,下面注释为Proxy的构造函数
             *protected Proxy(InvocationHandler h) {
             *    this.h = h;
             *}
            */
            
             super(paramInvocationHandler);   
    
        }    
        
          private static Method m1;
          private static Method m3;
          private static Method m4;
          private static Method m0;
          private static Method m2;
    
        static
        {
          try
          {
           //通过反射获取接口中的方法f() 和 g(),这就决定了委托类必须实现接口,不然的话没有办法通过反射来调用委托类中的方法
            m3 = Class.forName("test.java.dynamicProxy.Display").getMethod("g", new Class[0]);
            m4 = Class.forName("test.java.dynamicProxy.Display").getMethod("f", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
            return;
          }
          catch (NoSuchMethodException localNoSuchMethodException)
          {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
          }
          catch (ClassNotFoundException localClassNotFoundException)
          {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
          }
        }
        //在调用代理类中的接口方法时,代理类会将此方法和方法的参数作为入参来调用paramInvocationHandler的invoke函数,在invoke函数中调用委托类中对应的函数
        public final void g() throws 
        {
          try
          {
            //m3代表的是g方法,null是g的入参,因为g没有入参所以为null
            this.h.invoke(this, m3, null);
            return;
          }
          catch (Error|RuntimeException localError)
          {
            throw localError;
          }
          catch (Throwable localThrowable)
          {
            throw newpublic final void f() throws 
        {
          try
          {  
            this.h.invoke(this, m4, null);
            return;
          }
          catch (Error|RuntimeException localError)
          {
            throw localError;
          }
          catch (Throwable localThrowable)
          {
            throw new UndeclaredThrowableException(localThrowable);
          }
        }
        
        public final boolean equals(Object paramObject) throws 
        {
          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 int hashCode() throws 
        {
          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() throws 
        {
          try
          {
            return (String)this.h.invoke(this, m2, null);
          }
          catch (Error|RuntimeException localError)
          {
            throw localError;
          }
          catch (Throwable localThrowable)
          {
            throw new UndeclaredThrowableException(localThrowable);
          }
        }
    }

     基于jdk动态代理所生成的代理类和静态代理类一样都要实现接口,动态代理类需要继承Proxy(不知道为何要继承此类)。动态代理invoke函数中的对于委托类的方法调用是反射调用,效率上比这静态代理要差一些。

    在编码时,静态代理类需要用户实现每一个接口方法,而动态代理只需要实现 InvocationHandler 中的invoke函数,因此动态代理使得代码比较简洁,所有对method的预处理都在invoke函数中完成。

       

     三 代理类的产生

    上面使用反编译的手段来分析了代理类的源码,下面要介绍一下代理类到底是怎么生成的

     1. Proxy.newProxyInstance方法

    newProxyInstance 是 Proxy类中的静态方法,它的作用就是根据入参来返回一个代理类的实例,下面来介绍一下入参:

    Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{Display.class},handler);

    target.getClass().getClassLoader():是用来加载代理类的class loader

    new Class<?>[]{Display.class}:是需要代理的接口,一个代理类可以代理多个接口,所以这里是个数组

    handler: 传递委托类方法调用的调用处理类,在例子一种对应的是InvokerHandler的实例
    我们来看一下 newProxyInstance的源码:

     public static Object newProxyInstance(ClassLoader loader,
                          Class<?>[] interfaces,
                          InvocationHandler h)
        throws IllegalArgumentException
        {
        if (h == null) {
            throw new NullPointerException();
        }
        //生成代理类   
        Class cl = getProxyClass(loader, interfaces);
    
        try {
            //获取构造函数,生成并返回代理类的实例
            //根据第二节中对生成代理类的分析,构造函数的参数类型为 { InvocationHandler.class }
            Constructor cons = cl.getConstructor(constructorParams);
            return (Object) cons.newInstance(new Object[] { h });
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString());
        }
        }

    2. getProxyClass

    这个函数主要实现了下列的功能:

    • 对需要代理的接口进行合法性验证(接口对传入newProxyInstance的Class loader是否可见,是否是接口类型,接口去重)

    在此只关注一下接口的去重。此函数中采用HashSet的方法来简单进行去重,代码如下:

    Set interfaceSet = new HashSet();
    
    if (interfaceSet.contains(interfaceClass)) {
        throw new IllegalArgumentException(
             "repeated interface: " + interfaceClass.getName());
    }
    • 使用本地缓存loaderToCache来缓存已经产生的代理类

    先来看一下本地缓存的初始化,它采用WeakHashMap这个特殊的Map类型,关于WeakHashMap在此不再赘述

         private static Map loaderToCache = new WeakHashMap();

    loaderToCache 的类型是<ClassLoader,<Object,Class>>,涉及到缓存操作的代码如下,代码比较简单,又有完整的注释,这里不予过多分析,以免画蛇添足之嫌。

    Map cache;
    synchronized (loaderToCache) {
        cache = (Map) loaderToCache.get(loader);
        if (cache == null) {
        cache = new HashMap();
        loaderToCache.put(loader, cache);
        }
        /*
         * This mapping will remain valid for the duration of this
         * method, without further synchronization, because the mapping
         * will only be removed if the class loader becomes unreachable.
         */
    }
    
    /*
     * Look up the list of interfaces in the proxy class cache using
     * the key.  This lookup will result in one of three possible
     * kinds of values:
     *     null, if there is currently no proxy class for the list of
     *         interfaces in the class loader,
     *     the pendingGenerationMarker object, if a proxy class for the
     *         list of interfaces is currently being generated,
     *     or a weak reference to a Class object, if a proxy class for
     *         the list of interfaces has already been generated.
     */
    synchronized (cache) {
        /*
         * Note that we need not worry about reaping the cache for
         * entries with cleared weak references because if a proxy class
         * has been garbage collected, its class loader will have been
         * garbage collected as well, so the entire cache will be reaped
         * from the loaderToCache map.
         */
        do {
        Object value = cache.get(key);
        if (value instanceof Reference) {
            proxyClass = (Class) ((Reference) value).get();
        }
        if (proxyClass != null) {
            // proxy class already generated: return it
            return proxyClass;
        } else if (value == pendingGenerationMarker) {
            // proxy class being generated: wait for it
            try {
            cache.wait();
            } catch (InterruptedException e) {
            /*
             * The class generation that we are waiting for should
             * take a small, bounded time, so we can safely ignore
             * thread interrupts here.
             */
            }
            continue;
        } else {
            /*
             * No proxy class for this list of interfaces has been
             * generated or is being generated, so we will go and
             * generate it now.  Mark it as pending generation.
             */
            cache.put(key, pendingGenerationMarker);
            break;
        }
        } while (true);
    }
    • 产生缓存中没有的代理类,并将该类存入缓存

    如果缓存中没有所需的代理类,则由下面的这个函数来根据需要代理的接口产生。

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

     

    3. ProxyGenerator

     这个类是jdk动态代理的核心类,class文件的生成就是在这个类中完成的,在分析这个类之前,首先来看一下class file的 格式,这里只简要的提一下,可以在jvm规范中找到详细的解释。

    ClassFile {
        u4 magic; //此处必须为0xCAFEBABE
        u2 minor_version;
        u2 major_version;
        u2 constant_pool_count;
        cp_info constant_pool[constant_pool_count-1];
        u2 access_flags;
        u2 this_class;
        u2 super_class;
        u2 interfaces_count;
        u2 interfaces[interfaces_count];
        u2 fields_count;                  //代理类中的field info没有属性
        field_info fields[fields_count];
        u2 methods_count;
        method_info methods[methods_count];//代理类中的method info只有Code属性和Exceptions属性
        u2 attributes_count; // 代理类没有属性,所以attributes_count=0
        attribute_info attributes[attributes_count];
    }

    java class file中最复杂的就是各种各样的attribute,而在代理类中只存在两种属性"Code" 和 "Exceptions",由此可以看出,该类也是比较简单的。

    constant_pool[constant_pool_count-1] 是class file中不可缺少的元素,这里需要提一下,在class file中引用constant_pool中的元素时下标是从1 开始的,比如constant_pool_count是39 那么只能使用constant_pool[1] --> constant_pool[38]的

    元素。

    在ProxyGenerator 中使用静态内部类ConstantPool来管理constant pool,在ConstantPool中使用private List<Entry> pool = new ArrayList<Entry>(32) 来存储constant pool entries,用private Map<Object,Short> map = new HashMap<Object,Short>(16) 来存储entries 与下标之间的对应关系,这样的设计避免了需要轮询pool来查找需要存入的条目是否已经存在了。

     

    4. ProxyGenerator.generateClassFile

    在ProxyGenerator中generateClassFile 是入口函数,该函数可以对照class file的结构来阅读。

        private byte[] generateClassFile() {
    
            /* ============================================================
             * Step 1: Assemble ProxyMethod objects for all methods to
             * generate proxy dispatching code for.
             */
    
            /*
             * Record that proxy methods are needed for the hashCode, equals,
             * and toString methods of java.lang.Object.  This is done before
             * the methods from the proxy interfaces so that the methods from
             * java.lang.Object take precedence over duplicate methods in the
             * proxy interfaces.
             */
             
             /* hashCodeMethod equalsMethod toStringMethod 存在于每个生成的代理类中
             * addProxyMethod 中会扫描每个接口中的方法,对于函数签名一致的方法则判断
             * 抛出的异常类型是否一致,如果不一致且没有继承关系,则不抛出异常。如果不
             * 一致但异常有继承关系的,则抛出子类的异常
             * 例如  接口1中 void f() throws Exception
             *       接口2中 void f() throws IOException
             * 代理类中      void f() throws IOException
             */
            addProxyMethod(hashCodeMethod, Object.class);
            addProxyMethod(equalsMethod, Object.class);
            addProxyMethod(toStringMethod, Object.class);
    
            /*
             * Now record all of the methods from the proxy interfaces, giving
             * earlier interfaces precedence over later ones with duplicate
             * methods.
             */
            for (int i = 0; i < interfaces.length; i++) {
                Method[] methods = interfaces[i].getMethods();
                for (int j = 0; j < methods.length; j++) {
                    addProxyMethod(methods[j], interfaces[i]);
                }
            }
    
            /*
             * For each set of proxy methods with the same signature,
             * verify that the methods' return types are compatible.
             */
             /*这里需要强调一下,签名一致的函数返回值类型不一样且没有继承关系,则是不兼容的
             * 如果返回值类型不一致而返回值存在继承关系的,在代理类中返回值类型为子类
             * 例如: 接口1  superclass f();
             *        接口2  subclass f();
             *    代理类中   subclass f();  其中 subclass extends superclass
             */
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                checkReturnTypes(sigmethods);
            }
    
            /* ============================================================
             * Step 2: Assemble FieldInfo and MethodInfo structs for all of
             * fields and methods in the class we are generating.
             */
            try {
                methods.add(generateConstructor());//生成构造函数的字节码
    
                for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                    for (ProxyMethod pm : sigmethods) {
    
                        // add static field for method's Method object
                        fields.add(new FieldInfo(pm.methodFieldName,
                            "Ljava/lang/reflect/Method;",
                             ACC_PRIVATE | ACC_STATIC));
    
                        // generate code for proxy method and add it
                        //生成equals,toString,hashCode 和 接口方法的字节码
                        methods.add(pm.generateMethod());
                    }
                }
                //生成静态代码块的字节码
                methods.add(generateStaticInitializer());
    
            } catch (IOException e) {
                throw new InternalError("unexpected I/O Exception");
            }
    
            if (methods.size() > 65535) {
                throw new IllegalArgumentException("method limit exceeded");
            }
            if (fields.size() > 65535) {
                throw new IllegalArgumentException("field limit exceeded");
            }
    
            /* ============================================================
             * Step 3: Write the final class file.
             */
    
            /*
             * Make sure that constant pool indexes are reserved for the
             * following items before starting to write the final class file.
             */
            cp.getClass(dotToSlash(className));
            cp.getClass(superclassName);
            for (int i = 0; i < interfaces.length; i++) {
                cp.getClass(dotToSlash(interfaces[i].getName()));
            }
    
            /*
             * Disallow new constant pool additions beyond this point, since
             * we are about to write the final constant pool table.
             */
            cp.setReadOnly();
    
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            DataOutputStream dout = new DataOutputStream(bout);
    
            try {
                /*
                 * Write all the items of the "ClassFile" structure.
                 * See JVMS section 4.1.
                 */
                                           // u4 magic;
                dout.writeInt(0xCAFEBABE);
                                            // u2 minor_version;
                dout.writeShort(CLASSFILE_MINOR_VERSION);
                                            // u2 major_version;
                dout.writeShort(CLASSFILE_MAJOR_VERSION);
    
                cp.write(dout);             // (write constant pool)
                                            // u2 access_flags;
                dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
                                            // u2 this_class;
                dout.writeShort(cp.getClass(dotToSlash(className)));
                                            // u2 super_class;
                dout.writeShort(cp.getClass(superclassName));
                                            // u2 interfaces_count;
                dout.writeShort(interfaces.length);
                                            // u2 interfaces[interfaces_count];
                for (int i = 0; i < interfaces.length; i++) {
                    dout.writeShort(cp.getClass(
                        dotToSlash(interfaces[i].getName())));
                }
                                            // u2 fields_count;
                dout.writeShort(fields.size());
                                            // field_info fields[fields_count];
                for (FieldInfo f : fields) {
                    f.write(dout);
                }
                                            // u2 methods_count;
                dout.writeShort(methods.size());
                                            // method_info methods[methods_count];
                for (MethodInfo m : methods) {
                    m.write(dout);
                }
                                             // u2 attributes_count;
                dout.writeShort(0); // (no ClassFile attributes for proxy classes)
    
            } catch (IOException e) {
                throw new InternalError("unexpected I/O Exception");
            }
    
            return bout.toByteArray();
        }
  • 相关阅读:
    c# – 通过反射获取命名空间中的所有类型
    宝塔任务计划通道设置
    DRF项目框架基础设计
    Redis-数据特征和应用场景
    Redis-持久化详解
    Dockerfile文件详解
    ntp同步阿里服务器时间(centos)
    NUC8/11更新EC Firmware
    ambarella H2 kernel调试记录
    MobaXterm 执行make menuconfig不能删除字符
  • 原文地址:https://www.cnblogs.com/cruze/p/3819761.html
Copyright © 2011-2022 走看看