前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Groovy踩坑记之方法调用八层认识

Groovy踩坑记之方法调用八层认识

作者头像
FunTester
发布2022-05-17 16:32:09
5010
发布2022-05-17 16:32:09
举报
文章被收录于专栏:FunTester

这个问题源于某一次性能测试中写了一个异步显示QPS的功能,思路是在动态性能测试模型中异步线程中增加输出QPS的能力。就是获取1s内发出去的请求,然后当做实时QPS输出。

但是在实际使用中,每次输出的QPS只有1,这就特别尴尬了。如果不输出日志信息QPS就是正常的。经过查看线程转储和使用jconsole分析,发现每次执行相关方法的时候执行线程被锁住了。

经过了百思不得其解,然后终于彻头彻尾地悟道,这原来是Groovy其中一个特性导致的BUG。

第一层

由于原框架比较复杂,这里分享一个复现的Demo文件。

首先有一个父类Parent和子类Child,父类有一个静态test方法,子类有一个静态getTest方法。然后子类有一个成员方法bugs,改方法调用了test方法。

代码语言:javascript
复制
class FunTester {

    public static void main(String[] args) {
        new Child().bugs()
    }

    private static class Child extends Parent {

        void bugs() {
            println(test(12))
        }

        static Child getTest() {
            println("子类方法 无参数")
            retrun new Child()
        }

    }

    private static class Parent {

        static def test(int i) {
            return "父类方法 参数$i"
        }
    }
}

你们猜一猜控制台输出什么,我是想破头也想不出来:

代码语言:javascript
复制
子类方法 无参数
父类方法 参数12

Process finished with exit code 0

居然先调用了子类的getTest方法,再去调用的父类方法,太神奇了。

第二次

我们改动一下getTest的返回值,看看下面的代码:

代码语言:javascript
复制
class FunTester {

    public static void main(String[] args) {
        new Child().bugs()
    }

    private static class Child extends Parent {

        void bugs() {
            println(test(12))
        }

        static String getTest() {
            println("子类方法 无参数")
            return "FunTester"
        }

    }

    private static class Parent {

        static def test(int i) {
            return "父类方法 参数$i"
        }
    }
}

控制台如下:

代码语言:javascript
复制
子类方法 无参数
父类方法 参数12

Process finished with exit code 0

如果我们把返回值改成Groovy关键字def,方法如下:

代码语言:javascript
复制
        static def getTest() {
            println("子类方法 无参数")
            return "FunTester"
        }

控制台输出:

代码语言:javascript
复制
子类方法 无参数
父类方法 参数12

Process finished with exit code 0

输出逻辑没有问题,依然是先调用了子类的方法。

第三层

这次我们把返回值改成void,看看效果如何:

这里就不放全部代码了,只展示改动部分。

代码语言:javascript
复制
        static void getTest() {
            println("子类方法 无参数")
        }

控制台输出:

代码语言:javascript
复制
父类方法 参数12

Process finished with exit code 0

终于恢复正常了。

第四层

如果我们这次恢复def返回值,改动一下getTest的方法名,如下:

代码语言:javascript
复制
        static def getTeest() {
            println("子类方法 无参数")
        }

控制台输出:

代码语言:javascript
复制
父类方法 参数12

Process finished with exit code 0

也是正常的,跟Java无异。

到现在不知道读者是否有点迷糊。下面我们看一下更进一步测试。

第五层

我们先恢复异常时候的代码,然后改动bugs方法的使用test时候的参数。改动如下:

代码语言:javascript
复制
        void bugs() {
            println(test("FunTester"))
        }

        static def getTest() {
            println("子类方法 无参数")
        }

控制台输出如下:

代码语言:javascript
复制
子类方法 无参数
Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: static com.funtest.groovytest.FunTester.test() is applicable for argument types: (String) values: [FunTester]
Possible solutions: getAt(java.lang.String), wait(), use([Ljava.lang.Object;), wait(long), tap(groovy.lang.Closure), is(java.lang.Object)
-------省略错误信息-----------
 at com.funtest.groovytest.FunTester.main(FunTester.groovy:6)

Process finished with exit code 1

看样子是先调用的子类方法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 !

  • FunTester原创大赏
  • 性能测试专题【FunTester原创】
  • 接口功能测试专题【FunTester原创】
  • Java、Groovy、Python和Golang如何把方法当作参数
  • 下单延迟10s撤单性能测试
  • 自动化如何选择用例
  • Java&Go高性能队列之channel性能测试
  • 动态模型之动态增减【FunTester测试框架】
  • 白盒测试扫盲
  • 6个重要的JVM性能参数
  • Java&Go三种HTTP客户端性能测试
  • 测试人员常用借口
  • 又双叒叕一行代码:Map按值排序
  • 基于爬虫的测试自动化经验分享
  • 利用闭包实现自定义等待方法
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一层
  • 第二次
  • 第三层
  • 第四层
  • 第五层
  • 第六层
  • 第七层
  • 第八层
    • Have Fun ~ Tester !
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档