• Java JDK动态代理解析


      动态代理虽不常自己实现,但在Spring或MyBatis中都有重要应用。动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。Spring常JDK和CGLIB动态代理技术。现就了解的JDK动态代理做个笔记。

      先举个例子,然后再慢慢分析。也可以直接跳过例子,回过头再看。

      比如十一的票好难买,乘客要买回家的票买不到,只好找黄牛做代理,让黄牛帮忙抢票。当然黄牛还要多收点钱,到卖票的时候,就帮乘客买票了。代码如下,先定义一个People接口,Passenger乘客类实现People接口,HuangNiuProxya黄牛类实现InvocationHandler接口。

    //People接口,声明买票的功能
    package proxytest;
    
    public interface People{
        public People buyTickets(String trafficTool);
    }
    //乘客类,实现People接口,和买票的函数
    package proxytest;
    
    public class Passenger implements People{
        public People buyTickets(String trafficTool) {
            System.out.println("I am a prssenger.");
            System.out.println("I buy tickets of the " + trafficTool);
            return this;
        }
    }
    //黄牛类,实现InvocationHandler类,和invoke函数
    package proxytest;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class HuangNiuProxy implements InvocationHandler{
        //真实对象
        private Object target = null;
        
        /**
         * 建议代理对象和真实对象代理关系,并返回代理对象
         * @param target 真实对象
         * @return 代理对象
         */
        public Object bind(Object target) {
            this.target = target;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
        
        /*
         * 代理方法逻辑
         * @param proxy 代理对象
         * @param method 当前调试方法
         * @param args 当前方法参数
         * @return 代理结束返回
         * @throws Throwable 异常
         */
        public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
            System.out.println("I am a HuangNiu. I can buy tickets for passengers.");
            System.out.println("I am belong to " + this.getClass().getName());
            System.out.println("I take more money.");
            System.out.println("Method: " + method);
            Object obj = method.invoke(target, args);
            System.out.println("I am a HuangNiu. I give passenger the tickets.");
            //可以返回代理对象
            return proxy;
            //也可以返回method返回值
            //return obj;
        }
    }
    //测试类
    package proxytest;
    
    public class TestProxy{
        public static void main(String args[]) {
            
            HuangNiuProxy huangNiu = new HuangNiuProxy();
            //绑定关系
            People proxy = (People)huangNiu.bind(new Passenger());
            System.out.println("proxy:" + proxy.getClass().getName());
            //买票,先买火车票,再买汽车票
            proxy.buyTickets("train").buyTickets("bus");
        }
    }
    //输出结果
    proxy:com.sun.proxy.$Proxy0
    I am a HuangNiu. I can buy tickets for passengers.
    I am belong to proxytest.HuangNiuProxy
    I take more money.
    Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String)
    I am a prssenger.
    I buy tickets of the train
    I am a HuangNiu. I give passenger the tickets.
    I am a HuangNiu. I can buy tickets for passengers.
    I am belong to proxytest.HuangNiuProxy
    I take more money.
    Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String)
    I am a prssenger.
    I buy tickets of the bus
    I am a HuangNiu. I give passenger the tickets.

      从上面输出结果可以看到,黄牛收了更多的钱,帮乘客买了票,最后把票给了乘客,确实起到了代理的作用。

      针对上面的代码及输出,我们提出几个问题,从问题入手,分析JDK动态代理:

      1. 先声明一个People接口有什么用?

      2. 为什么要实现InvocationHandler接口?

      3. 测试类中的proxy是什么类型?

      4. 测试类中的proxy为什么可以转换成People类型?

      5. proxy.buyTickes函数是如何调用到invoke函数?

      6. InvocationHandler接口的invoke函数要返回什么类型?

      要回答上面几个问题,第三个问题好像是关键,因为涉及到的类的代码都写的明明白白,就它生成的不清不楚,但动态代理功能又是通过它调用。在上面的例子中,也打出了这个proxy实例的所属的类型,它是类型即不是Proxy类型,也不是我们声明的任何一个类型,而是$Proxy0。

    System.out.println("proxy:" + proxy.getClass().getName());
    //proxy:com.sun.proxy.$Proxy0

      原因是通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,Proxy为中,最后一个数字表示对象的标号。既然是动态生成的,那必定有个性化的设置,否则,用静态代码就可以了。

      我们通过定义一个输出.class文件的javaagent来输出编译运行过程中生成的.class文件,再反编译生成源代码,就可以看到$Proxy0的定义了。让我们稍做铺垫,看过源码之后,相信上面的问题就比较好解决了。为了不占篇幅,这段代码在最下面。只要看开头类的声明,构造函数,以及buyTickets函数和最下面的static静态代码段就可以了。

       现在来解答一下上面的问题

      1. 声明一个People接口有什么用?

      JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象,所以先定义接口。在代码后面的应用中,我们也看到了,生成代理对象时,在Proxy.newProxyInstance传了目录类的接口(target.getClass().getInterfaces()),从动态生成的$Proxy0的定义中可以看到,$Proxy0实现了这个接口。

      2. 为什么要实现InvocationHandler接口?

      InvocationHandler是由代理实例的调用处理程序实现的接口 。

      每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

      上面这段话是java API里的解释,约定了,代理实例(proxy)调用方法时,就会调用到InvocationHandler接口的invoke方法。那是如何实现的呢?

      篇尾$Proxy0的代码给了我们答案。

    Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this)

      在Proxy.newProxyInstance函数中传入了参数this,this就是InvocationHandler的一个实例,而在$Proxy0类中,这个InvocationHandler实例被传入$Proxy0的构造函数,$Proxy0的实例proxy就持有了这个InvocationHandler实例。由此可以调用InvocationHandler接口的invoke方法,至于invoke方法最后执行哪个method,是由入参决定的,这也是由$Proxy0的实例proxy控制的。

      3. 测试类中的proxy是什么类型?

      它是类型即不是Proxy类型,也不是我们声明的任何一个类型,而是$Proxy0。通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,Proxy为中,最后一个数字表示对象的标号。

      4. 测试类中的proxy为什么可以转换成People类型?

      proxy是$Proxy0类型的实例,$Proxy0类型实现了People接口。该接口是通过Proxy.newProxyInstance传了目录类的接口target.getClass().getInterfaces())传入的。

      5. proxy.buyTickes函数是如何调用到invoke函数?

      proxy实例持有InvocationHandler接口的实例,从而可以调用InvocationHandler接口里的invoke方法。$Proxy0类中还声明了几个函数类型,通过给invoke函数传递不同的函数类型,来实现调用不同的功能。

      6. invoke函数要返回什么类型?

      在上面黄牛买票的例子中,invoke函数既可以返回代理类型(proxy),也可以返回method.invoke返回的类型。不同之处在于,返回代理类型,可以继续用代理类型操作,返回method.invode返回的类型,就是被代理对象函数里返回的类型。就上面例子买票的代码而言(如下),如果invoke函数返回代理类型,那么在代理买完火车票之后,会再代理买汽车票;如果invoke函数返回被代理对象返回的类型(返回的是passenger本身),那么passenger本身会再买汽车票,而没有再用到代理。

    //买票,先买火车票,再买汽车票
    proxy.buyTickets("train").buyTickets("bus");
    //invoke函数返回proxy实例时,proxy会再代理买汽车票
    proxy:com.sun.proxy.$Proxy0
    I am a HuangNiu. I can buy tickets for passengers.
    I am belong to proxytest.HuangNiuProxy
    I take more money.
    Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String)
    I am a prssenger.
    I buy tickets of the train
    I am a HuangNiu. I give passenger the tickets.
    I am a HuangNiu. I can buy tickets for passengers.
    I am belong to proxytest.HuangNiuProxy
    I take more money.
    Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String)
    I am a prssenger.
    I buy tickets of the bus
    I am a HuangNiu. I give passenger the tickets.
    //如果invoke函数返回的被代理实例函数返回的类型(passenger),那由passenger自己买票
    proxy:com.sun.proxy.$Proxy0
    I am a HuangNiu. I can buy tickets for passengers.
    I am belong to proxytest.HuangNiuProxy
    I take more money.
    Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String)
    I am a prssenger.
    I buy tickets of the train
    I am a HuangNiu. I give passenger the tickets.
    I am a prssenger.
    I buy tickets of the bus

    文中对应的代码见:https://github.com/zhaoshizi/JDKProxyTest,包括黄牛买票例子,$Proxy0.java源码,生成.class文件的javaagent.jar

    要想输出输出的.class文件,在运行时加上jvm参数 -javaagent:XXX/jagent.jar

    XXX为路径,.class文件生成在项目文件夹下的EXPORTED文件夹中

    动态生成的$Proxy0类型的反编译的源代码:

    package com.sun.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    import proxytest.People;
    
    public final class $Proxy0 extends Proxy  implements People
    {
      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 (...){
             ......
        }
      }
      
      public final String toString()
      {
        try{
          return (String)this.h.invoke(this, m2, null);
        }catch (...){
             ......
        }
      }
      //$Proxy0类型中实现了与People接口中的buyTickets函数,并且调用了InvocaHandler的invoke函数
      public final People buyTickets(String paramString)
      {
        try{
          return (People)this.h.invoke(this, m3, new Object[] { paramString });
        }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 (...){
             ......
        }
      }
      
      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被定义为buyTickes类函数类型
          m3 = Class.forName("proxytest.People").getMethod("buyTickets", new Class[] { Class.forName("java.lang.String") });
          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());
        }
      }
    }

      

  • 相关阅读:
    sql server 跨IP库更新表字段(OPENDATASOURCE 、update)
    sql server 开启一个事务
    ARMv8 汇编指令集查询
    开源操作系统项目
    Cmake使用教程交叉编译
    开源机器人项目
    网络通信协议之粘包问题
    开源图像处理计算机视觉
    开源机器学习项目
    C语言中的回调函数
  • 原文地址:https://www.cnblogs.com/zhaoshizi/p/9740788.html
Copyright © 2020-2023  润新知