invoke dynamic的学习与应用

MethodHandle实现动态调用

下面这段代码基本上是从《深入理解java虚拟机》中拷贝出来的,在这里主要想说一下动态调用的理解。动态调用与面向对象中的多态很像,只不过多态在jvm层面并不是动态调用,而是基于虚方法表实现的。 jvm层面中的动态调用,就是只有在程序运行时,才知道最终用的哪个方法。就像下面的代码,根据当前的机器时钟是否是4的倍数,确定时用System.out的println还是自己写的println。 熟悉反射的同学,可能觉得这里的代码跟反射太像了,确实跟反射很像,但是与反射还是有很大的差别。反射是获取类的定义,进而找到方法并执行,反射中包括了很多类和方法的描述信息,但是methodHandle却不包含这些信息。methodHandle是jvm新增指令InvokeDynamic在java代码层面的实现,InvokeDynamic指令是通过Dynamic Call Site 动态调用点来执行方法,因此可以简单的理解为:methodHandle和Dynamic call site实际上就是包含了某一个真实方法执行所需的核心参数的封装对象,这些核心参数主要包括:方法的真实地址(或者方法所属真实对象),参数和返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hplegend.jvm;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;

import static java.lang.invoke.MethodHandles.lookup;

/**
* @author hplegend
* @date 2019/11/9 14:10
*/
public class InvokeDynamicTestOne {

public static class ClassA {
public void println(String msg) {
System.out.println("InvokeDynamicTestOne.ClassA.println: " + msg);
}
}

public static void main(String[] args) throws Throwable {
Object outObj = System.currentTimeMillis() % 4 == 0 ? System.out : new ClassA();
MethodHandle methodHandle = null;
{
MethodType mt = MethodType.methodType(void.class, String.class);
methodHandle = lookup().findVirtual(outObj.getClass(), "println", mt).bindTo(outObj);
}
methodHandle.invokeExact("hplegend");
}
}

通过动态调用我们能干什么呢? 最简单的就是改造多态调用,例如我们在一个绘图程序的draw方法中,根据传入的不同对象实现不同形状的绘制,那么如果我们改成传入不同的形状参数,动态的去寻找对于对象的methodHandle来实现多态性? 看看下面的代码(当然只是举例子,不要较真):

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.hplegend.jvm;

/**
* @author hplegend
* @date 2019/11/9 14:48
*/
public class Circle {

public void draw() {
System.out.println("I'am a circle");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
package com.hplegend.jvm;

/**
* @author hplegend
* @date 2019/11/9 14:48
*/
public class Rectangle {

public void draw() {
System.out.println("I'am a rectangle");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.hplegend.jvm;

/**
* @author hplegend
* @date 2019/11/9 14:48
*/
public class Square {

public void draw() {
System.out.println("I'am a square");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.hplegend.jvm;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.HashMap;
import java.util.Scanner;

import static java.lang.invoke.MethodHandles.lookup;

/**
* @author hplegend
* @date 2019/11/9 14:50
*/
public class DynamicImplement {

private static final String EXIT_CMD = "exit";

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

HashMap<String, Object> nameObjectMap = new HashMap<>(3);
nameObjectMap.put("circle", new Circle());
nameObjectMap.put("square", new Square());
nameObjectMap.put("rect", new Rectangle());

Scanner scanner = new Scanner(System.in);
String shapeCmd;
while (!EXIT_CMD.equals(shapeCmd = scanner.nextLine())) {

Object shapeExe = nameObjectMap.get(shapeCmd);
MethodHandle methodHandle = null;
{
MethodType mt = MethodType.methodType(void.class);
methodHandle = lookup().findVirtual(shapeExe.getClass(), "draw", mt).bindTo(shapeExe);
}
methodHandle.invokeExact();
}
}

}

输出的测试结果

image-20191109145723730

上面这个例子的目的当然不是说多态不好,只是从动态调用的角度说明可以由程序员自己定义该用谁的哪个方法。