JMeter源码解析之NewDriver.java(二)完结
NewDriver.java主要作用
JMeter程序入口:JMeter的主类-设置初始类路径和加载程序。
文件路径
路径地址:...\apache-jmeter-5.1\src\core\org\apache\jmeter\NewDriver.java
关于Main内容中的代码解析
java
/**
* The main program which actually runs JMeter.
* 实际运行JMeter的主程序
* @param args
* the command line arguments
*/
public static void main(String[] args) {
/**
* 当启动加载的异常内容里面,没有相应的报错时,程序启动,否则报错。
* EXCEPTIONS_IN_INIT的数据主要来源 EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e));
*/
if(!EXCEPTIONS_IN_INIT.isEmpty()) {
System.err.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use
} else {
//通过将 loader 设置为当前线程的上下文类加载器,可以将自定义的类加载器 loader 用于当前线程中的类加载操作,从而实现需要使用特定类加载器加载的类或资源
Thread.currentThread().setContextClassLoader(loader);
//设置日志环境系统属性,简单的可以连接为,当我们入参为-j D:\1\1\1.log,我们会在对应的路径D:\1\1\生成1.log日志
setLoggingProperties(args);
try {
// Only set property if it has not been set explicitely
// 设置系统属性变成noGUI模式运行
if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) {
System.setProperty(HEADLESS_MODE_PROPERTY, "true");
}
/*
* 这段代码通过反射加载和执行了org.apache.jmeter.JMeter类的启动方法。
* 它首先加载主类,然后使用反射创建主类的实例,接着获取start方法并通过反射执行该方法,以启动Apache JMeter。
* */
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
}
}
}
上述代码中我们可以发现一有趣的代码:
java
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 });
然后就比较好奇,为啥我们不直接引用JMeter这个类,然后直接调用里调用里面的start方法呢?所以针对这个去查询了下资料,得到如下回答:
使用反射方式调用方法和直接调用类的方法有以下不同点和好处
1.动态性:通过反射,可以在运行时动态地获取类和方法的信息,而不需要在编译时确定。这样可以实现更加灵活的代码逻辑和动态的代码调用。
2.解耦合:通过反射,可以在不知道具体类的情况下调用其方法。这样可以实现松耦合的代码结构,提高代码的可维护性和可扩展性。
3.功能扩展:使用反射可以实现一些无法在编译时确定的功能,比如动态添加、修改和删除类的属性、方法等。
4.框架开发:很多框架和库都使用反射来实现通用的功能,比如Spring框架的IOC容器、Hibernate框架的ORM映射等。通过反射可以实现通用的代码模块,提高开发效率和代码复用性。
需要注意的是,使用反射可能会带来一些性能上的损失,因为反射调用的过程需要额外的计算和处理。但是在很多情况下,这种性能损失可以接受,特别是在需要动态性和灵活性的场景下。因此,在选择使用反射还是直接调用类的方法时,需要综合考虑具体的需求和性能要求。
关于setLoggingProperties内容中的代码解析
java
/*
* Set logging related system properties.
* 设置与日志记录相关的系统属性
*/
private static void setLoggingProperties(String[] args) {
//获取NoGUI时输入的命令中-j后面的参数,如-j D:\1\1\1.log,返回的结果是:D:\1\1\1.log
String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$
if (jmLogFile != null && !jmLogFile.isEmpty()) {
jmLogFile = replaceDateFormatInFileName(jmLogFile);
//System.setProperty()方法用于设置系统属性。系统属性是一些键值对,用于存储关于操作系统和应用程序的配置信息
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$
} else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$
}
//日志类型,但是目前网上搜到的资料,并没有确切的-i相关的参数进行入参,可以理解为当前就是默认的日志格式
String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$
File logConfFile = null;
if (jmLogConf != null && !jmLogConf.isEmpty()) {
logConfFile = new File(jmLogConf);
} else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$
logConfFile = new File("log4j2.xml");// $NON-NLS-1$
if (!logConfFile.isFile()) {
logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$
}
}
if (logConfFile != null) {
System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$
}
}
关于getCommandLineArgument内容中的代码解析
java
/*
* Find command line argument option value by the id and name.
* 根据id和名称查找命令行参数选项值
* 输入的JMeter命令比如是这样:jmeter -n -t C:\Users\Desktop\tokenZB.jmx -l D:\1\1\1.jtl -e -o D:\1\1\report -j D:\1\1\1.log
* 然后我们可以得到一个String类型数组,内容就是这样的:
* new String[]{"-n","-t","C:\\Users\\Desktop\\tokenZB.jmx","-l","D:\\1\\1\\1.jtl","-e","-o","D:\\1\\1\\report","-j","D:\\1\\1\\1.log"};
*/
private static String getCommandLineArgument(String[] args, int id, String name) {
//获取对应的命令后面的参数,比如-j,为配置日志路径,则通过传入id获取对应-j后面的参数内容,"--"也是一样的道理
final String shortArgName = "-" + ((char) id);// $NON-NLS-1$
final String longArgName = "--" + name;// $NON-NLS-1$
String value = null;
for (int i = 0; i < args.length; i++) {
//获取某个参数后面指定的值,比如-j后面的值,并且此处是可以进行指定参数与自己的输入的参数连写,JMeter也会进行自动识别
if ((shortArgName.equals(args[i]) && i < args.length - 1)
|| longArgName.equals(args[i])) {
if (!args[i + 1].startsWith("-")) {// $NON-NLS-1$
value = args[i + 1];
}
break;
//比如原则上-j D:\1\\1\1.log,但是如果我们不小心写成了-jD:\1\\1\1.log,JMeter可以读取-j后面的参数,即:D:\1\\1\1.log
} else if (!shortArgName.equals(args[i]) && args[i].startsWith(shortArgName)) {
value = args[i].substring(shortArgName.length());
break;
}
}
return value;
}