Groovy 踩坑记之方法调用八层认识
这个问题源于某一次性能测试中写了一个异步显示 QPS 的功能,思路是在动态性能测试模型中异步线程中增加输出 QPS 的能力。就是获取 1s 内发出去的请求,然后当做实时 QPS 输出。
但是在实际使用中,每次输出的 QPS 只有 1,这就特别尴尬了。如果不输出日志信息 QPS 就是正常的。经过查看线程转储和使用jconsole分析,发现每次执行相关方法的时候执行线程被锁住了。
经过了百思不得其解,然后终于彻头彻尾地悟道,这原来是 Groovy 其中一个特性导致的 BUG。
第一层
由于原框架比较复杂,这里分享一个复现的 Demo 文件。
首先有一个父类Parent和子类Child,父类有一个静态test方法,子类有一个静态getTest方法。然后子类有一个成员方法bugs,改方法调用了test方法。
你们猜一猜控制台输出什么,我是想破头也想不出来:
居然先调用了子类的getTest方法,再去调用的父类方法,太神奇了。
第二次
我们改动一下getTest的返回值,看看下面的代码:
控制台如下:
如果我们把返回值改成Groovy关键字def,方法如下:
控制台输出:
输出逻辑没有问题,依然是先调用了子类的方法。
第三层
这次我们把返回值改成void,看看效果如何:
这里就不放全部代码了,只展示改动部分。
控制台输出:
终于恢复正常了。
第四层
如果我们这次恢复def返回值,改动一下getTest的方法名,如下:
控制台输出:
也是正常的,跟 Java 无异。
到现在不知道读者是否有点迷糊。下面我们看一下更进一步测试。
第五层
我们先恢复异常时候的代码,然后改动bugs方法的使用test时候的参数。改动如下:
控制台输出如下:
看样子是先调用的子类方法getTest,然后去调用父类方法,因为参数类型不一样导致了一个这个报错。
第六层
下面到了解密时刻,起因是因为Groovy特性中的两点:
如果有
get方法,会隐性给当前类或者当前对象增加一个属性或者变量。当前方法调用出开始,会寻找最近的方法调用,这里只看方法名是否一致或者符合
get+方法名首字母大写的方法尝试寻找符合的方法调用Groovy 语言中,会把闭包和通常变量命令方式无异,而且 Groovy 语言检查中并不会检查这个。
到了第六层,其实已经很接近真相了,通过前面几个例子,应该都可以总结出来了。
第七层
以上 Demo 中test(12)这个方法调用,通常理解为:调用test方法,参数是FunTester,然后在子类中找test方法,结果没找到。就去找父类方法,然后找到了,而且参数数量和类型匹配,所以会调用父类的test方法。这个也是Java的思路。
第八层
但是在 Groovy 世界中,test(12)还有另外一层理解:
test理解为Child一个静态属性/变量,或者一个对象属性/变量(因为bugs是个成员方法)。然后
test(12)调用,先去找当前子类的test属性,然后把test对象当做闭包,去调用call(12)。由于
Groovy特性,子类有个方法getTest,所以有了隐性的test属性。所以会先调用getTest方法。如果
getTest返回void时,那么getTest就是不符合Groovy语言中GET方法,所以在子类找不到test属性定义方法,只能去父类找响应的方法。
经过以上分析,这个问题是不是就明明白白了,真是一个让人头疼不已,夜不能寐的问题。
Have Fun ~ Tester !
版权声明: 本文为 InfoQ 作者【FunTester】的原创文章。
原文链接:【http://xie.infoq.cn/article/84b95a6f7e82bcbade4a9bfa3】。文章转载请联系作者。










评论