命令行如何执行jar包里面的方法

2020-12-24 14:52:34 浏览数 (1)

最近遇到一个尴尬的问题,由于公司机测试环境的机房迁移,导致办公区的网络跟测试环境网络之前延迟比较大,大到什么程度呢?大到不能正常使用测试环境。

由于网络组一直在排查,暂时没有答复,所以只能采取一个比较临时的办法。我自己在本机用的Java写的测试框架以及Groovy写的测试脚本,具体情况可参考:如何统一接口测试的功能、自动化和性能测试用例

由于本人之前拥有的一台独立物理测试机被收回,现在分给测试组的只有一个docker容器起来的服务。本来最优的方案是在docker file文件时候吧Groovy SDK加上去,保证一个Groovy运行环境,但也被否掉了,只留了一个口子给我,就是上传文件到项目Git中,然后通过够部署项目把文件弄到docker容器中。

Groovy SDK又比较大,完事儿还需要重新设置环境变量等等问题,我想到了两个其他方案:

  • 将项目buildjar包,测试用例(也就是某个类的main方法),通过执行jar包中的class类的main方法,达到执行不同测试用例的目的,顺手做一个参数化。
  • 定义一个统一的main方法入口,通过反射执行不同的方法。

显然第二个思路用途更广,但是实现起来略微麻烦了一些,而且传参的时候比较复杂,个人建议还是优先考虑第一种方式。

下面分享这两种方式的实现。

执行class的main方法

首先我写一个测试用例,内容如下:

代码语言:javascript复制
package com.okayqa.composer.performance.teach1_1

import com.fun.frame.execute.Concurrent
import com.fun.frame.httpclient.ClientManage
import com.fun.frame.httpclient.FanLibrary
import com.fun.frame.thread.HeaderMark
import com.fun.frame.thread.RequestThreadTimes
import com.fun.utils.ArgsUtil
import com.okayqa.composer.base.OkayBase
import com.okayqa.composer.function.IMSocket

class ActivityUnread extends OkayBase{
    public static void main(String[] args) {
        ClientManage.init(5, 5, 0, "", 0)
        def util = new ArgsUtil(args)
        def thread = util.getIntOrdefault(0, 100)
        def times = util.getIntOrdefault(1, 100)
        def base = getBase()
        def socket = new IMSocket(base)
        socket.getActivityUnread(81951375949,43519,43504)
        def request = FanLibrary.getLastRequest()

        def mark = new HeaderMark("requestid")
        def times1 = new RequestThreadTimes(request, times, mark)

        new Concurrent(times1, thread, "activity未读消息").start()


        allOver()
    }
}

然后使用Mavenpackage命令打包。执行Java命令即可执行jar包中某个classmain方法,可参数化。

java -cp okay_test-1.0-SNAPSHOT.jar com.okayqa.composer.performance.teach1_1.ActivityUnread 1 1 start

下面是输出:

代码语言:javascript复制
INFO-> 当前用户:fv,IP:10.60.192.21,工作目录:/Users/fv/Documents/workspace/okay_test/target/,系统编码格式:UTF-8,系统Mac OS X版本:10.15.7
INFO-> requestid: Fdev1607495809625
INFO-> 请求uri:https://teacherpad-stress.xk12.cn/api/t_pad/user/login,耗时:368 ms
INFO-> 教师:61951375269,学科:null,名称:61951375269,登录成功!
INFO-> requestid: Fdev1607495810141
INFO-> 请求uri:https://ailearn-composer-interface-stress.xk12.cn/api/composer/activity/course_list/unread_num,耗时:106 ms
INFO->
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
>  {
>  ① . "data":{
>  ② . . . "activity_unread_num":[
>  ③ . . . . . {
>  ③ . . . . . "activity_id":43519,
>  ③ . . . . . "msg_count":208
>  ② . . . },
>  ② . . . {
>  ③ . . . . . "activity_id":43504,
>  ③ . . . . . "msg_count":0
>  ③ . . . . . }
>  ② . . . ]
>  ① . },
>  ① . "meta":{
>  ② . . . "emsg":"成功",
>  ② . . . "ecode":0
>  ① . }
>  }
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
INFO-> gc回收线程开始了!
INFO-> 线程:activity未读消息0,执行次数:1,错误次数: 0,总耗时:1 s
INFO-> 总计1个线程,共用时:0.059 s,执行总数:1,错误数:0,失败数:0
INFO-> 数据保存成功!文件名:/Users/fv/Documents/workspace/okay_test/target/long/data/1activity未读消息20201209143650
INFO->
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
>  {
>  ① . "rt":56,
>  ① . "total":1,
>  ① . "qps":17.857142857142858,
>  ① . "failRate":0.0,
>  ① . "threads":1,
>  ① . "startTime":"2020-12-09 14:36:50",
>  ① . "endTime":"2020-12-09 14:36:50",
>  ① . "errorRate":0.0,
>  ① . "executeTotal":1,
>  ① . "mark":"activity未读消息20201209143650",
>  ① . "table":""
>  }
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
INFO->
INFO-> gc回收线程结束了!

完美执行1 !!!

反射执行方法

首先封装一个反射执行的工具类,代码如下:

代码语言:javascript复制
package com.fun.frame.execute;

import com.alibaba.fastjson.JSON;
import com.fun.base.exception.FailException;
import com.fun.config.Constant;
import com.fun.frame.SourceCode;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@SuppressFBWarnings({"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", "NP_NULL_ON_SOME_PATH_EXCEPTION"})
public class ExecuteSource extends SourceCode {

    private static Logger logger = LoggerFactory.getLogger(ExecuteSource.class);

    /**
     * 执行包内所有类的非 main 方法
     *
     * @param packageName
     */
    public static void executeAllMethodInPackage(String packageName) {
        List<String> classNames = getClassName(packageName);
        if (classNames != null) {
            for (String className : classNames) {
                String path = packageName   "."   className;
                executeAllMethod(path);// 执行所有方法
            }
        }
    }


    /**
     * 执行一个类的方法内所有的方法,非 main,执行带参方法的代码过滤
     *
     * @param path 类名
     */
    public static void executeAllMethod(String path) {
        Class<?> c = null;
        Object object = null;
        try {
            c = Class.forName(path);
            object = c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            try {
                method.invoke(object);
            } catch (IllegalAccessException e) {
                logger.warn("非法访问导致反射方法执行失败!", e);
            } catch (InvocationTargetException e) {
                logger.warn("反射调用目标异常导致方法执行失败!", e);
            } catch (Exception e) {
                logger.warn("反射方法执行失败!", e);
            } finally {
                sleep(Constant.EXECUTE_GAP_TIME);
            }
        }
    }

    /**
     * 提供给命令行main方法使用
     *
     * @param params
     */
    public static void executeMethod(String... params) {
        String[] ps = Arrays.copyOfRange(params, 1, params.length);
        executeMethod(params[0], ps);
    }

    /**
     * 执行具体的某一个方法,提供内部方法调用
     *
     * @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 = new Integer(changeStringToInt(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;
        }
    }

    /**
     * 获取当前类的所有用例方法名
     *
     * @param path
     * @return
     */
    public static List<String> getAllMethodName(String path) {
        List<String> methods = new ArrayList<>();
        Class<?> c = null;
        Object object = null;
        try {
            c = Class.forName(path);
            object = c.newInstance();
        } catch (Exception e) {
            FailException.fail("初始化对象失败:"   path);
        }
        Method[] all = c.getDeclaredMethods();
        for (int i = 0; i < all.length; i  ) {
            String str = all[i].getName();
            methods.add(str);
        }
        return methods;
    }

    /**
     * 获取某包下所有类
     *
     * @param packageName 包名
     * @return 类的完整名称
     */
    public static List<String> getClassName(String packageName) {
        List<String> fileNames = new ArrayList<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();// 获取当前位置
        String packagePath = packageName.replace(".", Constant.OR);// 转化路径,Linux 系统
        URL url = loader.getResource(packagePath);// 具体路径
        if (url == null || !"file".equals(url.getProtocol())) {
            FailException.fail("获取包路径失败!");
        }
        File file = new File(url.getPath());
        File[] childFiles = file.listFiles();
        for (File childFile : childFiles) {
            String path = childFile.getPath();
            if (path.endsWith(".class")) {
                path = path.substring(path.lastIndexOf(OR)   1, path.lastIndexOf("."));
                fileNames.add(path);
            }
        }
        return fileNames;
    }


}

使用Demo如下:

代码语言:javascript复制
package com.fun.main;

import com.fun.frame.SourceCode;
import com.fun.frame.execute.ExecuteSource;

public class ExecuteMethod extends SourceCode {

    public static void main(String[] args) {
        args = new String[]{"com.fun.ztest.java.T.test", "java.lang.Integer", "1"};
        ExecuteSource.executeMethod(args);
    }


}

其中T的代码中test()方法如下:

代码语言:javascript复制
    public static void test(int i) {
        output(33333333   i);
    }

这里我模拟了args参数,可以看出这里的参数非常复杂,都是较长的String字符串。

控制台输出:

代码语言:javascript复制
INFO-> 当前用户:fv,IP:10.60.192.21,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.15.7
WARN-> 方法属性处理错误!
java.lang.NoSuchMethodException: com.fun.ztest.java.T.test(java.lang.Integer)
 at java.lang.Class.getMethod(Class.java:1786) ~[?:1.8.0_51]
 at com.fun.frame.execute.ExecuteSource.executeMethod(ExecuteSource.java:106) [classes/:?]
 at com.fun.frame.execute.ExecuteSource.executeMethod(ExecuteSource.java:77) [classes/:?]
 at com.fun.main.ExecuteMethod.main(ExecuteMethod.java:10) [classes/:?]
INFO-> 33333334

Process finished with exit code 0

  • 这里的报错是因为test()方法的参数是int并不是我传入的java.lang.Integer导致的,单并不影响后面的方法调用正常执行,可忽略。

完美执行2 !!!

  • 还有一种神器可以解决这个问题:arthas,可以通过arthas命令redefine实现Java热更新的方式替换方法类,这个比较复杂,而且适用范围更窄,不可取。

0 人点赞