Groovy中方法的调用实现方式浅析(CallSite)

Stella981
• 阅读 650

  在Groovy中可以很方便的交换两个变量的值, 如:

def (a, b) = [1, 2];
(a, b) = [b, a];

  这样, a,b变量的值就交换了, 那么Groovy是怎样实现的呢?

  来看看生成的字节码文件, 关键的代码如下:

  // Method descriptor #39 ()Ljava/lang/Object;
  // Stack: 4, Locals: 6
  public java.lang.Object run();
      0  invokestatic Main.$getCallSiteArray() : org.codehaus.groovy.runtime.callsite.CallSite[] [17]
      3  astore_1
      4  iconst_2
      5  anewarray java.lang.Object [41]
      8  dup
      9  iconst_0
     10  iconst_1
     11  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
     14  aastore
     15  dup
     16  iconst_1
     17  iconst_2
     18  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
     21  aastore
     22  invokestatic org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(java.lang.Object[]) : java.util.List [53]
     25  astore_2
     26  aload_1
     27  ldc <Integer 1> [54]
     29  aaload
     30  aload_2
     31  iconst_0
     32  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
     35  invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
     40  astore_3 [a]
     41  aload_1
     42  ldc <Integer 2> [58]
     44  aaload
     45  aload_2
     46  iconst_1
     47  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
     50  invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
     55  astore 4 [b]
     57  aload_2
     58  pop
     59  iconst_2
     60  anewarray java.lang.Object [41]
     63  dup
     64  iconst_0
     65  aload 4 [b]
     67  aastore
     68  dup
     69  iconst_1
     70  aload_3 [a]
     71  aastore
     72  invokestatic org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(java.lang.Object[]) : java.util.List [53]
     75  astore 5
     77  aload_1
     78  ldc <Integer 3> [59]
     80  aaload
     81  aload 5
     83  iconst_0
     84  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
     87  invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
     92  astore_3 [a]
     93  aload_1
     94  ldc <Integer 4> [60]
     96  aaload
     97  aload 5
     99  iconst_1
    100  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
    103  invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
    108  astore 4 [b]
    110  aload 5
    112  areturn
    113  aconst_null
    114  areturn

  反编译过来, 类似于这样的代码:

public Object main(){
    org.codehaus.groovy.runtime.callsite.CallSite[] callsite = Main.$getCallSiteArray();
    
    List alist = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(new Object[]{1,2});
    
    Object a = callsite[1].call(alist, 0);//等价于 alist.getAt(0) 等价于alist.get(0);
    Object b = callsite[2].call(alist, 1);//等价于 alist.getAt(1) 等价于alist.get(1);
    
    List blist = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(new Object[]{b,a});
    
    a = callsite[3].call(blist, 0);//等价于 blist.getAt(0) 等价于blist.get(0);
    b = callsite[4].call(blist, 1);//等价于 blist.getAt(1) 等价于blist.get(1);
}

private static synthetic SoftReference<CallSiteArray> $callSiteArray;

private static synthetic org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray(){
    org.codehaus.groovy.runtime.callsite.CallSiteArray rtrun = null;
    
    if(Main.$callSiteArray == null){
        rtrun = Main.$createCallSiteArray();
        Main.$callSiteArray = new SoftReference<CallSiteArray>(temp);
    }else{
        rtrun = Main.$callSiteArray.get();
    }
    return rturn.array;
}

private static synthetic org.codehaus.groovy.runtime.callsite.CallSiteArray $createCallSiteArray(){
    String[] sarry = new String[5];
    Main.$createCallSiteArray_1(sarry)
    
    return new CallSiteArray(Main.class, sarry);
}

private static synthetic void $createCallSiteArray_1(java.lang.String[] sarry){
    sarry[0] = "runScript";
    sarry[1] = "getAt";
    sarry[2] = "getAt";
    sarry[3] = "getAt";
    sarry[4] = "getAt";
}

  可以很清楚的看到Groovy编译器所做的事情.

  很简单,但是可以看出,Groovy的执行方式, 编译器会根据方法的调用来创建对应的CallSiteArray对象,

  Groovy将很多方法的调用都改为CallSite.call方式的调用,利用这种方式来支持很多的动态特性.

  比如上面的例子, Groovy通过创建CallSite, 然后通过CallSite来调用 getAt 方法.

  Groovy将调用委托到CallSite后, 开始时, CallSite的具体实现为CallSiteArray, 

  CallSiteArray通过将调用委托到org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSite, Object, Object[])方法

  该方法负责创建具体的调用点对象, 并执行对应方法.

  这里最终创建的调用点对象为 org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.PojoMetaMethodSiteNoUnwrapNoCoerce, 具体代码如下:

    public static class PojoMetaMethodSiteNoUnwrapNoCoerce extends PojoMetaMethodSite {

        public PojoMetaMethodSiteNoUnwrapNoCoerce(CallSite site, MetaClassImpl metaClass, MetaMethod metaMethod, Class params[]) {
            super(site, metaClass, metaMethod, params);
        }

        public final Object invoke(Object receiver, Object[] args) throws Throwable {
            try {
                return metaMethod.invoke(receiver,  args);
            } catch (GroovyRuntimeException gre) {
                throw ScriptBytecodeAdapter.unwrap(gre);
            }
        }
    }

  真实的调用会委托到这个类的invoke方法:

  这里metaMethod具体的类型为:

  org.codehaus.groovy.runtime.dgm$243@527e5409[name: getAt params: [int] returns: class java.lang.Object owner: interface java.util.List]

 这个类没有源码, 只有class文件,反编译如下:

import java.util.List;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.GeneratedMetaMethod;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;

public class dgm$243 extends GeneratedMetaMethod {
    public dgm$243(String paramString, CachedClass paramCachedClass, Class paramClass, Class[] paramArrayOfClass) {
        super(paramString, paramCachedClass, paramClass, paramArrayOfClass);
    }

    public Object invoke(Object paramObject, Object[] paramArrayOfObject) {
        return DefaultGroovyMethods
                .getAt((List) paramObject, DefaultTypeTransformation.intUnbox(paramArrayOfObject[0]));
    }

    public final Object doMethodInvoke(Object paramObject, Object[] paramArrayOfObject) {
        paramArrayOfObject = coerceArgumentsToClasses(paramArrayOfObject);
        return DefaultGroovyMethods
                .getAt((List) paramObject, DefaultTypeTransformation.intUnbox(paramArrayOfObject[0]));
    }
}

  可以看到, 具体的调用又是:org.codehaus.groovy.runtime.DefaultGroovyMethods.getAt(List, int)

    /**
     * Support the subscript operator for a List.
     * <pre class="groovyTestCase">def list = [2, "a", 5.3]
     * assert list[1] == "a"</pre>
     *
     * @param self a List
     * @param idx  an index
     * @return the value at the given index
     * @since 1.0
     */
    public static <T> T getAt(List<T> self, int idx) {
        int size = self.size();
        int i = normaliseIndex(idx, size);
        if (i < size) {
            return self.get(i);
        } else {
            return null;
        }
    }

  终于, 一个方法的调用完成了, 可以看到, 虽然提供了很高的灵活性, 但是也牺牲了一部分性能.

  PS: Groovy会将上面创建的CallSite对象缓存, 且为SoftReference类型.

  说了个大概,具体的细节还有很多~~~

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这