美文网首页技术干货程序员JAVA
JDK动态代理深入探究

JDK动态代理深入探究

作者: 秃头哥编程 | 来源:发表于2018-05-15 21:34 被阅读111次

    在上一篇文章设计模式之代理模式里,我们说了JDK动态代理类,不过我相信好多人都有疑惑,包括我,刚开始学的时候都是一脸懵逼,那么到底疑惑在哪里呢?

    我的疑惑就是这个InvocationHandler的invoke方法到底是有啥用?我们都没有调用它。newProxyInstance返回的东西到底是啥?等等,这篇文章我们就一起来探讨一下吧。


    首先我们先写一个简单的动态代理吧,有例子更好说明。还是买车的例子,哈哈,说明我挺想买车的。我就直接复制上篇文章的。

    // Car.java
    package com.codeliu.dao;
    
    public interface Car {
        // vip客户可以直接买车
        public void buyCar();
    }
    
    // CarImp1.java
    package com.codeliu.dao.impl;
    import com.codeliu.dao.Car;
    /**
     * 委托类1
     */
    public class CarImp1 implements Car {
        public void buyCar() {
            System.out.println("我是vip1");
        }
    }
    
    // CarProxy.java
    package com.codeliu.dao.impl;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 动态代理类
     */
    public class CarProxy implements InvocationHandler {
        private Object target;
        /**
         * 绑定一个委托对象并获得一个代理类对象
         * @param target [description]
         * @return [description]
         */
        public Object bind(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("调用这个方法前:" + method);
            // 执行委托类的方法
            Object result = method.invoke(target,args);
            System.out.println("调用这个方法后:" + method);
            return result;
        }
    }
    
    package com.codeliu.test;
    
    import com.codeliu.dao.impl.CarImp1;
    import com.codeliu.dao.impl.CarImp2;
    import com.codeliu.dao.impl.CarProxy;
    import com.codeliu.dao.Car;
    public class TestProxy {
        public static void main(String[] args) {
            CarProxy cp = new CarProxy();
            // 传入一个实现了该接口的实例就行
            Car car = (Car)cp.bind(new CarImp1());
            // Car car = (Car)cp.bind(new CarImp2());
            car.buyCar();
        }
    }
    

    好了,那么现在我们就来看看Test.java里生成的car对象到底是个什么东a西,是我们的Car接口吗?(我一开始一直理解不通)

    我们利用反射可以获取到car对应类的大部分信息,好了,现在我们把Test.java改成如下形式

    package com.codeliu.test;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import com.codeliu.dao.Car;
    import com.codeliu.dao.impl.CarImp1;
    import com.codeliu.dao.impl.CarProxy;
    
    public class Test {
        public static void main(String[] args) {
            CarProxy cp = new CarProxy();
            // 传入一个实现了该接口的实例就行
            Car car = (Car)cp.bind(new CarImp1());
            // Car car = (Car)cp.bind(new CarImp2());
            car.buyCar();
            
            System.out.println("car是Proxy类的子类吗?" + (car instanceof Proxy));
            Class<?> c = car.getClass();
            System.out.println(c);
            System.out.println("car的Class类是:" + c.getName());
            Field[] fields = c.getDeclaredFields();
            System.out.print("car的Class类中有哪些字段:");
            for(Field f:fields) {
                System.out.print(f.getName() + "   ");
            }
            System.out.println();
            System.out.print("car的Class类中有哪些方法:");
            Method[] methods = c.getDeclaredMethods();
            for(Method m:methods) {
                System.out.print(m.getName() + "   ");
            }
            System.out.println();
            System.out.println("car的Class类的父类是:" + c.getSuperclass());
            
            Class<?>[] class1 = c.getInterfaces();
            System.out.print("car的Class类实现的接口有:");
            for(Class<?> c1:class1) {
                System.out.println(c1.getName() + "  ");
            }
        }
    }
    

    运行一下,输出如下

    调用这个方法前:public abstract void com.codeliu.dao.Car.buyCar()
    我是vip1
    调用这个方法后:public abstract void com.codeliu.dao.Car.buyCar()
    car是Proxy类的子类吗?true
    class com.sun.proxy.$Proxy0
    car的Class类是:com.sun.proxy.$Proxy0
    car的Class类中有哪些字段:m1   m2   m3   m0   
    car的Class类中有哪些方法:equals   toString   hashCode   buyCar   
    car的Class类的父类是:class java.lang.reflect.Proxy
    car的Class类实现的接口有:com.codeliu.dao.Car  
    

    恩,发现了什么,这个玩意竟然是一个Proxy类的子类,它的名字是$Proxy0,实现了我们自己定义的Car接口,还有四个字段和方法。



    既然知道了上面的结果,那么我们就去看看newProxyInstance方法的源码,应该能找到答案了。
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    }
    

    我就截了比较重要的,getProxyClass0方法,它的作用就是生成一个proxy类,好,我们看看这个方法。

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

    再去看看这个get方法,emmmmm,代码太多了,尼玛,我也看不太懂,不过经过我查资料,总结如下

    newProxyInstance方法调用getProxyClass0生成一个$Proxy0类。
    恩没了,现在我们要找答案就得去看看$Proxy0类的源码了。

    因为生成的是一个.class文件,所以我们先反编译后,可以看到源码如下

    package com.sun.proxy;
    
    import com.codeliu.dao.Car;
    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 Car
    {
      private static Method m1;
      private static Method m2;
      private static Method m3;
      private static Method m0;
      
      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 String toString()
      {
        try
        {
          return (String)this.h.invoke(this, m2, null);
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
      
      public final void buyCar()
      {
        try
        {
          this.h.invoke(this, m3, null);
          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);
        }
      }
      
      static
      {
        try
        {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m3 = Class.forName("com.codeliu.dao.Car").getMethod("buyCar", 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());
        }
      }
    }
    

    $Proxy0继承了Proxy类并实现了Car接口,它有一个构造方法,传入了一个InvocationHandler实例并调用了父类的构造方法,然后是四个final方法,其他三个是Object类传过来的,还有一个叫buyCar,这不就是我们在接口中定义的吗?我们看看它怎么写的

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

    恩,首先m3在静态块中,取得Car接口中的buyCar方法,然后是调用invoke方法,666,现在终于解决了文章开头的疑惑,原来在这里调用了,等等,那么这个h又是什么东西,我们看看Proxy的源码

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

    一个字段和一个带参的构造函数,我们再看看$Proxy0的源码中有这么一段

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

    好家伙,到这里就真相大白了,虽然我还是不知道这个$Proxy0.class怎么生出来的,但基本也懂了,再总结一下整趟流程。

    首先是我们写了一个接口,一个委托类实现了这个接口,然后写一个代理类实现InvocationHandler接口并重写invoke方法,之后调用Proxy的newProxyInstance方法,这个方法调用getProxyClass0方法生成一个动态代理对象,这个对象对应的类是$Proxy0,$Proxy0继承了Proxy类并且实现了我们自己写的接口,在这个类中,有静态字段和final修饰的方法,其中有我们在接口中定义的方法(为了好表述,我先把它叫做method),通过method方法,调用我们在代理类中重写的invoke,而我们在invoke方法中,又调用了委托类的方法,所以表面上好像是直接调用了委托类的方法,其实绕了一大圈,是系统帮我们调用了。



    不知道大家有没有看懂,没有看懂就多看几遍,因为我说的也有点蒙圈了。今天连续写了两篇文章,好饿啊,不过弄懂了困扰我的问题,贼开心。

    我是一个追求完美的人啊,我好想画一个流程图啊,不然一大段文字,不形象又难懂,不过我没力气了,有没有好心人画一个啊。==

    相关文章

      网友评论

        本文标题:JDK动态代理深入探究

        本文链接:https://www.haomeiwen.com/subject/mjwydftx.html