Scripting Java #3:Groovy与invokedynamic

简介:

只需看看今天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文档就清楚了。

參考资料

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





本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/4908670.html,如需转载请自行联系原作者


相关文章
|
6月前
|
IDE Java Apache
Eclipse 安装了 Groovy 开发插件之后,双击打不开 .java 文件了
Eclipse 安装了 Groovy 开发插件之后,双击打不开 .java 文件了
44 0
|
IDE Java Apache
Eclipse 安装了 Groovy 开发插件之后,双击打不开 .java 文件了
Eclipse 安装了 Groovy 开发插件之后,双击打不开 .java 文件了
115 0
|
Java
【Groovy】Groovy 动态语言特性 ( Groovy 语言与 Java 语言执行效率对比 | 以动态特性编译的 Groovy 类 | 以静态特性编译的 Groovy 类 )
【Groovy】Groovy 动态语言特性 ( Groovy 语言与 Java 语言执行效率对比 | 以动态特性编译的 Groovy 类 | 以静态特性编译的 Groovy 类 )
233 0
|
Java 编译器 API
Groovy和Java相比的特点
Groovy和Java相比的特点
205 0
|
Java
Groovy - == 一定等价于 Java 的 equals() 吗?
Groovy - == 一定等价于 Java 的 equals() 吗?
380 0
|
安全 Oracle NoSQL
Java 近期新闻:Vector API、Spring 升级及 CVE、Payara 平台、Groovy 和 TomEE 升级
Java 近期新闻综述,内容主要涉及 OpenJDK、JEP 426、Oracle 发布 4 月关键补丁更新、JDK 19、Liberica JDK 及原生镜像工具包升级、多个 Spring 点版本和里程碑版本、Payara 平台 April 2022 版本发布、Quarkus 2.8.1.Final、Apache Groovy 4.0.2、Apache TomEE 8.0.11、JobRunr 5.0.1 和一项 JReleaser 1.0 升级。
255 0
|
Java
【Groovy】Groovy 动态语言特性 ( Groovy 中的变量自动类型推断以及动态调用 | Java 中必须为变量指定其类型 )
【Groovy】Groovy 动态语言特性 ( Groovy 中的变量自动类型推断以及动态调用 | Java 中必须为变量指定其类型 )
187 0
【Groovy】Groovy 动态语言特性 ( Groovy 中的变量自动类型推断以及动态调用 | Java 中必须为变量指定其类型 )
|
Java
【Groovy】Groovy 脚本调用 ( Java 类中调用 Groovy 脚本 )
【Groovy】Groovy 脚本调用 ( Java 类中调用 Groovy 脚本 )
600 0
|
Java Android开发
【Groovy】Groovy 方法调用 ( Java 中函数参数是接口类型 | 函数参数是接口类型 可以 直接传递闭包 )
【Groovy】Groovy 方法调用 ( Java 中函数参数是接口类型 | 函数参数是接口类型 可以 直接传递闭包 )
278 0
【Groovy】Groovy 方法调用 ( Java 中函数参数是接口类型 | 函数参数是接口类型 可以 直接传递闭包 )
|
Java Android开发
【Groovy】Groovy 方法调用 ( 字符串切割 | 使用 Java 语法切割字符串 | 使用 Groovy 语法切割字符串直接为变量赋值 | 数组赋值给变量 变量个数小于等于数组长度 )
【Groovy】Groovy 方法调用 ( 字符串切割 | 使用 Java 语法切割字符串 | 使用 Groovy 语法切割字符串直接为变量赋值 | 数组赋值给变量 变量个数小于等于数组长度 )
674 0
【Groovy】Groovy 方法调用 ( 字符串切割 | 使用 Java 语法切割字符串 | 使用 Groovy 语法切割字符串直接为变量赋值 | 数组赋值给变量 变量个数小于等于数组长度 )