JVM方法句柄

JVM方法句柄

方法句柄是一个强类型的,能够被直接执行的引用。该引用可以指向常规的静态方法或者实例方法,也可以指向构造器或者字段。当指向字段时,方法句柄实则指向包含字段访问字节码的虚构方法,语义上等价于目标字段的 getter 或者 setter 方法

方法句柄的类型(MethodType)是由所指向方法的参数类型以及返回类型组成的。它是用来确认方法句柄是否适配的唯一关键。当使用方法句柄时,我们其实并不关心方法句柄所指向方法的类名或者方法名

方法句柄的创建是通过 MethodHandles.Lookup 类来完成的。它提供了多个 API,既可以使用反射 API 中的 Method 来查找,也可以根据类、方法名以及方法句柄类型来查找

当使用后者这种查找方式时(方法句柄类型),用户需要区分具体的调用类型,比如说对于用 invokestatic 调用的静态方法,我们需要使用 Lookup.findStatic 方法;对于用 invokevirutal 调用的实例方法,以及用 invokeinterface 调用的接口方法,我们需要使用 findVirtual 方法;对于用 invokespecial 调用的实例方法,我们则需要使用 findSpecial 方法。

具体操作

方法类型

一个 Java 方法可以视为由四个基本内容所构成:

  • 名称
  • 签名(包含返回类型)
  • 定义它的类
  • 实现方法的字节码

方法句柄首先需要的一个构建块就是表达方法签名的方式,以便于查找。在 Java 7 引入的 Method Handles API 中,这个角色是由 java.lang.invoke.MethodType 类来完成的,它使用一个不可变的实例来代表签名。要获取 MethodType,我们可以使用 methodType() 工厂方法。这是一个参数可变(variadic)的方法,以 class 对象作为参数。

第一个参数所使用的 class 对象,对应着签名的返回类型;剩余参数中所使用的 class 对象,对应着签名中方法参数的类型。例如:


//toString() 的签名 
MethodType mtToString = MethodType.methodType(String.class);

// setter 方法的签名 
MethodType mtSetter = MethodType.methodType(void.class, Object.class);

// Comparator 中 compare() 方法的签名 
MethodType mtStringComparator = MethodType.methodType(int.class, String.class)

现在可以使用 MethodType,再组合方法名称以及定义方法的类来查找方法句柄。要实现这一点,我们需要调用静态的 MethodHandles.lookup() 方法。这样的话,会给我们一个“查找上下文(lookup context)”,这个上下文基于当前正在执行的方法(也就是调用 lookup() 的方法)的访问权限。

查找上下文对象有一些以“find”开头的方法,例如,findVirtual()、findConstructor()、findStatic() 等。这些方法将会返回实际的方法句柄,需要注意的是,只有在创建查找上下文的方法能够访问(调用)被请求方法的情况下,才会返回句柄。这与反射不同,我们没有办法绕过访问控制。换句话说,方法句柄中并没有与 setAccessible() 对应的方法。

通过MethodHandle进行方法调用一般需要以下几步:

  • (1)创建MethodType对象,指定方法的签名;

  • (2)在MethodHandles.Lookup中查找类型为MethodType的MethodHandle;

  • (3)传入方法参数并调用MethodHandle.invoke或者MethodHandle.invokeExact方法。

MethodType

可以通过MethodHandle类的type方法查看其类型,返回值是MethodType类的对象。也可以在得到MethodType对象之后,调用MethodHandle.asType(mt)方法适配得到MethodHandle对象。可以通过调用MethodType的静态方法创建MethodType实例,有三种创建方式:

  • (1)methodType及其重载方法:需要指定返回值类型以及0到多个参数;

  • (2)genericMethodType:需要指定参数的个数,类型都为Object;

  • (3)fromMethodDescriptorString:通过方法描述来创建。

Lookup

MethodHandle.Lookup相当于MethodHandle工厂类,通过findxxx方法可以得到相应的MethodHandle

几个 MethodHandle 方法与字节码的对应:

  • findStatic
    对应字节码:invokestatic
    调用静态方法
  • findSpecial
    对应字节码:invokespecial
    调用实例构造方法,私有方法,父类方法
  • findVirtual
    对应字节码:invokevirtual
    调用所有的虚方法

  • findVirtual
    对应字节码:invokeinterface
    调用接口方法,会在运行时再确定一个实现此接口的对象

invoke

在得到MethodHandle后就可以进行方法调用了,有三种调用形式:

  • (1)invokeExact:调用此方法与直接调用底层方法一样,需要做到参数类型精确匹配;

  • (2)invoke:参数类型松散匹配,通过asType自动适配;

  • (3)invokeWithArguments:直接通过方法参数来调用。其实现是先通genericMethodType方法得到MethodType,再通过MethodHandle的asType转换后得到一个新的MethodHandle,最后通过新MethodHandle的invokeExact方法来完成调用。
官方文档例子

public class examples {

    public static void main(String[] args) throws Throwable {


        Object x, y; String s; int i;
        MethodType mt; MethodHandle mh;
        MethodHandles.Lookup lookup = MethodHandles.lookup();

// mt is (char,char)String
        mt = MethodType.methodType(String.class, char.class, char.class);
        mh = lookup.findVirtual(String.class, "replace", mt);
        s = (String) mh.invokeExact("daddy",'d','n');


// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
//        assertEquals(s, "nanny");
        System.out.println(s);
        System.out.println("-----------------------------");



// weakly typed invocation (using MHs.invoke)
        s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
//        assertEquals(s, "savvy");
        System.out.println(s);
        System.out.println("-----------------------------");



// mt is (Object[])List
        mt = MethodType.methodType(java.util.List.class, Object[].class);
        mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
        assert(mh.isVarargsCollector());
        x = mh.invoke("one", "two");


// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
//        assertEquals(x, java.util.Arrays.asList("one","two"));
        System.out.println(x);
        System.out.println("-----------------------------");


// mt is (Object,Object,Object)Object
        mt = MethodType.genericMethodType(3);
        mh = mh.asType(mt);
        x = mh.invokeExact((Object)1, (Object)2, (Object)3);


// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;


//        assertEquals(x, java.util.Arrays.asList(1,2,3));
        System.out.println(x);
        System.out.println("-----------------------------");



// mt is ()int
        mt = MethodType.methodType(int.class);
        mh = lookup.findVirtual(java.util.List.class, "size", mt);
        i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));



// invokeExact(Ljava/util/List;)I
        assert(i == 3);
        mt = MethodType.methodType(void.class, String.class);
        mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
        mh.invokeExact(System.out, "Hello, world.");


// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V

    }
}


MethodHandle 与 Method 区别

  • MethodHandle 在模拟 字节码 层次的方法调用,因而可适用于所有 JVM 语言 ;
  • Reflection 在模拟 Java 层次的方法调用,仅可适用于 Java。

  • MethodHandle 可进行 JVM 的内联优化,Reflection 屏蔽了 JVM ,所以完全没有相应的优化。

  • MethodHandle 从 JVM 层次支持调用,只需要包含方法必要的信息,所以说是轻量级的,而 Reflection 是 Java Api 层次的反射调用,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含有执行权限等的运行期信息,所以说是重量级的。
  • MethodHandle 方法调用需要考虑到 字节码,而 Reflection 则不用考虑这些。

参考文章

https://www.infoq.cn/article/Invokedynamic-Javas-secret-weapon

https://segmentfault.com/a/1190000017208820

https://www.zhihu.com/question/40427344/answer/252825611

你可能感兴趣的:(JVM方法句柄)