动态方法调用


java7增加了一个invokedynamic的指令,这个命令是为了更好地支持JVM平台上的动态语言和lambda表达式。在给出一个使用示例时,我们先看四个概念
  • 方法句柄(MethodHandle):这个很像一个方法的代理,通过它就可以调用一个方法。(class文件常量池中的CONSTANT_MethodHandle就是方法句柄)
  • 调用点(CallSite):这个是对方法句柄的一个封装,通过在变调用点上设置不同的方法句柄就可以调用不同的方法
  • 启动方法(BootstrapMethods):通过启动方法可以获得调用点
  • 方法类型(MethodType):主要用于设置方法的参数和返回值

下面我们看一下方法句柄的使用

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
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.time.LocalDateTime;

public class TestMethodHandler {

public static void main(String[] args) throws Throwable {
// specify method's parameter and return types
MethodType methodType = MethodType.methodType(void.class);

// invoke static method
MethodHandle printHelloWorld = MethodHandles.lookup().findStatic(TestMethodHandler.class, "printHelloWorld", methodType);
printHelloWorld.invoke();

// invoke instance method
TestMethodHandler testMethodHandler = new TestMethodHandler();
MethodHandle printTime = MethodHandles.lookup().findVirtual(TestMethodHandler.class, "printTime", methodType).bindTo(testMethodHandler);
printTime.invoke();

}

public static void printHelloWorld() {
System.out.println("Hello World!");
}

public void printTime() {
System.out.println(LocalDateTime.now().toString());
}
}

结果为

1
2
Hello World!
2016-07-24T01:05:31.010

下面我们依次看一下上面的例子中使用的方法

  • findStatic(): 查找一个static方法,然后使用invokestatic进行函数调用
  • findVirtual(): 查找一个虚方法,使用invokevirtual指令进行函数调用
  • findSpecial(): 查找私有方法或者父类的方法,使用invokespecial指令进行调用
  • findConstructor():查找构造器方法

下面我们接着看一下调用点,调用点一共分为3种

  • 常量调用点
  • 可变调用点
  • 易变调用点

我们接着看一下常量调用点的示例

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
import java.lang.invoke.*;
import java.time.LocalDateTime;

public class TestCallSite {

public static void main(String[] args) throws Throwable {
// specify method's parameter and return types
MethodType methodType = MethodType.methodType(void.class);

// invoke static method
MethodHandle printHelloWorld = MethodHandles.lookup().findStatic(TestCallSite.class, "printHelloWorld", methodType);

CallSite callSite = new ConstantCallSite(printHelloWorld);
MethodHandle invoker = callSite.dynamicInvoker();
invoker.invoke();

MethodHandle printTime = MethodHandles.lookup().findStatic(TestCallSite.class, "printTime", methodType);
callSite.setTarget(printTime);
invoker.invoke();
}

public static void printHelloWorld() {
System.out.println("Hello World!");
}

public static void printTime() {
System.out.println(LocalDateTime.now().toString());
}
}

结果为

1
2
3
4
5
6
7
8
9
Exception in thread "main" java.lang.UnsupportedOperationException
at java.lang.invoke.ConstantCallSite.setTarget(ConstantCallSite.java:106)
at TestCallSite.main(TestCallSite.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Hello World!

首先我们发现,当再一个常量调用点上进行重置target的时候,产生了一个异常,那么我们使用可变调用点试试呢?

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
import java.lang.invoke.*;
import java.time.LocalDateTime;

public class TestCallSite {

public static void main(String[] args) throws Throwable {
// specify method's parameter and return types
MethodType methodType = MethodType.methodType(void.class);

// invoke static method
MethodHandle printHelloWorld = MethodHandles.lookup().findStatic(TestCallSite.class, "printHelloWorld", methodType);

CallSite callSite = new MutableCallSite(printHelloWorld);
MethodHandle invoker = callSite.dynamicInvoker();
invoker.invoke();

MethodHandle printTime = MethodHandles.lookup().findStatic(TestCallSite.class, "printTime", methodType);
callSite.setTarget(printTime);
invoker.invoke();
}

public static void printHelloWorld() {
System.out.println("Hello World!");
}

public static void printTime() {
System.out.println(LocalDateTime.now().toString());
}
}

结果为

1
2
Hello World!
2016-07-24T01:18:02.905