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系统需要根据方法名(+)和参数类型(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_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方法中给出调用点的具体实现,这样就能达到运行时才确定具体实现的目的了。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_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方法传递的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文档就清楚了。

参考资料

  • 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
0 0
原创粉丝点击