cover

Jmeter源码系列(1) - NewDriver 类详解-Jmeter 的启动器

写在前面的话

Jmeter 全称(Apache JMeter)是一个开源的、功能强大的性能测试工具,用于对各种应用程序和协议进行功能、负载、压力和性能测试。它被广泛应用于软件开发和计划阶段,以确保应用程序在各种负载情况下的稳定性和可靠性。 本系列将从 Jmeter 代码层面陆续剖析其实现原理,包括但不限于 Jmeter 设计思路,Jmeter 核心对象/接口/方法。如有错误,敬请指正!

NewDriver

NewDriver 是 org.apache.jmeter 包下的一个类,如下是 NewDriver 源码中的类说明

/**
 * Main class for JMeter - sets up initial classpath and the loader.
 */

从这个说明中,我们可以知道,这个类提供了 2 个主要功能:

  • 初始化 classpath
  • 初始化一个 loader, 这个 loader 其实就是一个动态类加载器

以下内容摘抄自 NewDriver 源码,在源码中会使用注释来说明关键代码的作用,最后也会做总结,让我们开始吧

public final class NewDriver {

    /**
     * 定义一堆常量,会在 static 代码块中使用
     */
    private static final String CLASSPATH_SEPARATOR = File.pathSeparator;
    private static final String OS_NAME = System.getProperty("os.name");
    private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH);
    private static final String JAVA_CLASS_PATH = "java.class.path";
    private static final String JMETER_LOGFILE_SYSTEM_PROPERTY = "jmeter.logfile";
    private static final String HEADLESS_MODE_PROPERTY = "java.awt.headless";
    /**
     * 动态类加载器,继承自 URLClassLoader,提供了一个静态方法 updateLoader(URL [] urls) 实现了动态加载 jar
     * 的功能。
     */
    private static final DynamicClassLoader loader;
    private static final String JMETER_INSTALLATION_DIRECTORY;
    private static final List<Exception> EXCEPTIONS_IN_INIT = new ArrayList<>();

    static {
        final List<URL> jars = new ArrayList<>();
        /**
         * 启动时从 jvm 获取 classpath
         */
        final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);
        String tmpDir;
        /**
         * 按照指定标记符来分割给定的字符串,但是 StringTokenizer 是一个遗留类,出于兼容性原因而保留,建议使用 String 的拆分方法或 java.util.regex 包。
         * 顺便说一下,Jmeter 源码中会包含非常多的过时的方法或者写法,有些是因为 Jmeter 本身开发较早,当时的 jdk 版本没有我们常用的新方法,
         * 有些则是因为当时 jdk 早期版本存在 bug,jmeter 会使用另一种写法来规避这些 bug,当然,现在这些 bug 可能已经修复了,不过 jmeter 的源码中
         * 任然会保留这部分注释
         */
        StringTokenizer tok = new StringTokenizer(initiaClasspath, File.pathSeparator);
        /**
         * 对 mac 系统做了单独的判断,我也没有深究为啥要单独处理,不晓得现在还需不需要这么写
         */
        if (tok.countTokens() == 1|| (tok.countTokens()  == 2 && OS_NAME_LC.startsWith("mac os x"))) {
            File jar = new File(tok.nextToken());
            try {
                tmpDir = jar.getCanonicalFile().getParentFile().getParent();
            } catch (IOException e) {
                tmpDir = null;
            }
        } else {
            /**
             * 从 jvm 获取 jmeter.home 属性,没有的话就默认从环境变量 JMETER_HOME 取值,当然这个值也不一定有,因为不是所有人都会配置 JMETER_HOME 这个环境变量
             * 其实从这边开始,大家就会发现,Jmeter 会经常使用 System.getProperty 来获取一些属性,在后面的代码中我们也会经常见到这样的代码
             */
            tmpDir = System.getProperty("jmeter.home", System.getenv("JMETER_HOME"));
            if (tmpDir == null || tmpDir.length() == 0) {
                File userDir = new File(System.getProperty("user.dir"));
                tmpDir = userDir.getAbsoluteFile().getParent();
            }
        }
        if (tmpDir == null) {
            tmpDir = System.getenv("JMETER_HOME");
        }
        JMETER_INSTALLATION_DIRECTORY = tmpDir;
        boolean usesUNC = OS_NAME_LC.startsWith("windows");
        StringBuilder classpath = new StringBuilder();
        /**
         * 下面的几个目录大家就很眼熟了,就是 Jmeter 解压后,主目录下的文件夹,里面都是 Jmeter 可能用到的一些 jar 包
         */
        File[] libDirs = new File[] { new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),
                new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),
                new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};
        for (File libDir : libDirs) {
            File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
            if (libJars == null) {
                new Throwable("Could not access " + libDir).printStackTrace();
                continue;
            }
            /**
             * 不晓得为啥要排个序
             */
            Arrays.sort(libJars);
            for (File libJar : libJars) {
                try {
                    String s = libJar.getPath();
                    if (usesUNC) {
                        if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {
                            s = "\\\\" + s;
                        } else if (s.startsWith("//") && !s.startsWith("///")) {
                            s = "//" + s;
                        }
                    }
                    jars.add(new File(s).toURI().toURL());
                    classpath.append(CLASSPATH_SEPARATOR);
                    classpath.append(s);
                } catch (MalformedURLException e) {
                    EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e));
                }
            }
        }
        System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString());
        /**
         * 类加载器会加载扫描到的这些 jar 包,为 Jmeter 真正启动做好准备
         */
        loader = AccessController.doPrivileged(
                (PrivilegedAction<DynamicClassLoader>) () ->
                        new DynamicClassLoader(jars.toArray(new URL[jars.size()]))
        );
    }
}

从上面的代码中,我们可以看到,NewDriver 在实例化时,会执行一个静态代码块,主要作用就是加载 Jmeter 安装目录下的 jar 包。

Main方法介绍

下面介绍 NewDriver 的 main 方法,这个方法就是整个 Jmeter 启动的入口方法。

public static void main(String[] args) {
        /**
         * 检查初始化是不是报错了
         */
        if(!EXCEPTIONS_IN_INIT.isEmpty()) {
            System.err.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT));
        } else {
            /**
             * 设置当前线程的类加载器,也就是 Jmeter 自己写的那个动态类加载器
             */
            Thread.currentThread().setContextClassLoader(loader);
            /**
             * 配置一些日志属性,不重要
             */
            setLoggingProperties(args);
            try {
                /**
                 * 判断要不要用 GUI 模式启动,默认 true,也可以通过 Jmeter 命令行参数 -n 来指定使用非 GUI 模式启动
                 */
                if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) {
                    System.setProperty(HEADLESS_MODE_PROPERTY, "true");
                }
                /**
                 * 获取 Jmeter 类,作用类似于 Class.forName(String clazzName)
                 */
                Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter");
                /**
                 * 获取 Jmeter 实例
                 */
                Object instance = initialClass.getDeclaredConstructor().newInstance();
                /**
                 * 获取 Jmeter.start方法,并调用
                 */
                Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });
                startup.invoke(instance, new Object[] { args });
            } catch(Throwable e){ 
                e.printStackTrace();
                System.err.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY);
            }
        }
    }

main 方法其实很简单直接,就是看下是不是要启动 GUI,然后就是通过反射调用 Jmeter 的 start 方法,来开始测试。 综上,NewDriver 其实就是一个启动器,正如其所在源码模块 launcher 一样,他的作用就是为 Jmeter 真正启动做好准备。 好了,NewDriver 就介绍完了,下一章将介绍 Jmeter 这个核心类,以及调用其 start(String[] args) 之后会发生什么...

2
博客已迁移至:https://linvaux.github.io/
加入