前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JMeter5.1核心类SaveService解析jmx文件的源码分析

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

原创
作者头像
天堂小说
发布2021-12-03 16:39:30
1.6K0
发布2021-12-03 16:39:30
举报
文章被收录于专栏:JMeter源码分析

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) +"\n\n Detail:"+e, e);
            } else {
                throw new IllegalArgumentException("Problem loading XML. \nCause:\n"+
                        ExceptionUtils.getRootCauseMessage(e) +"\n\n 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();
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.概述
  • 2.XStream介绍
    • 2.1特点
      • 2.2下载
      • 3.SaveService源码分析
        • 3.1 saveservice.properties文件
          • 3.1.1别名和类名键值对
          • 3.1.2 转换器
        • 3.2主要变量
          • 3.3静态代码块
            • 3.4重写Xstream类
              • 3.5 解析测试jmx文件
                • 3.6 存储测试jmx文件
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档