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

    在《springAOP之代理模式》中说了代理模式,包含静态代理和动态代理,在动态代理模式中又分为JDK动态代理和CGlib动态代理,今天重点来看JDK动态代理。

    一、概述

    说到JDK动态代理就必须想到JDK动态代理要求有一个统一的接口,那为什么要有接口,下面会说到,下面看我的接口类,

    package cn.com.jdk.proxy;
    
    public interface Subject {
    
        void sayHello(String a);
    }

    接口类很简单就是一个简单的方法定义。下面看实际的接口的实现类SubjectImpl,

    package cn.com.jdk.proxy;
    
    public class SubjectImpl implements Subject {
    
        @Override
        public void sayHello(String a) {
            // TODO Auto-generated method stub
    
            System.out.println("hello:"+a);
        }
    
    }

    实现类简单的事项了Subject接口,进行了打印操作。下面看代理类

    package cn.com.jdk.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class JDKProxy implements InvocationHandler {
        private SubjectImpl si;
        //此属性不用管
        private String a;
    /**
     * proxy  JDK动态生成的代理类的实例
     * method 目标方法的Method对象     Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
     * args   目标方法的参数                       new Object[] { paramString }
     */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // TODO Auto-generated method stub
            System.out.println("before");
            //使用反射的放式调用si(被代理类)目标方法
            Object o=method.invoke(si, args);
            System.out.println("after");
            return o;
        }
        public JDKProxy(SubjectImpl si,String a) {
            this.si=si;
            this.a=a;
        }
    
    }

    上面是代理类的实现,在代理类中含义被代理类的一个引用,且提供了响应的构造方法。下面具体的使用,

    package cn.com.jdk.proxy;
    
    import java.lang.reflect.Proxy;
    
    public class ProxyTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            //进行此项设置,可以在项目的com/sun/proxy目录下找到JDK动态生成的代理类的字节码文件
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
            SubjectImpl si=new SubjectImpl();
            
            Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));
            subject.sayHello("tom");
    
        }
    
    }

    上面是使用的代码,通过Proxy类的newProxyInstance方法获得一个Subject的实例,调用sayHello方法,下面看执行结果

    before
    hello:tom
    after

    可以看到执行了sayHello方法,且打印了before和after,这不正是代理类中invoke方法的执行吗,看下面

    很神奇的一件事,我们不光调用了sayHello方法,实现了打印,而且在加入了自己的打印方法,这不正是AOP的增强功能吗。这一切是怎么发生的那,下面细细说来。

    二、详述

    上面,我们又复习了JDK动态代理的内容,以及演示了如何使用JDK动态代理,下面我们要看这是怎么实现的,先从测试的下面这段代码说起,也是最重要的代码,JDK动态代理的精华都在这句代码里,

    Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

    这句代码是调用了Proxy类的newProxyInstance方法,此方法的入参如下,

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

     一共三个参数,一个是ClassLoader,这里传入的是被代理对象的类加载器;一个是Class,这里传入的是被代理对象所实现的接口;一个是InvocationHandler,这里传入的是代理类,代理类实现了InvocationHandler接口。

     1、newProxyInstance方法

    下面看newProxyInstance方法的定义,

    @CallerSensitive
        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.
             */
              //1、使用代理类的类加载器和其所实现的接口,动态生成代理类
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
                //2、返回JDK生成的代理类的构造方法,该构造方法的参数为
                //  InvocationHandler
                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;
                        }
                    });
                }
    //3、返回该构造方法的一个实例,也就是使用InvocationHandler为参数的构造方法利用反射的机制返回一个实例。
    return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }

    该方法中有三步比较重要,上面的注释已经标出。

    1.1、getProxyClass0(loader, intfs)方法

    该方法便是上面的第一步,这一步的作用是JDK返回一个代理类的实例,方法上的注释如下,

    /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);

    注释直译过来是查找或者生成指定的代理类,这里有两层意思,一个是查找,第二个是生成,由此可以想到这个方法中应该有缓存,下面看方法的具体定义,

    /**
         * 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);
        }

    这个方法很简单,判断了接口的数量,大于65535便抛异常,接口的数量大于65535的可能性不大。最后调用了proxyClassCache的get方法,首先看proxyClassCache,从字面上理解是代理类的缓存,看其定义,

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

    是一个WeakCache对象实例,看下该构造方法,

    /**
         * Construct an instance of {@code WeakCache}
         *
         * @param subKeyFactory a function mapping a pair of
         *                      {@code (key, parameter) -> sub-key}
         * @param valueFactory  a function mapping a pair of
         *                      {@code (key, parameter) -> value}
         * @throws NullPointerException if {@code subKeyFactory} or
         *                              {@code valueFactory} is null.
         */
        public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                         BiFunction<K, P, V> valueFactory) {
            this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
            this.valueFactory = Objects.requireNonNull(valueFactory);
        }

    看了该类的构造方法后,回到proxyClassCache.get(loader, interfaces)方法的调用,我们已经知道proxyClassCache是WeakCache的一个实例,那么get方法如下,

     /**
         * Look-up the value through the cache. This always evaluates the
         * {@code subKeyFactory} function and optionally evaluates
         * {@code valueFactory} function if there is no entry in the cache for given
         * pair of (key, subKey) or the entry has already been cleared.
         *
         * @param key       possibly null key
         * @param parameter parameter used together with key to create sub-key and
         *                  value (should not be null)
         * @return the cached value (never null)
         * @throws NullPointerException if {@code parameter} passed in or
         *                              {@code sub-key} calculated by
         *                              {@code subKeyFactory} or {@code value}
         *                              calculated by {@code valueFactory} is null.
         */
        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
            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
                    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 {
                    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);
                    }
                }
            }
        }

     上面是WeakCache的get方法,这个方法暂时不作说明,后面会详细介绍WeakCache类,请参见《JDK动态代理之WeakCache 》。这里只需记住该get方法会返回一个代理类的实例即可。那么此代理类是如何定义的那?

    1.1.1、$Proxy0.class代理类

    这个代理类是JDK动态生成的,其命名规则为以“$”开头+Proxy+“从0开始的序列”。上面在测试的时候,我们加入了下面这行代码,

    //进行此项设置,可以在项目的com/sun/proxy目录下找到JDK动态生成的代理类的字节码文件
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

    注释中写到可以生成代理类的字节码文件,下面是使用反编译工具过来的java代码,

    package com.sun.proxy;
    
    import cn.com.jdk.proxy.Subject;
    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 Subject
    {
      private static Method m1;
      private static Method m3;
      private static Method m2;
      private static Method m0;
     //参数为InvocationHandler的构造方法
      public $Proxy0(InvocationHandler paramInvocationHandler)
        throws 
      {
       //调用父类Proxy的构造方法,在父类的构造方法中会初始化h属性
        super(paramInvocationHandler);
      }
    
      public final boolean equals(Object paramObject)
        throws 
      {
        try
        {
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        }
        catch (RuntimeException localRuntimeException)
        {
          throw localRuntimeException;
        }
        catch (Throwable localThrowable)
        {
        }
        throw new UndeclaredThrowableException(localThrowable);
      }
    //实现的Subject的sayHello方法
      public final void sayHello(String paramString)
        throws 
      {
        try
        {
          //调用h的invoke方法,这里的h指的是实现了InvocationHandler的类
          //调用其中的invoke方法,在本例中是调用JDKProxy类中的invoke方
          //
          this.h.invoke(this, m3, new Object[] { paramString });
          return;
        }
        catch (RuntimeException localRuntimeException)
        {
          throw localRuntimeException;
        }
        catch (Throwable localThrowable)
        {
        }
        throw new UndeclaredThrowableException(localThrowable);
      }
    
      public final String toString()
        throws 
      {
        try
        {
          return (String)this.h.invoke(this, m2, null);
        }
        catch (RuntimeException localRuntimeException)
        {
          throw localRuntimeException;
        }
        catch (Throwable localThrowable)
        {
        }
        throw new UndeclaredThrowableException(localThrowable);
      }
    
      public final int hashCode()
        throws 
      {
        try
        {
          return ((Integer)this.h.invoke(this, m0, null)).intValue();
        }
        catch (RuntimeException localRuntimeException)
        {
          throw localRuntimeException;
        }
        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("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
        }
        throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
      }
    }

    上面是反编译过来的JDK生成的代理类的代码,包含了一个使用InvocationHandler作为参数的构造方法,以及实现了Subject接口的sayHello方法。上面注释中写到该构造方法调用了其父类Proxy的构造方法,下面看其父类Proxy的构造方法,

    protected Proxy(InvocationHandler h) {
            Objects.requireNonNull(h);
            this.h = h;
        }

    把InvocationHandler的值赋给了h,h的定义如下,

    protected InvocationHandler h;

    那么在生成的代理类中自然会继承该属性,所以在代理类中的sayHello中使用下面的方法调用,

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

    上面的this.h便是其父类的h属性。在上面的this.h.invoke中的m3是怎么来的那,看下面,

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

    在该类的静态代码块中给出了4个属性。

    1.2、getConstructor(constructorParams)方法

    在上面的getProxyClass0方法中我们知道该方法会返回一个JDK生成代理类的Class对象,此类的定义便是上面的$Proxy0.class类。其定义在上面已经分析过。getConstructor方法要返回一个以constructorParams为参数的构造方法,

    @CallerSensitive
        public Constructor<T> getConstructor(Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
            return getConstructor0(parameterTypes, Member.PUBLIC);
        }

    调用了getConstuctor0方法返回一个public的构造方法,

    private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                            int which) throws NoSuchMethodException
        {
            Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
            for (Constructor<T> constructor : constructors) {
                if (arrayContentsEq(parameterTypes,
                                    constructor.getParameterTypes())) {
                    return getReflectionFactory().copyConstructor(constructor);
                }
            }
            throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
        }

    上面的方法会返回一个public的构造方法。

    回到最初的调用,我们看getConstructor方法的参数是constructorParams,此属性定义如下,

    /** parameter types of a proxy class constructor */
        private static final Class<?>[] constructorParams =
            { InvocationHandler.class };

    是一个Class数组,其类型为InvocationHandler。这样便可以知道是通过代理类的Class对象返回其构造方法cons。有了构造方法下面便是通过构造方法生成实例。

    1.3、cons.newInstance(new Object[]{h})方法

    此方法便是通过构造方法返回一个代理类的实例。

    上面分析了Proxy的newProxyInstance方法,此方法最终会返回一个代理类的实例,会经过下面几个步骤,

    从上面的步骤,我们知道在获得代理类的构造方法时,是获得其参数为InvocationHandler的构造方法,所以肯定要实现InvocationHandler接口,在本例中便是JDKProxy类,这个类实现了这个接口。值开篇我们讲到JDK动态代理必须要有统一的接口,从上面的步骤中我们知道在生成代理类的Class对象时使用了两个参数,一个ClassLoader,另一个是接口,这里就是为什么要有统一的接口,因为在生成代理类的Class对象中需要接口,所以被代理类必须要有一个接口。

    2、方法调用

    这里的方法调用,便是对应使用方法中的下面这行代码,

    subject.sayHello("tom");

    在上面的分析中获得了一个代理类的实例,即下面这行代码,

    Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

    通过使用被代理类的类加载器、被代理类所实现的接口、实现了InvocationHandler接口的类的实例三个参数,返回了一个代理类的实例。上面已经详细分析过。此代理类的实例继承了Proxy,实现了Subject接口。其sayHello方法如下,

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

    上面已经分析过,this.h是InvocationHandler的实例,这里便是new JDKProxy(si,"111"),m3是m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });下面看JDKProxy中的invoke方法,

    @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // TODO Auto-generated method stub
            System.out.println("before");
            //使用反射的放式调用目标方法
            Object o=method.invoke(si, args);
            System.out.println("after");
            return o;
        }

    此方法的三个参数分别为代理类的实例、Method对象(sayHello),调用sayHello时的参数,所以要调用被代理类的sayHello方法,需要这样写:method.invoke(si,args),即调用被代理类(SubjectImpl)的sayHello方法,参数为args(tom)。下面是一个简单的方法调用过程,

    三、总结

    本文分析了JDK动态代理的简单使用方法及背后的原理,有不当之处欢迎指正,感谢!

  • 相关阅读:
    Java集合中List,Set以及Map等集合体系详解
    Rabbit-MQ-3 队列的属性和消息的属性
    RabbitMQ-1 基本概念和实现简单生产消费者
    ActiveMQ-3
    日期时间类
    字符串反转
    SpringBoot2.0+Shiro+MyBatisPlus权限管理系统
    Servlet+JSP+JDBC学生选课系统
    SpringMVC+Spring+Hibernate个人家庭财务管理系统
    Servlet中FilterConfig的使用
  • 原文地址:https://www.cnblogs.com/teach/p/13143430.html
Copyright © 2011-2022 走看看