欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-09 13:18:29
jdk动态代理详解 本文主要介绍jdk动态代理的基本原理,让大家更深刻的理解jdk proxy,知其然知其所以然。明白jdk动态代理真正的原理及其生成的过程,我们以后写j...

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

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

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

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

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

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

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含有重复的方法签名,以接口传入顺序为准,谁在前面就用谁的方法,代理类中只会保留一个,不会有重复的方法签名;

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