Scripting Java #3:Groovy与invokedynamic

时间:2021-02-12 16:11:32

只需看看今天Groovy语言实现机制。在此之前,是第一个推倒静态类型动态类型语言在实现上面的一些差异。

静态类型 vs. 动态类型

看以下这个简单的栗子。

def addtwo(a, b) {
return a + b;
}

静态类型语言与动态类型语言对于上面这个简单的加法实现全然不同。静态类型语言。比如Java。语言的编译器在编译时就已经进行类型检查,所以能够将+运算符编译成特定的指令。语言的runtime系统能够直接执行该指令。比如javac会将两个int类型的+运算编译成iadd指令,执行时由JVM直接执行iadd指令。

而对于动态类型语言,因为须要到执行时才干确定变量的类型,因此运算符的详细实现也须要到执行时才干确定。a + b会被编译成相似(+ a b)这个方案调用(Lisp风格^_^)。+仅仅是个方法名。语言的runtime系统须要依据方法名(+)和參数类型(ab的类型)来确定这个加法运算的详细实现。

The challenge of compiling dynamically typed languages is how to implement a runtime system that can choose the most appropriate implementation of a method or function — after the program has been compiled.

总而言之,言而总之。静态类型语言苦了编译器爽了执行时。动态类型语言爽了编译器苦了执行时。

既然是这样,那以下我们就来看看Groovy的编译器(groovyc)是怎么苦了Groovy的runtime系统的。

invokedynamic之前

使用groovyc编译上面的栗子。得到class文件。javap看下字节码,

> groovyc demo.groovy
> javap -v -p demo
Classfile /C:/Users/tongyuan.zbs/demo.class
Last modified 2015-3-7; size 2287 bytes
MD5 checksum ee25ddebc1ef5ab750baebf75f8031b6
Compiled from "demo.groovy"
public class demo extends groovy.lang.Script
SourceFile: "demo.groovy"
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER Constant pool:
#1 = Utf8 demo
#2 = Class #1 // demo
#3 = Utf8 groovy/lang/Script
#4 = Class #3 // groovy/lang/Script
#5 = Utf8 demo.groovy
#6 = Utf8 $staticClassInfo
#7 = Utf8 Lorg/codehaus/groovy/reflection/ClassInfo;
#8 = Utf8 __$stMC
#9 = Utf8 Z
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = NameAndType #10:#11 // "<init>":()V
#13 = Methodref #4.#12 // groovy/lang/Script."<init>":()V
#14 = Utf8 $getCallSiteArray
#15 = Utf8 ()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
#16 = NameAndType #14:#15 // $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
#17 = Methodref #2.#16 // demo.$getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
#18 = Utf8 this
#19 = Utf8 Ldemo;
#20 = Utf8 (Lgroovy/lang/Binding;)V
#21 = NameAndType #10:#20 // "<init>":(Lgroovy/lang/Binding;)V
#22 = Methodref #4.#21 // groovy/lang/Script."<init>":(Lgroovy/lang/Binding;)V
#23 = Utf8 context
#24 = Utf8 Lgroovy/lang/Binding;
#25 = Utf8 main
#26 = Utf8 ([Ljava/lang/String;)V
#27 = Integer 0
#28 = Utf8 org/codehaus/groovy/runtime/InvokerHelper
#29 = Class #28 // org/codehaus/groovy/runtime/InvokerHelper
#30 = Utf8 org/codehaus/groovy/runtime/callsite/CallSite
#31 = Class #30 // org/codehaus/groovy/runtime/callsite/CallSite
#32 = Utf8 call
#33 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#34 = NameAndType #32:#33 // call:(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#35 = InterfaceMethodref #31.#34 // org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#36 = Utf8 args
#37 = Utf8 [Ljava/lang/String;
#38 = Utf8 run
#39 = Utf8 ()Ljava/lang/Object;
#40 = Utf8 addtwo
#41 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#42 = Integer 1
#43 = NameAndType #32:#41 // call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#44 = InterfaceMethodref #31.#43 // org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#45 = Utf8 a
#46 = Utf8 Ljava/lang/Object;
#47 = Utf8 b
#48 = Utf8 $getStaticMetaClass
#49 = Utf8 ()Lgroovy/lang/MetaClass;
#50 = Utf8 java/lang/Object
#51 = Class #50 // java/lang/Object
#52 = Utf8 getClass
#53 = Utf8 ()Ljava/lang/Class;
#54 = NameAndType #52:#53 // getClass:()Ljava/lang/Class;
#55 = Methodref #51.#54 // java/lang/Object.getClass:()Ljava/lang/Class;
#56 = Utf8 org/codehaus/groovy/runtime/ScriptBytecodeAdapter
#57 = Class #56 // org/codehaus/groovy/runtime/ScriptBytecodeAdapter
#58 = Utf8 initMetaClass
#59 = Utf8 (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
#60 = NameAndType #58:#59 // initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
#61 = Methodref #57.#60 // org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
#62 = NameAndType #6:#7 // $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
#63 = Fieldref #2.#62 // demo.$staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
#64 = Utf8 org/codehaus/groovy/reflection/ClassInfo
#65 = Class #64 // org/codehaus/groovy/reflection/ClassInfo
#66 = Utf8 getClassInfo
#67 = Utf8 (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
#68 = NameAndType #66:#67 // getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
#69 = Methodref #65.#68 // org/codehaus/groovy/reflection/ClassInfo.getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
#70 = Utf8 getMetaClass
#71 = NameAndType #70:#49 // getMetaClass:()Lgroovy/lang/MetaClass;
#72 = Methodref #65.#71 // org/codehaus/groovy/reflection/ClassInfo.getMetaClass:()Lgroovy/lang/MetaClass;
#73 = Utf8 $callSiteArray
#74 = Utf8 Ljava/lang/ref/SoftReference;
#75 = Utf8 $createCallSiteArray_1
#76 = Utf8 runScript
#77 = String #76 // runScript
#78 = Utf8 plus
#79 = String #78 // plus
#80 = Utf8 $createCallSiteArray
#81 = Utf8 ()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;
#82 = Integer 2
#83 = Utf8 java/lang/String
#84 = Class #83 // java/lang/String
#85 = NameAndType #75:#26 // $createCallSiteArray_1:([Ljava/lang/String;)V
#86 = Methodref #2.#85 // demo.$createCallSiteArray_1:([Ljava/lang/String;)V
#87 = Utf8 org/codehaus/groovy/runtime/callsite/CallSiteArray
#88 = Class #87 // org/codehaus/groovy/runtime/callsite/CallSiteArray
#89 = Utf8 (Ljava/lang/Class;[Ljava/lang/String;)V
#90 = NameAndType #10:#89 // "<init>":(Ljava/lang/Class;[Ljava/lang/String;)V
#91 = Methodref #88.#90 // org/codehaus/groovy/runtime/callsite/CallSiteArray."<init>":(Ljava/lang/Class;[Ljava/lang/String;)V
#92 = NameAndType #73:#74 // $callSiteArray:Ljava/lang/ref/SoftReference;
#93 = Fieldref #2.#92 // demo.$callSiteArray:Ljava/lang/ref/SoftReference;
#94 = Utf8 java/lang/ref/SoftReference
#95 = Class #94 // java/lang/ref/SoftReference
#96 = Utf8 get
#97 = NameAndType #96:#39 // get:()Ljava/lang/Object;
#98 = Methodref #95.#97 // java/lang/ref/SoftReference.get:()Ljava/lang/Object;
#99 = NameAndType #80:#81 // $createCallSiteArray:()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;
#100 = Methodref #2.#99 // demo.$createCallSiteArray:()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;
#101 = Utf8 (Ljava/lang/Object;)V
#102 = NameAndType #10:#101 // "<init>":(Ljava/lang/Object;)V
#103 = Methodref #95.#102 // java/lang/ref/SoftReference."<init>":(Ljava/lang/Object;)V
#104 = Utf8 array
#105 = Utf8 [Lorg/codehaus/groovy/runtime/callsite/CallSite;
#106 = NameAndType #104:#105 // array:[Lorg/codehaus/groovy/runtime/callsite/CallSite;
#107 = Fieldref #88.#106 // org/codehaus/groovy/runtime/callsite/CallSiteArray.array:[Lorg/codehaus/groovy/runtime/callsite/CallSite;
#108 = Utf8 Code
#109 = Utf8 LocalVariableTable
#110 = Utf8 LineNumberTable
#111 = Utf8 SourceFile
{
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC public static transient boolean __$stMC;
flags: ACC_PUBLIC, ACC_STATIC, ACC_TRANSIENT, ACC_SYNTHETIC private static java.lang.ref.SoftReference $callSiteArray;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC public demo();
flags: ACC_PUBLIC Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokespecial #13 // Method groovy/lang/Script."<init>":()V
4: invokestatic #17 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
7: astore_1
8: return
LocalVariableTable:
Start Length Slot Name Signature
4 4 0 this Ldemo; public demo(groovy.lang.Binding);
flags: ACC_PUBLIC Code:
stack=2, locals=3, args_size=2
0: invokestatic #17 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_2
4: aload_0
5: aload_1
6: invokespecial #22 // Method groovy/lang/Script."<init>":(Lgroovy/lang/Binding;)V
9: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Ldemo;
0 9 1 context Lgroovy/lang/Binding; public static void main(java.lang.String...);
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code:
stack=4, locals=2, args_size=1
0: invokestatic #17 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_1
4: aload_1
5: ldc #27 // int 0
7: aaload
8: ldc #29 // class org/codehaus/groovy/runtime/InvokerHelper
10: ldc #2 // class demo
12: aload_0
13: invokeinterface #35, 4 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: pop
19: return
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String; public java.lang.Object run();
flags: ACC_PUBLIC Code:
stack=1, locals=2, args_size=1
0: invokestatic #17 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_1
4: aconst_null
5: areturn
6: aconst_null
7: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Ldemo; public java.lang.Object addtwo(java.lang.Object, java.lang.Object);
flags: ACC_PUBLIC Code:
stack=3, locals=4, args_size=3
0: invokestatic #17 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_3
4: aload_3
5: ldc #42 // int 1
7: aaload
8: aload_1
9: aload_2
10: invokeinterface #44, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
15: areturn
16: aconst_null
17: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Ldemo;
0 16 1 a Ljava/lang/Object;
0 16 2 b Ljava/lang/Object;
LineNumberTable:
line 2: 4 protected groovy.lang.MetaClass $getStaticMetaClass();
flags: ACC_PROTECTED, ACC_SYNTHETIC Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokevirtual #55 // Method java/lang/Object.getClass:()Ljava/lang/Class;
4: ldc #2 // class demo
6: if_acmpeq 14
9: aload_0
10: invokestatic #61 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
13: areturn
14: getstatic #63 // Field $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
17: astore_1
18: aload_1
19: ifnonnull 34
22: aload_0
23: invokevirtual #55 // Method java/lang/Object.getClass:()Ljava/lang/Class;
26: invokestatic #69 // Method org/codehaus/groovy/reflection/ClassInfo.getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
29: dup
30: astore_1
31: putstatic #63 // Field $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
34: aload_1
35: invokevirtual #72 // Method org/codehaus/groovy/reflection/ClassInfo.getMetaClass:()Lgroovy/lang/MetaClass;
38: areturn private static void $createCallSiteArray_1(java.lang.String[]);
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code:
stack=3, locals=1, args_size=1
0: aload_0
1: ldc #27 // int 0
3: ldc #77 // String runScript
5: aastore
6: aload_0
7: ldc #42 // int 1
9: ldc #79 // String plus
11: aastore
12: return private static org.codehaus.groovy.runtime.callsite.CallSiteArray $createCallSiteArray();
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code:
stack=4, locals=1, args_size=0
0: ldc #82 // int 2
2: anewarray #84 // class java/lang/String
5: astore_0
6: aload_0
7: invokestatic #86 // Method $createCallSiteArray_1:([Ljava/lang/String;)V
10: new #88 // class org/codehaus/groovy/runtime/callsite/CallSiteArray
13: dup
14: ldc #2 // class demo
16: aload_0
17: invokespecial #91 // Method org/codehaus/groovy/runtime/callsite/CallSiteArray."<init>":(Ljava/lang/Class;[Ljava/lang/String;)V
20: areturn private static org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray();
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code:
stack=3, locals=1, args_size=0
0: getstatic #93 // Field $callSiteArray:Ljava/lang/ref/SoftReference;
3: ifnull 20
6: getstatic #93 // Field $callSiteArray:Ljava/lang/ref/SoftReference;
9: invokevirtual #98 // Method java/lang/ref/SoftReference.get:()Ljava/lang/Object;
12: checkcast #88 // class org/codehaus/groovy/runtime/callsite/CallSiteArray
15: dup
16: astore_0
17: ifnonnull 35
20: invokestatic #100 // Method $createCallSiteArray:()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;
23: astore_0
24: new #95 // class java/lang/ref/SoftReference
27: dup
28: aload_0
29: invokespecial #103 // Method java/lang/ref/SoftReference."<init>":(Ljava/lang/Object;)V
32: putstatic #93 // Field $callSiteArray:Ljava/lang/ref/SoftReference;
35: aload_0
36: getfield #107 // Field org/codehaus/groovy/runtime/callsite/CallSiteArray.array:[Lorg/codehaus/groovy/runtime/callsite/CallSite;
39: areturn
}

人肉反编译,大概是这个样子来使用Groovy的runtime的,

    private static void addtwo(Object o1, Object o2) throws Throwable {
String[] names = new String[]{"plus"};
CallSiteArray callSiteArray = new CallSiteArray(Main.class, names);
CallSite callSite = callSiteArray.array[0];
System.out.println(callSite.call(o1, o2));
}

plus就是我们上面说到的方法名+

写个栗子跑跑看,

    public static void main(String[] args) throws Throwable {
addtwo(7, 7);
addtwo("hello,", "world");
addtwo(new Receiver(), new Parameter());
}
public class Receiver implements GroovyInterceptable {

    @Override
public Object invokeMethod(String name, Object args) {
System.out.println("methodName->" + name);
System.out.println("args->" + args);
if(args instanceof Object[]) {
Object[] params = (Object[])args;
System.out.println("params->");
for (Object param : params) {
System.out.println(param);
}
}
return "Receiver#invokeMethod";
}
...
}

输出例如以下,Receiver的输出跟Groovy的MOP有关,这个以后再说。

14
hello,world
methodName->plus
args->[Ljava.lang.Object;@6b573f80
params->
me.kisimple.just4fun.Parameter@2d0a238e
Receiver#invokeMethod

Groovy的runtime应该也是个不小的坑。以后再研究。以下来看下invokedynamic

使用invokedynamic

从上面的栗子能够看到,groovyc须要生成非常多runtime相关的字节码,为了使动态类型语言在Java平台上更easy实现,JavaSE 7引入了invokedynamic指令。

简单来讲,执行时虚拟机在执行invokedynamic指令时会执行用户自己定义的bootstrap方法,用户能够在bootstrap方法中给出调用点的详细实现,这样就能达到执行时才确定详细实现的目的了。invokedynamicMethodHandle的详细内容參官方文档,以下我们来看下Groovy是怎样使用invokedynamic的。

依据官方文档说明更换一下jar包,编译时加上--indy选项。得到的字节码例如以下,

Classfile /home/blues/Projects/groovy-core/demo.class
Last modified Mar 8, 2015; size 1839 bytes
MD5 checksum 5bbf49b81b00dece4523fbf55f8e7266
Compiled from "demo.groovy"
public class demo extends groovy.lang.Script
BootstrapMethods:
0: #31 invokestatic org/codehaus/groovy/vmplugin/v7/IndyInterface.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
Method arguments:
#33 runScript
#34 0
1: #31 invokestatic org/codehaus/groovy/vmplugin/v7/IndyInterface.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
Method arguments:
#48 plus
#34 0
SourceFile: "demo.groovy"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 demo
#2 = Class #1 // demo
#3 = Utf8 groovy/lang/Script
#4 = Class #3 // groovy/lang/Script
#5 = Utf8 demo.groovy
#6 = Utf8 $staticClassInfo
#7 = Utf8 Lorg/codehaus/groovy/reflection/ClassInfo;
#8 = Utf8 __$stMC
#9 = Utf8 Z
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = NameAndType #10:#11 // "<init>":()V
#13 = Methodref #4.#12 // groovy/lang/Script."<init>":()V
#14 = Utf8 this
#15 = Utf8 Ldemo;
#16 = Utf8 (Lgroovy/lang/Binding;)V
#17 = NameAndType #10:#16 // "<init>":(Lgroovy/lang/Binding;)V
#18 = Methodref #4.#17 // groovy/lang/Script."<init>":(Lgroovy/lang/Binding;)V
#19 = Utf8 context
#20 = Utf8 Lgroovy/lang/Binding;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 org/codehaus/groovy/runtime/InvokerHelper
#24 = Class #23 // org/codehaus/groovy/runtime/InvokerHelper
#25 = Utf8 org/codehaus/groovy/vmplugin/v7/IndyInterface
#26 = Class #25 // org/codehaus/groovy/vmplugin/v7/IndyInterface
#27 = Utf8 bootstrap
#28 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
#29 = NameAndType #27:#28 // bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
#30 = Methodref #26.#29 // org/codehaus/groovy/vmplugin/v7/IndyInterface.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
#31 = MethodHandle #6:#30 // invokestatic org/codehaus/groovy/vmplugin/v7/IndyInterface.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
#32 = Utf8 runScript
#33 = String #32 // runScript
#34 = Integer 0
#35 = Utf8 invoke
#36 = Utf8 (Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/String;)Ljava/lang/Object;
#37 = NameAndType #35:#36 // invoke:(Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/String;)Ljava/lang/Object;
#38 = InvokeDynamic #0:#37 // #0:invoke:(Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/String;)Ljava/lang/Object;
#39 = Utf8 args
#40 = Utf8 [Ljava/lang/String;
#41 = Utf8 run
#42 = Utf8 ()Ljava/lang/Object;
#43 = Utf8 java/lang/Throwable
#44 = Class #43 // java/lang/Throwable
#45 = Utf8 addtwo
#46 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#47 = Utf8 plus
#48 = String #47 // plus
#49 = NameAndType #35:#46 // invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#50 = InvokeDynamic #1:#49 // #1:invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#51 = Utf8 a
#52 = Utf8 Ljava/lang/Object;
#53 = Utf8 b
#54 = Utf8 $getStaticMetaClass
#55 = Utf8 ()Lgroovy/lang/MetaClass;
#56 = Utf8 java/lang/Object
#57 = Class #56 // java/lang/Object
#58 = Utf8 getClass
#59 = Utf8 ()Ljava/lang/Class;
#60 = NameAndType #58:#59 // getClass:()Ljava/lang/Class;
#61 = Methodref #57.#60 // java/lang/Object.getClass:()Ljava/lang/Class;
#62 = Utf8 org/codehaus/groovy/runtime/ScriptBytecodeAdapter
#63 = Class #62 // org/codehaus/groovy/runtime/ScriptBytecodeAdapter
#64 = Utf8 initMetaClass
#65 = Utf8 (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
#66 = NameAndType #64:#65 // initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
#67 = Methodref #63.#66 // org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
#68 = NameAndType #6:#7 // $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
#69 = Fieldref #2.#68 // demo.$staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
#70 = Utf8 org/codehaus/groovy/reflection/ClassInfo
#71 = Class #70 // org/codehaus/groovy/reflection/ClassInfo
#72 = Utf8 getClassInfo
#73 = Utf8 (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
#74 = NameAndType #72:#73 // getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
#75 = Methodref #71.#74 // org/codehaus/groovy/reflection/ClassInfo.getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
#76 = Utf8 getMetaClass
#77 = NameAndType #76:#55 // getMetaClass:()Lgroovy/lang/MetaClass;
#78 = Methodref #71.#77 // org/codehaus/groovy/reflection/ClassInfo.getMetaClass:()Lgroovy/lang/MetaClass;
#79 = Utf8 Code
#80 = Utf8 LocalVariableTable
#81 = Utf8 StackMapTable
#82 = Utf8 LineNumberTable
#83 = Utf8 BootstrapMethods
#84 = Utf8 SourceFile
{
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC public static transient boolean __$stMC;
flags: ACC_PUBLIC, ACC_STATIC, ACC_TRANSIENT, ACC_SYNTHETIC public demo();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #13 // Method groovy/lang/Script."<init>":()V
4: return
LocalVariableTable:
Start Length Slot Name Signature
4 0 0 this Ldemo; public demo(groovy.lang.Binding);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #18 // Method groovy/lang/Script."<init>":(Lgroovy/lang/Binding;)V
5: return
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo;
0 5 1 context Lgroovy/lang/Binding; public static void main(java.lang.String...);
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=3, locals=1, args_size=1
0: ldc #24 // class org/codehaus/groovy/runtime/InvokerHelper
2: ldc #2 // class demo
4: aload_0
5: invokedynamic #38, 0 // InvokeDynamic #0:invoke:(Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/String;)Ljava/lang/Object;
10: pop
11: return
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String; public java.lang.Object run();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aconst_null
1: areturn
2: nop
3: athrow
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Ldemo;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 2
locals = []
stack = [ class java/lang/Throwable ] public java.lang.Object addtwo(java.lang.Object, java.lang.Object);
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_1
1: aload_2
2: invokedynamic #50, 0 // InvokeDynamic #1:invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
7: areturn
8: nop
9: athrow
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Ldemo;
0 8 1 a Ljava/lang/Object;
0 8 2 b Ljava/lang/Object;
LineNumberTable:
line 2: 0
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 8
locals = []
stack = [ class java/lang/Throwable ] protected groovy.lang.MetaClass $getStaticMetaClass();
flags: ACC_PROTECTED, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokevirtual #61 // Method java/lang/Object.getClass:()Ljava/lang/Class;
4: ldc #2 // class demo
6: if_acmpeq 14
9: aload_0
10: invokestatic #67 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass:(Ljava/lang/Object;)Lgroovy/lang/MetaClass;
13: areturn
14: getstatic #69 // Field $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
17: astore_1
18: aload_1
19: ifnonnull 34
22: aload_0
23: invokevirtual #61 // Method java/lang/Object.getClass:()Ljava/lang/Class;
26: invokestatic #75 // Method org/codehaus/groovy/reflection/ClassInfo.getClassInfo:(Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
29: dup
30: astore_1
31: putstatic #69 // Field $staticClassInfo:Lorg/codehaus/groovy/reflection/ClassInfo;
34: aload_1
35: invokevirtual #78 // Method org/codehaus/groovy/reflection/ClassInfo.getMetaClass:()Lgroovy/lang/MetaClass;
38: areturn
StackMapTable: number_of_entries = 2
frame_type = 14 /* same */
frame_type = 252 /* append */
offset_delta = 19
locals = [ class org/codehaus/groovy/reflection/ClassInfo ] }

能够看到生成的字节码确实比不使用invokedynamic要少得多。

跟上面一样,人肉反编译,invokedynamic相关的runtime大概是这么来使用的。

    private static void addtwo(Object o1, Object o2) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(Object.class,
Object.class, Object.class);
java.lang.invoke.CallSite callSite =
IndyInterface.bootstrap(lookup, "invoke", mt, "plus", 0);
MethodHandle mh = callSite.getTarget();
System.out.println(mh.invoke(o1, o2));
}

有一点值得说明的是,通过字节码能够看到,除了bootstrap方法默认的三个參数。groovyc还多生成了两个參数。

         2: invokedynamic #50,  0             // InvokeDynamic #1:invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    1: #31 invokestatic org/codehaus/groovy/vmplugin/v7/IndyInterface.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;
Method arguments:
#48 plus
#34 0

也就是说给bootstrap方法传递的methodNameinvokemethodType(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object,而我们关心的方法名。也就是plus。是作为额外的參数传进去的。所以我们上面的栗子中对bootstrap方法的调用才会是IndyInterface.bootstrap(lookup, "invoke", mt, "plus", 0)这个样子的。假设是要实现减法。则能够这么写IndyInterface.bootstrap(lookup, "invoke", mt, "minus", 0)。也就是通过额外的參数来进行方法的分发。至于为什么要这么来实现。须要后面再研究下Groovy的runtime看看了。

简单实现

Groovy的runtime是比較复杂的,以下我们用相关的API实现一个简单一点的,仅仅能进行整数的加法与字符串的加法。

public class IntegerOps {
public static Integer adder(Integer x, Integer y) {
return x + y;
}
}
public class StringOps {
public static String adder(String x, String y) {
return x + y;
}
}
public class BootstrapMethod {

    public static CallSite link(MethodHandles.Lookup callerClass,
String dynMethodName,
MethodType dynMethodType)
throws Throwable {
if("adder".equals(dynMethodName)) { MethodHandle mh;
Class receiverType = dynMethodType.parameterType(0);
if(receiverType.equals(Integer.class)) {
mh = callerClass.findStatic(
IntegerOps.class,
"adder",
MethodType.methodType(Integer.class, Integer.class, Integer.class));
} else if(receiverType.equals(String.class)) {
mh = callerClass.findStatic(
StringOps.class,
"adder",
MethodType.methodType(String.class, String.class, String.class));
} else {
return null;
}
return new ConstantCallSite(mh); }
return null;
} }
    private static void addtwo(Object o1, Object o2) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(o1.getClass(),
o1.getClass(), o2.getClass());
java.lang.invoke.CallSite callSite =
BootstrapMethod.link(lookup, "adder", mt);
MethodHandle mh = callSite.getTarget();
System.out.println(mh.invoke(o1, o2));
}

有两点须要说明。首先是MethodType,栗子的bootstrap方法须要依据MethodType来进行方法分发(这里我们仅仅依据第一个參数的类型来推断)。究竟如今是要进行整数的加法,还是要进行字符串的加法。

但因为这个MethodTypeinvokedynamic指令的參数,因此个人认为这样设计事实上是有问题的,所以groovyc也才会直接传的(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object

另外一点是MethodHandle的问题。当我们执行addtwo(7, 7L)也就是第二个參数传的是个long型就跪了。java.lang.ClassCastException: Cannot cast java.lang.Long to java.lang.Integer,须要MethodHandle#asType转换一下。这里就不赘述了,想了解的同学看下API文档就清楚了。

參考资料

版权声明:本文博主原创文章。博客,未经同意不得转载。