Scripting Java #3:Groovy与invokedynamic
来源:互联网 发布:光年无限科技公司知乎 编辑:程序博客网 时间:2024/04/30 15:05
今天来简单看下Groovy语言的实现机制。在那之前得先来扯下静态类型与动态类型语言在实现上面的一些差异。
静态类型 vs. 动态类型
看下面这个简单的栗子,
def addtwo(a, b) { return a + b;}
静态类型语言与动态类型语言对于上面这个简单的加法实现完全不同。静态类型语言,例如Java,语言的编译器在编译时就已经进行类型检查,所以能够将+
运算符编译成特定的指令,语言的runtime系统可以直接执行该指令。例如javac会将两个int
类型的+
运算编译成iadd
指令,运行时由JVM直接执行iadd
指令。
而对于动态类型语言,由于需要到运行时才能确定变量的类型,因此运算符的具体实现也需要到运行时才能确定。a + b
会被编译成类似(+ a b)
这样的方法调用(Lisp风格^_^),+
只是个方法名。语言的runtime系统需要根据方法名(+
)和参数类型(a
和b
的类型)来确定这个加法运算的具体实现。
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_SUPERConstant 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有关,这个以后再说。
14hello,worldmethodName->plusargs->[Ljava.lang.Object;@6b573f80params->me.kisimple.just4fun.Parameter@2d0a238eReceiver#invokeMethod
Groovy的runtime应该也是个不小的坑,以后再研究。下面来看下invokedynamic
。
使用invokedynamic
从上面的栗子可以看到,groovyc需要生成很多runtime相关的字节码,为了使动态类型语言在Java平台上更容易实现,JavaSE 7引入了invokedynamic
指令。
简单来讲,运行时虚拟机在执行invokedynamic
指令时会执行用户自定义的bootstrap方法,用户可以在bootstrap方法中给出调用点的具体实现,这样就能达到运行时才确定具体实现的目的了。invokedynamic
与MethodHandle
的详细内容参官方文档,下面我们来看下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_SUPERConstant 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方法传递的methodName
是invoke
,methodType
是(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
来进行方法分发(这里我们只根据第一个参数的类型来判断),到底现在是要进行整数的加法,还是要进行字符串的加法。但由于这个MethodType
是invokedynamic
指令的参数,因此个人觉得这样设计其实是有问题的,所以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文档就清楚了。
参考资料
- http://docs.oracle.com/javase/8/docs/technotes/guides/vm/multiple-language-support.html
- http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html
- http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
- http://docs.groovy-lang.org/docs/groovy-2.4.0/pdf/wiki-snapshot.pdf
- Scripting Java #3:Groovy与invokedynamic
- Java8学习笔记(3) -- InvokeDynamic指令在Groovy里的使用
- invokedynamic与lambda表达式
- Java中的invokedynamic
- groovy与java交互
- Java与Groovy的操作
- Java与Groovy的整合
- Groovy与Java的区别
- Groovy与java的不同之处
- groovy 与 java 差异 (1)
- Groovy与Java的不同点
- Groovy与Java的区别
- Groovy与Java的不同点
- Groovy轻松入门——通过与Java的比较,迅速掌握Groovy(3)
- MethodHandle与反射Method区别,invokedynamic指令
- Reflect与jdk7引进的InvokeDynamic
- Java语言的动态性-invokedynamic
- groovy与java比较,展现groovy的魅力
- Perl脚本语言在Linux系统下的安装步骤
- Weka算法Classifier-tree-J48源码分析(一)算法和基本数据结构
- 快速学习php
- 《杜拉拉升职记》之经典语录
- MySQL数据库不允许从远程访问怎么办
- Scripting Java #3:Groovy与invokedynamic
- Tiny6410 初体验
- Python脚本在Linux上怎么运行
- js跨域及解决方案
- 《嵌入式系统设计师》笔记之一——嵌入式系统基础知识
- (转)java 异常
- Mysql 自动启动的配置
- 纯c语言写的拼图游戏
- Http、TCP/IP协议与Socket之间的区别