JMeter 源码解读 [3] - 程序入口 NewDriver

NewDriver 的完整路径是org.apache.jmeter.NewDriver , 它是整个JMeter 的入口类,主要的作用是提供了main 函数用于启动JMeter.

不过在启动程序 main 函数之前,NewDriver会做一些运行环境的检查和初始化,主要通过 static initializer 的方式来做,我们先来看一些代码初始化的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$

// 其他省略了一些代码不贴这里了,否则太长了

// 这个就是静态初始化的代码块
static {
final List<URL> jars = new LinkedList<>();
final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);

// Find JMeter home dir from the initial classpath
String tmpDir;
StringTokenizer tok = new StringTokenizer(initiaClasspath, File.pathSeparator);
if (tok.countTokens() == 1
|| (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath
&& OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$
)
) {
File jar = new File(tok.nextToken());
try {
tmpDir = jar.getCanonicalFile().getParentFile().getParent();
} catch (IOException e) {
tmpDir = null;
}
} else {// e.g. started from IDE with full classpath
tmpDir = System.getProperty("jmeter.home","/Users/ylshao/code/github/jmeter");// Allow override $NON-NLS-1$ $NON-NLS-2$
if (tmpDir.length() == 0) {
File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$
tmpDir = userDir.getAbsoluteFile().getParent();
}
}
JMETER_INSTALLATION_DIRECTORY=tmpDir; // 获取 jmeter 当前安装目录

这个是静态初始化代码的一部分,我们看到主要的作用是判断 JMeter 的安装目录,对于通过jmeter.sh 运行的方式是通过 classpath 来推导 parent ,否则就通过读取系统变量 jmeter.home 来获取,最后把tmpDir 赋值给 JMETER_INSTALLATION_DIRECTORY 变量,JMETER_INSTALLATION_DIRECTORY 这个变量还是很很重要的,用来推导整个jmeter 后续的目录结构, 看下面的代码

1
2
3
4
5
6
// Add standard jar locations to initial classpath
StringBuilder classpath = new StringBuilder();
File[] libDirs = new File[] { new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$

通过上面计算出来的JMETER_INSTALLATION_DIRECTORY,可以获取相关 lib 和其子目录的路径,并读取到他们下面所有的jar 文件,最后的目的是通过这些 jar 文件创建一个 classloader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
for (File libJar : libJars) {
try {
String s = libJar.getPath();

// Fix path to allow the use of UNC URLs
if (usesUNC) {
if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$
s = "\\\\" + s;// $NON-NLS-1$
} else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$
s = "//" + s;// $NON-NLS-1$
}
} // usesUNC

jars.add(new File(s).toURI().toURL());// See Java bug 4496398
classpath.append(CLASSPATH_SEPARATOR);
classpath.append(s);
} catch (MalformedURLException e) { // NOSONAR
EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e));
}
}
}

// ClassFinder needs the classpath
System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString());
// ### 创建自定义的classloader
loader = AccessController.doPrivileged(
(PrivilegedAction<DynamicClassLoader>) () ->
new DynamicClassLoader(jars.toArray(new URL[jars.size()]))
);
}

直到创建了 loader 这个自定义的 classloader 后,整个静态初始化才算结束

最后我们来看一些 main 函数,逻辑就比较简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) {
if(!EXCEPTIONS_IN_INIT.isEmpty()) {
System.err.println("Con figuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use
} else {
Thread.currentThread().setContextClassLoader(loader);


setLoggingProperties(args);

try {
// Only set property if it has not been set explicitely
if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) {
System.setProperty(HEADLESS_MODE_PROPERTY, "true");
}
Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$
Object instance = initialClass.getDeclaredConstructor().newInstance();
Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$
startup.invoke(instance, new Object[] { args });
} catch(Throwable e){ // NOSONAR We want to log home directory in case of exception
e.printStackTrace(); // NOSONAR No logger at this step
System.err.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use
}
}
}
  1. 先判断一下EXCEPTIONS_IN_INIT 是否为空,如果不为空就表示静态初始化有异常,直接退出进程并打印错误信息
  2. 初始化一些基本的日志配置
  3. 用刚才初始化的 classloader 加载类 org.apache.jmeter.JMeter 然后通过 java 反射的方式来调用 org.apache.jmeter.JMeterstart 方法,正式完成 JMeter 的启动