最近在做分布式性能测试拓展的过程,其中一个思路就是通过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 官方合作媒体,非著名测试开发。


↙↙↙阅读原文可查看相关链接,并与作者交流