最近在做分布式性能测试拓展的过程,其中一个思路就是通过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
,当做一个参数。因为我的性能测试脚本都是写成了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:最后说一下惊天大秘密,Java
和Groovy
反射执行方法居然是兼容的。哎,又多写了一份功能。