Groovy反射invokeMethod传参实践

最近在做分布式性能测试拓展的过程,其中一个思路就是通过Groovy反射执行方法。但是在创建groovy.lang.GroovyObject对象之后,通过调用groovy.lang.GroovyObject#invokeMethod方法执行类方法的时候遇到一个问题,就是groovy.lang.GroovyObject#invokeMethod只有两个参数,一个是String name方法名,另外一个是Object args方法参数。

源码如下:

    /**
     * Invokes the given method.
     *
     * @param name the name of the method to call
     * @param args the arguments to use for the method call
     * @return the result of invoking the method
     */
    Object invokeMethod(String name, Object args);

但是在性能测试脚本中一般至少有三个参数:1、用来控制线程数或者QPS;2、用来控制测试次数和测试时长;3、用来控制软启动时间。

这还不包括后期的自定义参数,一下子就犯难了。

在尝试搜索资料未果之后,我觉得自己动手测试一下。

String[] args参数

首先我的想法就是测试String[] args,当做一个参数。因为我的性能测试脚本都是写成了Groovy类的com.funtest.javatest.FunTester#main方法里面的,其实这也只有一个参数。

测试代码

    public static void main(String[] args) throws IOException {
        ExecuteGroovy.executeMethod("/Users/oker/IdeaProjects/funtester/src/test/groovy/com/funtest/groovytest/perf2.groovy", "test", new String[]{"32", "23"});
    }

被测方法

    static void test(String[] a) {
        output("FunTester成功了!${a[0]}   ${a[1]}");
    }

控制台输出

INFO-> 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> FunTester成功了!32 23

Process finished with exit code 0

成功了!最起码路没有被堵死。

多参数

接下来,我就放心多了,应该可以直接使用多个参数来验证猜想,把多个参数当做某个方法的一组参数。

测试代码

    public static void main(String[] args) throws IOException {
        ExecuteGroovy.executeMethod("/Users/oker/IdeaProjects/funtester/src/test/groovy/com/funtest/groovytest/perf2.groovy", "test", "32", "23");
    }

被测方法

    static void test(String a, String b) {
        output("FunTester成功了!$a $b");
    }

控制台输出

INFO-> 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> FunTester成功了!32 23

Process finished with exit code 0

依然成功了,这下前途光明了!

方法封装

相比Java的反射执行,Groovy明显就简单多了。

    /**
     * 获取groovy对象和执行类
     *
     * @param path 类文件路径
     * @param name 方法名
     * @param args 貌似只支持一个参数,这里默认{@link String}
     */
    public static void executeMethod(String path, String name, Object... args) {
        try {
            Class<?> groovyClass = loader.parseClass(new File(path));//创建类
            GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();//创建类对象
            groovyObject.invokeMethod(name, args);
        } catch (IOException | ReflectiveOperationException e) {
            logger.warn("获取类对象 {} 失败!", path, e);
            fail();
        }
    }

下面看一下Java的代码:

    /**
     * 执行具体的某一个方法,提供内部方法调用
     *
     * @param path
     */
    public static void executeMethod(String path, Object... paramsTpey) {
        int length = paramsTpey.length;
        if (length % 2 == 1) FailException.fail("参数个数错误,应该是偶数");
        String className = path.substring(0, path.lastIndexOf("."));
        String methodname = path.substring(className.length() + 1);
        Class<?> c = null;
        Object object = null;
        try {
            c = Class.forName(className);
            object = c.newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            logger.warn("创建实例对象时错误:{}", className, e);
        }
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            if (!method.getName().equalsIgnoreCase(methodname)) continue;
            try {
                Class[] classs = new Class[length / 2];
                for (int i = 0; i < paramsTpey.length; i = +2) {
                    classs[i / 2] = Class.forName(paramsTpey[i].toString());//此处基础数据类型的参数会导致报错,但不影响下面的调用
                }
                method = c.getMethod(method.getName(), classs);
            } catch (NoSuchMethodException | ClassNotFoundException e) {
                logger.warn("方法属性处理错误!", e);
            }
            try {
                Object[] ps = new Object[length / 2];
                for (int i = 1; i < paramsTpey.length; i = +2) {
                    String name = paramsTpey[i - 1].toString();
                    String param = paramsTpey[i].toString();
                    Object p = param;
                    if (name.contains("Integer")) {
                        p = Integer.parseInt(param);
                    } else if (name.contains("JSON")) {
                        p = JSON.parseObject(param);
                    }
                    ps[i / 2] = p;
                }
                method.invoke(object, ps);
            } catch (IllegalAccessException | InvocationTargetException e) {
                logger.warn("反射执行方法失败:{}", path, e);
            }
            break;
        }
    }

仔细看这两个方法,其实Java主要是因为传了参数的类型,由于传进来的都是String类型,所以要进行类型转换。

PS:最后说一下惊天大秘密,JavaGroovy反射执行方法居然是兼容的。哎,又多写了一份功能。


FunTester腾讯云年度作者Boss直聘签约作者GDevOps官方合作媒体,非著名测试开发。