《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄

1.方法句柄出现的原因

某个国家举办了跑步比赛,有亚洲,欧洲还是非洲人参赛,但是有机器人也参赛了。机器人不属于人类阵营,怎么能让机器人也参加进来呢?


interface Human{
    void race();
}

class Asian implements Human{

   public void race(){
       System.out.println("asian running");
   } ;

}

class European implements Human{

    public void race(){
        System.out.println("european running");
    }

}

class African implements Human{
    public void race(){
        System.out.println("african running");
    }
}

class Robot{

    public void race(){
        System.out.println("robot running");
    }
}

通常我们的做法是创建一个包装类,将Robot的race方法也包装一下,如下:

class SpecialHuman implements Human{

    private Robot robot;


    @Override
    public void race() {
        robot.race();
    }
}

或者可以利用反射,去调用每个类中的race方法

方式一:调用方式

 //方式一:包装
        Asian asian = new Asian();
        European european = new European();
        African african = new African();

        //参赛选手集合
        ArrayList<Human> humans = new ArrayList<>();
        humans.add(african);
        humans.add(european);
        humans.add(asian);
        Robot robot = new Robot();
        SpecialHuman specialHuman = new SpecialHuman();
        specialHuman.setRobot(robot);
        humans.add(specialHuman);

        for (Human human : humans) {
            human.race();
        }

方式二:反射调用方法

 //方法二
        Asian asian = new Asian();
        European european = new European();
        African african = new African();
        Robot robot = new Robot();

        ArrayList<Object> objectArrayList = new ArrayList<>();

        objectArrayList.add(african);
        objectArrayList.add(european);
        objectArrayList.add(asian);
        objectArrayList.add(robot);

        for (Object o : objectArrayList) {
            Class<?> aClass = o.getClass();
            Method race = aClass.getMethod("race");
            race.invoke(o);
        }

比起直接调用,这两种方式都比较复杂,性能更加低效。为了解决这个问题,从Java7开始,引入了动态调用方法的invokedynamic。该指令的调用机制抽象出调用点这一个概念,并允许应用程序将调用点链接至任意符合条件的方法上。

作为 invokedynamic 的准备工作,Java 7 引入了更加底层、更加灵活的方法抽象 :方法句柄(MethodHandle)。

2.什么是方法句柄

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

方法句柄 由 方法的形参列表和返回值类型进行匹配,与方法名和类名无关。

方法句柄的访问权限由LookUp的创建位置决定,与句柄的创建位置无关。在执行时不会被权限修饰符限制。如下的代码,LookUp创建在一个静态的public的方法中,因此Foo类相当于裸奔了,可以随时随地的调用bar方法,并且无需开启暴力反射

class Foo {
  private static void bar(Object o) {
    ..
  }
  public static Lookup lookup() {
    return MethodHandles.lookup();
  }
}

// 获取方法句柄的不同方式
MethodHandles.Lookup l = Foo.lookup(); // 具备Foo类的访问权限
Method m = Foo.class.getDeclaredMethod("bar", Object.class);
MethodHandle mh0 = l.unreflect(m);

MethodType t = MethodType.methodType(void.class, Object.class);
MethodHandle mh1 = l.findStatic(Foo.class, "bar", t);

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄-LMLPHP

3.方法句柄的操作

句柄的调用

严格匹配参数类型的 invokeExact

方法句柄的调用可分为两种,一是需要严格匹配参数类型的 invokeExact。它有多严格呢?假设一个方法句柄将接收一个 Object 类型的参数,如果你直接传入 String 作为实际参数,那么方法句柄的调用会在运行时抛出方法类型不匹配的异常。正确的调用方式是将该 String 显式转化为 Object 类型。

比如还是上述的代码,我们把invoke改成invokeExact方法,执行就会报错,会告诉你参数期望类型不一致


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

class Foo {
        private static void bar(Object o) {
            System.out.println("bar 执行了");
        }
        public static MethodHandles.Lookup lookup() {
            return MethodHandles.lookup();
        }
    }

class Main2{

    public static void main(String[] args) throws Throwable {
        //获取方法对象
        Class<Foo> fooClass = Foo.class;
        Method bar = fooClass.getDeclaredMethod("bar", Object.class);

        //获取方法句柄
        MethodHandles.Lookup lookup = Foo.lookup();
        MethodHandle unreflect = lookup.unreflect(bar);
        unreflect.invokeExact(new String());
    }

}

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄-LMLPHP

跟进方法会发现此本地方法的方法声明上有个@PolymorphicSignature注解。在碰到被它注解的方法调用时,Java 编译器会根据所传入参数的声明类型来生成方法描述符,而不是采用目标方法所声明的描述符。

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄-LMLPHP

如下代码的字节码所示


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

class Foo {
        private static void bar(Object o) {
            System.out.println("bar 执行了");
        }
        public static MethodHandles.Lookup lookup() {
            return MethodHandles.lookup();
        }
    }

class Main2{

    public static void main(String[] args) throws Throwable {
        //获取方法对象
        Class<Foo> fooClass = Foo.class;
        Method bar = fooClass.getDeclaredMethod("bar", Object.class);

        //获取方法句柄
        MethodHandles.Lookup lookup = Foo.lookup();
        MethodHandle unreflect = lookup.unreflect(bar);
        unreflect.invokeExact(new String());
        unreflect.invokeExact(new String());
    }

}

 0 ldc #2 <Foo>
 2 astore_1
 3 aload_1
 4 ldc #3 <bar>
 6 iconst_1
 7 anewarray #4 <java/lang/Class>
10 dup
11 iconst_0
12 ldc #5 <java/lang/Object>
14 aastore
15 invokevirtual #6 <java/lang/Class.getDeclaredMethod : (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;>
18 astore_2
19 invokestatic #7 <Foo.lookup : ()Ljava/lang/invoke/MethodHandles$Lookup;>
22 astore_3
23 aload_3
24 aload_2
25 invokevirtual #8 <java/lang/invoke/MethodHandles$Lookup.unreflect : (Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;>
28 astore 4
30 aload 4
32 new #9 <java/lang/String>
35 dup
36 invokespecial #10 <java/lang/String.<init> : ()V>
39 invokevirtual #11 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/String;)V>
42 aload 4
44 new #5 <java/lang/Object>
47 dup
48 invokespecial #1 <java/lang/Object.<init> : ()V>
51 invokevirtual #12 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/Object;)V>
54 return

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄-LMLPHP

同时我们也能发现,方法句柄调用的指令是 invokevirtual,其实这个指令与原方法调用指令是一致的,此处是调用实列方法,因此为 invokevirtual

自动适配参数类型的 invoke

如果你需要自动适配参数类型,那么你可以选取方法句柄的第二种调用方式 invoke。它同样是一个签名多态性的方法。invoke 会调用 MethodHandle.asType 方法,生成一个适配器方法句柄,对传入的参数进行适配,再调用原方法句柄。调用原方法句柄的返回值同样也会先进行适配,然后再返回给调用者。

4.方法句柄调用目标方法

改写第一个比赛跑步代码

import jdk.nashorn.internal.runtime.Specialization;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Objects;

interface Human {
    void race();
}

class Asian implements Human {

    public void race() {
        System.out.println("asian running");
    };

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }

}

class European implements Human {

    public void race() {
        System.out.println("european running");
    }

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class African implements Human {
    public void race() {
        System.out.println("african running");
    }
    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class Robot {

    public void race() {
        System.out.println("robot running");
    }

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class SpecialHuman implements Human {

    private Robot robot;

    public Robot getRobot() {
        return robot;
    }

    public void setRobot(Robot robot) {
        this.robot = robot;
    }

    @Override
    public void race() {
        robot.race();
    }
}

public class RunningRace {

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

            //拿到所有的Lookup,创建方法句柄
        MethodHandles.Lookup lookup1 = Asian.lookup();
        MethodHandles.Lookup lookup2 = European.lookup();
        MethodHandles.Lookup lookup3 = African.lookup();
        MethodHandles.Lookup lookup4 = Robot.lookup();

        Asian asian = new Asian();
        European european = new European();
        African african = new African();
        Robot robot = new Robot();

         lookup1.findVirtual(Asian.class, "race", MethodType.methodType(void.class)).invoke(asian);
        lookup2.findVirtual(European.class,"race", MethodType.methodType(void.class)).invoke(european);
        lookup3.findVirtual(African.class,"race", MethodType.methodType(void.class)).invoke(african);
        lookup4.findVirtual(Robot.class,"race", MethodType.methodType(void.class)).invoke(robot);

    }

}

05-14 17:59