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】。文章转载请联系作者。
评论