当前位置: 移动技术网 > IT编程>开发语言>Java > Java JDK动态代理的基本原理详细介绍

Java JDK动态代理的基本原理详细介绍

2019年07月22日  | 移动技术网IT编程  | 我要评论

jdk动态代理详解

本文主要介绍jdk动态代理的基本原理,让大家更深刻的理解jdk proxy,知其然知其所以然。明白jdk动态代理真正的原理及其生成的过程,我们以后写jdk proxy可以不用去查demo,就可以徒手写个完美的proxy。下面首先来个简单的demo,后续的分析过程都依赖这个demo去介绍,例子采用jdk1.8运行。

jdk proxy helloworld

package com.yao.proxy;
/**
 * created by robin
 */
public interface helloworld {
  void sayhello();
}
package com.yao.proxy;
import com.yao.helloworld;
/**
 * created by robin
 */
public class helloworldimpl implements helloworld {
  public void sayhello() {
    system.out.print("hello world");
  }
}
package com.yao.proxy;
import java.lang.reflect.invocationhandler;
import java.lang.reflect.method;
/**
 * created by robin
 */
public class myinvocationhandler implements invocationhandler{
  private object target;
  public myinvocationhandler(object target) {
    this.target=target;
  }
  public object invoke(object proxy, method method, object[] args) throws throwable {
    system.out.println("method :"+ method.getname()+" is invoked!");
    return method.invoke(target,args);
  }
}
package com.yao.proxy;
import com.yao.helloworld;
import java.lang.reflect.constructor;
import java.lang.reflect.invocationhandler;
import java.lang.reflect.invocationtargetexception;
import java.lang.reflect.proxy;

/**
 * created by robin
 */
public class jdkproxytest {
  public static void main(string[]args) throws nosuchmethodexception, illegalaccessexception, invocationtargetexception, instantiationexception {
    //这里有两种写法,我们采用略微复杂的一种写法,这样更有助于大家理解。
    class<?> proxyclass= proxy.getproxyclass(jdkproxytest.class.getclassloader(),helloworld.class);
    final constructor<?> cons = proxyclass.getconstructor(invocationhandler.class);
    final invocationhandler ih = new myinvocationhandler(new helloworldimpl());
    helloworld helloworld= (helloworld)cons.newinstance(ih);
    helloworld.sayhello();

    //下面是更简单的一种写法,本质上和上面是一样的
    /* 
    helloworld helloworld=(helloworld)proxy.
         newproxyinstance(jdkproxytest.class.getclassloader(),
            new class<?>[]{helloworld.class},
            new myinvocationhandler(new helloworldimpl()));
    helloworld.sayhello();
    */
  }

}

运行上面的代码,这样一个简单的jdk proxy就实现了。

代理生成过程

我们之所以天天叫jdk动态代理,是因为这个代理class是由jdk在运行时动态帮我们生成。在解释代理生成过程前,我们先把-dsun.misc.proxygenerator.savegeneratedfiles=true 这个参数加入到jvm 启动参数中,它的作用是帮我们把jdk动态生成的proxy class 的字节码保存到硬盘中,帮助我们查看具体生成proxy的内容。我用的intellij idea ,代理class生成后直接放在项目的根目录下的,以具体的包名为目录结构。

代理类生成的过程主要包括两部分:

  • 代理类字节码生成
  • 把字节码通过传入的类加载器加载到虚拟机中

proxy类的getproxyclass方法入口:需要传入类加载器和interface

然后调用getproxyclass0方法,里面的注解解释很清楚,如果实现当前接口的代理类存在,直接从缓存中返回,如果不存在,则通过proxyclassfactory来创建。这里可以明显看到有对interface接口数量的限制,不能超过65535。其中proxyclasscache具体初始化信息如下:

proxyclasscache = new weakcache<>(new keyfactory(), new proxyclassfactory());

其中创建代理类的具体逻辑是通过proxyclassfactory的apply方法来创建的。

proxyclassfactory里的逻辑包括了包名的创建逻辑,调用proxygenerator. generateproxyclass生成代理类,把代理类字节码加载到jvm。

1.包名生成逻辑默认是com.sun.proxy,如果被代理类是 non-public proxy interface ,则用和被代理类接口一样的包名,类名默认是$proxy 加上一个自增的整数值。

2.包名类名准备好后,就是通过proxygenerator. generateproxyclass根据具体传入的接口创建代理字节码,-dsun.misc.proxygenerator.savegeneratedfiles=true 这个参数就是在该方法起到作用,如果为true则保存字节码到磁盘。代理类中,所有的代理方法逻辑都一样都是调用invocationhander的invoke方法,这个我们可以看后面具体代理反编译结果。

 3.把字节码通过传入的类加载器加载到jvm中: defineclass0(loader, proxyname,proxyclassfile, 0, proxyclassfile.length);。

 private static final class proxyclassfactory
    implements bifunction<classloader, class<?>[], class<?>>
  {
    // prefix for all proxy class names
    private static final string proxyclassnameprefix = "$proxy";

    // next number to use for generation of unique proxy class names
    private static final atomiclong nextuniquenumber = new atomiclong();

    @override
    public class<?> apply(classloader loader, class<?>[] interfaces) {

      map<class<?>, boolean> interfaceset = new identityhashmap<>(interfaces.length);
      for (class<?> intf : interfaces) {
        /*
         * verify that the class loader resolves the name of this
         * interface to the same class object.
         */
        class<?> interfaceclass = null;
        try {
          interfaceclass = class.forname(intf.getname(), false, loader);
        } catch (classnotfoundexception e) {
        }
        if (interfaceclass != intf) {
          throw new illegalargumentexception(
            intf + " is not visible from class loader");
        }
        /*
         * verify that the class object actually represents an
         * interface.
         */
        if (!interfaceclass.isinterface()) {
          throw new illegalargumentexception(
            interfaceclass.getname() + " is not an interface");
        }
        /*
         * verify that this interface is not a duplicate.
         */
        if (interfaceset.put(interfaceclass, boolean.true) != null) {
          throw new illegalargumentexception(
            "repeated interface: " + interfaceclass.getname());
        }
      }

      string proxypkg = null;   // package to define proxy class in
      int accessflags = modifier.public | modifier.final;

      /*
       * record the package of a non-public proxy interface so that the
       * proxy class will be defined in the same package. verify that
       * all non-public proxy interfaces are in the same package.
       */
      //生成包名和类名逻辑
      for (class<?> intf : interfaces) {
        int flags = intf.getmodifiers();
        if (!modifier.ispublic(flags)) {
          accessflags = modifier.final;
          string name = intf.getname();
          int n = name.lastindexof('.');
          string pkg = ((n == -1) ? "" : name.substring(0, n + 1));
          if (proxypkg == null) {
            proxypkg = pkg;
          } else if (!pkg.equals(proxypkg)) {
            throw new illegalargumentexception(
              "non-public interfaces from different packages");
          }
        }
      }

      if (proxypkg == null) {
        // if no non-public proxy interfaces, use com.sun.proxy package
        proxypkg = reflectutil.proxy_package + ".";
      }

      /*
       * choose a name for the proxy class to generate.
       */
      long num = nextuniquenumber.getandincrement();
      string proxyname = proxypkg + proxyclassnameprefix + num;

      /*
       * generate the specified proxy class. 生成代理类的字节码
       * -dsun.misc.proxygenerator.savegeneratedfiles=true 在该部起作用
       */
      byte[] proxyclassfile = proxygenerator.generateproxyclass(
        proxyname, interfaces, accessflags);
      try {
        //加载到jvm中
        return defineclass0(loader, proxyname,
                  proxyclassfile, 0, proxyclassfile.length);
      } catch (classformaterror e) {
        /*
         * a classformaterror here means that (barring bugs in the
         * proxy class generation code) there was some other
         * invalid aspect of the arguments supplied to the proxy
         * class creation (such as virtual machine limitations
         * exceeded).
         */
        throw new illegalargumentexception(e.tostring());
      }
    }
  }

我们可以根据代理类的字节码进行反编译,可以得到如下结果,其中helloworld只有sayhello方法,但是代理类中有四个方法 包括了object上的三个方法:equals,tostring,hashcode。

代理的大概结构包括4部分:

  • 静态字段:被代理的接口所有方法都有一个对应的静态方法变量;
  • 静态块:主要是通过反射初始化静态方法变量;
  • 具体每个代理方法:逻辑都差不多就是 h.invoke,主要是调用我们定义好的invocatinohandler逻辑,触发目标对象target上对应的方法;
  • 构造函数:从这里传入我们invocationhandler逻辑;
package com.sun.proxy;

import com.yao.helloworld;
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 helloworld {
  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 boolean equals(object var1) throws {
    try {
      return ((boolean)super.h.invoke(this, m1, new object[]{var1})).booleanvalue();
    } catch (runtimeexception | error var3) {
      throw var3;
    } catch (throwable var4) {
      throw new undeclaredthrowableexception(var4);
    }
  }

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

  public final string tostring() throws {
    try {
      return (string)super.h.invoke(this, m2, (object[])null);
    } catch (runtimeexception | error var2) {
      throw var2;
    } catch (throwable var3) {
      throw new undeclaredthrowableexception(var3);
    }
  }

  public final int hashcode() throws {
    try {
      return ((integer)super.h.invoke(this, m0, (object[])null)).intvalue();
    } catch (runtimeexception | error var2) {
      throw var2;
    } catch (throwable var3) {
      throw new undeclaredthrowableexception(var3);
    }
  }

  static {
    try {
      m1 = class.forname("java.lang.object").getmethod("equals", new class[]{class.forname("java.lang.object")});
      m3 = class.forname("com.yao.helloworld").getmethod("sayhello", new class[0]);
      m2 = class.forname("java.lang.object").getmethod("tostring", new class[0]);
      m0 = class.forname("java.lang.object").getmethod("hashcode", new class[0]);
    } catch (nosuchmethodexception var2) {
      throw new nosuchmethoderror(var2.getmessage());
    } catch (classnotfoundexception var3) {
      throw new noclassdeffounderror(var3.getmessage());
    }
  }
}

常见问题:

1.tostring() hashcode() equal()方法 调用逻辑:这个三个object上的方法,如果被调用将和其他接口方法方法处理逻辑一样,都会经过invocationhandler逻辑,从上面的字节码结果就可以明显看出。其他object上的方法将不会走代理处理逻辑,直接走proxy继承的object上方法逻辑。

2.interface 含有equals,tostring hashcode方法时,和处理普通接口方法一样,都会走invocation handler逻辑,以目标对象重写的逻辑为准去触发方法逻辑;

3.interface含有重复的方法签名,以接口传入顺序为准,谁在前面就用谁的方法,代理类中只会保留一个,不会有重复的方法签名;

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网