1.概述
JMeter生成和解析jmx文件主要是通过XStream库来实现的。
SaveService类将XStream进行了代码封装。
2.XStream介绍
XStream 是一个简单的基于 Java 库,Java 对象序列化到 XML,反之亦然(即:可以轻易的将 Java 对象和 xml 文档相互转换)。
2.1特点
- 使用方便 - XStream 的 API 提供了一个高层次外观,以简化常用的用例。
- 无需创建映射 - XStream 的 API 提供了默认的映射大部分对象序列化。
- 性能 - XStream 快速和低内存占用,适合于大对象图或系统。
- 干净的XML - XStream 创建一个干净和紧凑 XML 结果,这很容易阅读。
- 不需要修改对象 - XStream 可序列化的内部字段,如私有和最终字段,支持非公有制和内部类。默认构造函数不是强制性的要求。
- 完整对象图支持 - XStream 允许保持在对象模型中遇到的重复引用,并支持循环引用。
- 可自定义的转换策略 - 定制策略可以允许特定类型的定制被表示为XML的注册。
- 安全框架 - XStream 提供了一个公平控制有关解组的类型,以防止操纵输入安全问题。
- 错误消息 - 出现异常是由于格式不正确的XML时,XStream 抛出一个统一的例外,提供了详细的诊断,以解决这个问题。
- 另一种输出格式 - XStream 支持其它的输出格式,如 JSON。
2.2下载
官网下载
http://x-stream.github.io/download.html
或者 maven 引入
代码语言:txt复制<!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
3.SaveService源码分析
3.1 saveservice.properties文件
3.1.1别名和类名键值对
主要用于解析和存储测试jmx文件,左边是别名,右边是类名。
只适用于JMeter自带结构,对于第三方扩展的Sampler,不支持。
代码语言:txt复制AccessLogSampler=org.apache.jmeter.protocol.http.sampler.AccessLogSampler
AjpSampler=org.apache.jmeter.protocol.http.sampler.AjpSampler
AjpSamplerGui=org.apache.jmeter.protocol.http.control.gui.AjpSamplerGui
AnchorModifier=org.apache.jmeter.protocol.http.modifier.AnchorModifier
AnchorModifierGui=org.apache.jmeter.protocol.http.modifier.gui.AnchorModifierGui
Argument=org.apache.jmeter.config.Argument
Arguments=org.apache.jmeter.config.Arguments
....
# removed in 3.2, class was deleted in r
monitorStats=org.apache.jmeter.visualizers.MonitorStats
sampleEvent=org.apache.jmeter.samplers.SampleEvent
3.1.2 转换器
要结合别名:类名键值对一起使用
代码语言:txt复制_org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseConverter=collection
_org.apache.jmeter.protocol.http.util.HTTPResultConverter=collection
_org.apache.jmeter.save.converters.BooleanPropertyConverter=
_org.apache.jmeter.save.converters.IntegerPropertyConverter=
_org.apache.jmeter.save.converters.LongPropertyConverter=
_org.apache.jmeter.save.converters.MultiPropertyConverter=collection
_org.apache.jmeter.save.converters.SampleEventConverter=
_org.apache.jmeter.save.converters.SampleResultConverter=collection
_org.apache.jmeter.save.converters.SampleSaveConfigurationConverter=collection
_org.apache.jmeter.save.converters.StringPropertyConverter=
_org.apache.jmeter.save.converters.HashTreeConverter=collection
_org.apache.jmeter.save.converters.TestElementConverter=collection
_org.apache.jmeter.save.converters.TestElementPropertyConverter=collection
_org.apache.jmeter.save.converters.TestResultWrapperConverter=collection
_org.apache.jmeter.save.ScriptWrapperConverter=mapping
3.2主要变量
创建XStream对象,用于解析和存储测试jmx文件和测试报告
代码语言:txt复制// 解析和存储测试jmx文件
private static final XStream JMXSAVER = new XStreamWrapper(new PureJavaReflectionProvider());
// 解析和存储测试报告
private static final XStream JTLSAVER = new XStreamWrapper(new PureJavaReflectionProvider());
获取别名和类名关系映射的saveservice.properties文件
代码语言:txt复制// 读取saveservice.properties文件使用
private static final String SAVESERVICE_PROPERTIES_FILE = "saveservice.properties";
// 别名转换为类名的properties对象,主要用于解析jmx文件使用
private static final Properties aliasToClass = new Properties();
// 类名转换为别名的properties对象,主要用于保存jmx文件使用
private static final Properties classToAlias = new Properties();
3.3静态代码块
初始化操作
代码语言:txt复制 static {
log.info("Testplan (JMX) version: {}. Testlog (JTL) version: {}", VERSION_2_2, VERSION_2_2);
initProps();
checkVersions();
}
获取saveservice.properties文件中的键值对
代码语言:txt复制 private static void initProps() {
// Load the alias properties
try {
fileVersion = getChecksumForPropertiesFile();
System.out.println("fileVersion:" fileVersion);
} catch (IOException | NoSuchAlgorithmException e) {
log.error("Can't compute checksum for saveservice properties file", e);
throw new JMeterError("JMeter requires the checksum of saveservice properties file to continue", e);
}
try {
// 读取saveservce.properties文件
Properties nameMap = loadProperties();
// now create the aliases
for (Map.Entry<Object, Object> me : nameMap.entrySet()) {
// 这是对应jmx文件中的key-value
String key = (String) me.getKey();
String val = (String) me.getValue();
// 别名:类名键值对
if (!key.startsWith("_")) { // $NON-NLS-1$
// 初始化alias
makeAlias(key, val);
} else {
// process special keys
if (key.equalsIgnoreCase("_version")) { // $NON-NLS-1$
propertiesVersion = val;
log.info("Using SaveService properties version {}", propertiesVersion);
} else if (key.equalsIgnoreCase("_file_version")) { // $NON-NLS-1$
log.info("SaveService properties file version is now computed by a checksum,"
"the property _file_version is not used anymore and can be removed.");
} else if (key.equalsIgnoreCase("_file_encoding")) { // $NON-NLS-1$
fileEncoding = val;
log.info("Using SaveService properties file encoding {}", fileEncoding);
} else {
key = key.substring(1);// Remove the leading "_"
System.out.println("subkey:" key);
// XStream转换器
registerConverter(key, val);
}
}
}
} catch (IOException e) {
log.error("Bad saveservice properties file", e);
throw new JMeterError("JMeter requires the saveservice properties file to continue");
}
}
读取saveservce.properties文件
代码语言:txt复制 public static Properties loadProperties() throws IOException{
Properties nameMap = new Properties();
// 获取saveservice.properties文件
File saveServiceFile = getSaveServiceFile();
if (saveServiceFile.canRead()){
try (FileInputStream fis = new FileInputStream(saveServiceFile)){
// 加载文件,获取键值对
nameMap.load(fis);
}
}
return nameMap;
}
private static File getSaveServiceFile() {
String saveServiceProps = JMeterUtils.getPropDefault(SAVESERVICE_PROPERTIES,SAVESERVICE_PROPERTIES_FILE); //$NON-NLS-1$
if (saveServiceProps.length() > 0){ //$NON-NLS-1$
return JMeterUtils.findFile(saveServiceProps);
}
throw new IllegalStateException("Could not find file configured in saveservice_properties property set to:" saveServiceProps);
}
存储别名和类名之间的映射关系
代码语言:txt复制 private static void makeAlias(String aliasList, String clazz) {
String[] aliases = aliasList.split(","); // Can have multiple aliases for same target classname
String alias = aliases[0];
for (String a : aliases){
// 存储别名:类名到aliasToClass
Object old = aliasToClass.setProperty(a,clazz);
if (old != null){
log.error("Duplicate class detected for {}: {} & {}", alias, clazz, old);
}
}
// 存储类名:别名到classToAlias
Object oldval=classToAlias.setProperty(clazz,alias);
if (oldval != null) {
log.error("Duplicate alias detected for {}: {} & {}", clazz, alias, oldval);
}
}
注册转换器
代码语言:txt复制 private static void registerConverter(String key, String val) {
try {
final String trimmedValue = val.trim();
// 判断val的值
boolean useMapper = "collection".equals(trimmedValue) || "mapping".equals(trimmedValue); // $NON-NLS-1$ $NON-NLS-2$
registerConverter(key, JMXSAVER, useMapper);
registerConverter(key, JTLSAVER, useMapper);
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException | IllegalArgumentException|
SecurityException | InvocationTargetException | NoSuchMethodException e1) {
log.warn("Can't register a converter: {}", key, e1);
}
}
// 注册转换器,注册一个转换器需要继承com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter接口
private static void registerConverter(String key, XStream jmxsaver, boolean useMapper)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException,
ClassNotFoundException {
if (useMapper){
jmxsaver.registerConverter((Converter) Class.forName(key).getConstructor(Mapper.class).newInstance(jmxsaver.getMapper()));
} else {
jmxsaver.registerConverter((Converter) Class.forName(key).getDeclaredConstructor().newInstance());
}
}
3.4重写Xstream类
重写Xstream类,解析和转换xml
代码语言:txt复制 private static final class XStreamWrapper extends XStream {
private XStreamWrapper(ReflectionProvider reflectionProvider) {
super(reflectionProvider);
}
// 反序列化
// Override wrapMapper in order to insert the Wrapper in the chain
@Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
// Provide our own aliasing using strings rather than classes
return new MapperWrapper(next){
// Translate alias to classname and then delegate to wrapped class
@Override
public Class<?> realClass(String alias) {
// 根据别名获取类名
String fullName = aliasToClass(alias);
if (fullName != null) {
fullName = NameUpdater.getCurrentName(fullName);
}
return super.realClass(fullName == null ? alias : fullName);
}
// 序列化
// Translate to alias and then delegate to wrapped class
@Override
public String serializedClass(@SuppressWarnings("rawtypes") // superclass does not use types
Class type) {
if (type == null) {
return super.serializedClass(null); // was type, but that caused FindBugs warning
}
// 根据类名获取别名
String alias = classToAlias(type.getName());
return alias == null ? super.serializedClass(type) : alias ;
}
};
}
}
3.5 解析测试jmx文件
代码语言:txt复制 public static HashTree loadTree(File file) throws IOException {
log.info("Loading file: {}", file);
try (InputStream inputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream =
new BufferedInputStream(inputStream)){
return readTree(bufferedInputStream, file);
}
}
private static HashTree readTree(InputStream inputStream, File file)
throws IOException {
ScriptWrapper wrapper = null;
try {
// Get the InputReader to use
InputStreamReader inputStreamReader = getInputStreamReader(inputStream);
// 通过XStream的fromXML方法进行解析
wrapper = (ScriptWrapper) JMXSAVER.fromXML(inputStreamReader);
inputStreamReader.close();
if (wrapper == null){
log.error("Problem loading XML: see above.");
return null;
}
// 将测试jmx文件转换为HashTree树结构对象
return wrapper.testPlan;
} catch (CannotResolveClassException | ConversionException | NoClassDefFoundError e) {
if(file != null) {
throw new IllegalArgumentException("Problem loading XML from:'" file.getAbsolutePath() "'. nCause:n"
ExceptionUtils.getRootCauseMessage(e) "nn Detail:" e, e);
} else {
throw new IllegalArgumentException("Problem loading XML. nCause:n"
ExceptionUtils.getRootCauseMessage(e) "nn Detail:" e, e);
}
}
}
private static InputStreamReader getInputStreamReader(InputStream inStream) {
// Check if we have a encoding to use from properties
Charset charset = getFileEncodingCharset();
return new InputStreamReader(inStream, charset);
}
3.6 存储测试jmx文件
代码语言:txt复制 // Called by Save function
public static void saveTree(HashTree tree, OutputStream out) throws IOException {
// Get the OutputWriter to use
OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out);
writeXmlHeader(outputStreamWriter);
// Use deprecated method, to avoid duplicating code
ScriptWrapper wrapper = new ScriptWrapper();
// 将HashTree对象转换为xml文件
wrapper.testPlan = tree;
JMXSAVER.toXML(wrapper, outputStreamWriter);
outputStreamWriter.write('n');// Ensure terminated properly
outputStreamWriter.close();
}