zl程序教程

您现在的位置是:首页 >  其他

当前栏目

JMeter5.1核心类SaveService解析jmx文件的源码分析

2023-04-18 14:26:50 时间

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 引入

<!-- 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,不支持。

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 转换器

要结合别名:类名键值对一起使用

_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文件和测试报告

// 解析和存储测试jmx文件
private static final XStream JMXSAVER = new XStreamWrapper(new PureJavaReflectionProvider());
// 解析和存储测试报告
private static final XStream JTLSAVER = new XStreamWrapper(new PureJavaReflectionProvider());

获取别名和类名关系映射的saveservice.properties文件

// 读取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静态代码块

初始化操作

    static {
        log.info("Testplan (JMX) version: {}. Testlog (JTL) version: {}", VERSION_2_2, VERSION_2_2);
        initProps();
        checkVersions();
    }

获取saveservice.properties文件中的键值对

    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文件

    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);
    }

存储别名和类名之间的映射关系

    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);
        }
     }

注册转换器

    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

    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文件

    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()+"'. 
Cause:
"+
                        ExceptionUtils.getRootCauseMessage(e) +"

 Detail:"+e, e);
            } else {
                throw new IllegalArgumentException("Problem loading XML. 
Cause:
"+
                        ExceptionUtils.getRootCauseMessage(e) +"

 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文件

    // 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('
');// Ensure terminated properly
        outputStreamWriter.close();
}