排查jacoco覆盖率对反射问题的影响

news2024/11/26 20:43:34

最近业务部门开始推行,在全部后台应用中自动开启覆盖率测试。然而,不久后就有业务测试的同学反馈出现问题。

问题的现象如下:
在这里插入图片描述

我们的业务通过 HTTP 调用腾讯OSS的服务,结果得到了以上的报错信息。测试同学验证后发现,关闭覆盖率测试后,问题就消失了。因此,我们可以大致确定是覆盖率引起的问题。

按照经验,覆盖率引起的问题基本上只有一种情况。这里我们可以看看官方文档中的FAQ。

我的代码使用反射。为什么用JaCoCo执行会失败?

为了收集执行数据,JaCoCo 检测被测类,它向类添加两个成员:一个私有静态字段 $jacocoData 和一个私有静态方法 $jacocoInit()。两个成员都被标记为合成的。

请更改您的代码以忽略合成成员。无论如何,这是一个很好的做法,因为 Java 编译器在某些情况下也会创建合成成员。

但这些仅是我猜测的原因。我们需要具体问题,具体分析才行。这个问题比较容易分析,因为我们可以发现报错的原因是传递的参数不合法,所以才会有这样的错误提示。因此,只需对比开启覆盖率和未开启覆盖率两次请求的数据,就可以查看两次请求的差异,进而分析影响请求内容的代码逻辑。

具体分析

尝试1: arthas

private HttpResponse executeOneRequest(HttpContext context, HttpRequestBase httpRequest) {
    HttpResponse httpResponse = null;
    try {
        httpResponse = httpClient.execute(httpRequest, context);
    } catch (IOException e) {
        httpRequest.abort();
        throw ExceptionUtils.createClientException(e);
    }
    return httpResponse;
}

跟踪代码我们发现,最后qcloud 的http接口执行会到这里来,而刚好,它这里有一个入参数 httpRequest, 那就可以通过arthas 去watch 一下看看对应的入参的差异内容啦。

watch com.qcloud.cos.http.DefaultCosHttpClient executeOneRequest '{params,returnObj,throwExp}'  -n 5  -x 3

我们看下打印的结果数据

在这里插入图片描述

我们发现请求的内容是一个流数据,所以想要通过arthas打印出内容,感觉不太可行了。只能换一种方式

尝试2: 抓包

在抓包上呢 又有一个问题,因为我们请求的域名是一个https的,所以想要抓包具体的包的内容,就有点困难了,所以只能尝试去修改下请求的地址跟协议,因为我们的目的也只是为了能够看到请求的内容而已。

在这里插入图片描述

最后我们发现两个请求的差异的内容

插桩后的请求xml格式内容

<Request>
    <Tag>Transcode</Tag>
    <BucketName>cos-public-1304449511</BucketName>
    <QueueId>p870dd99714054da5b311bc70d3110bf9</QueueId>
    <CallBack>http://xxx/cstore/api/v3/callback/async/task/tencent</CallBack>
    <CallBackFormat>json</CallBackFormat>
    <Input>
        <Object>dev-cos-public/90b73c9786584f5e9e7748a73b7bf984.mp3</Object>
    </Input>
    <Operation>
        <Watermark></Watermark>
        <RemoveWatermark></RemoveWatermark>
        <ConcatTemplate>
            <Video></Video>
            <Audio></Audio>
        </ConcatTemplate>
        <Transcode>
            <Container>
                <Format>mp4</Format>
            </Container>
            <TimeInterval></TimeInterval>
            <Video></Video>
            <Audio>
                <Codec>aac</Codec>
            </Audio>
            <TransConfig></TransConfig>
        </Transcode>
        <DigitalWatermark></DigitalWatermark>
        <Output>
            <Region>ap-shanghai</Region>
            <Object>dev-cos-public/b8a483f8b61e4d27baec298752416c1c.mp4</Object>
            <Bucket>cos-public-1304449511</Bucket>
        </Output>
        <PicProcess></PicProcess>
        <Snapshot>
            <SpriteSnapshotConfig></SpriteSnapshotConfig>
        </Snapshot>
        <Segment>
            <HlsEncrypt></HlsEncrypt>
        </Segment>
        <SmartCover></SmartCover>
        <VideoMontage>
            <Video></Video>
            <Audio></Audio>
            <AudioMix></AudioMix>
        </VideoMontage>
    </Operation>
</Request>

未插桩的请求数据

<Request>
    <Tag>Transcode</Tag>
    <BucketName>cos-public-1304449511</BucketName>
    <QueueId>p870dd99714054da5b311bc70d3110bf9</QueueId>
    <CallBack>http://cstore-dev.test.seewo.com/cstore/api/v3/callback/async/task/tencent</CallBack>
    <CallBackFormat>json</CallBackFormat>
    <Input>
        <Object>dev-cos-public/90b73c9786584f5e9e7748a73b7bf984.mp3</Object>
    </Input>
    <Operation>
        <Transcode>
            <Container>
                <Format>mp4</Format>
            </Container>
            <Audio>
                <Codec>aac</Codec>
            </Audio>
        </Transcode>
        <Output>
            <Region>ap-shanghai</Region>
            <Object>dev-cos-public/e5ef796313e0490f9571ede6758253a2.mp4</Object>
            <Bucket>cos-public-1304449511</Bucket>
        </Output>
    </Operation>
</Request>

通过上述的对比,我们就会发现,插桩后的xml请求多出来了很多多余的标签,那我们就要回到代码里面去查看,这个标签是在什么时候被添加的了。

认真查看代码后,我们发现增加具体的标签逻辑是在以下的代码中进行的

private static void addOperation(XmlWriter xml, MediaJobsRequest request) {
    MediaJobOperation operation = request.getOperation();
    xml.start("Operation");

    addIfNotNull(xml, "TemplateId", operation.getTemplateId());
    addWatermarkTemplateId(xml, operation.getWatermarkTemplateId());
    addWatermar(xml, operation.getWatermark());
    addWatermarList(xml, operation.getWatermarkList());
    addRemoveWatermark(xml, operation.getRemoveWatermark());
    addConcat(xml, operation.getMediaConcatTemplate());
    addTranscode(xml, operation.getTranscode());
    addExtractDigitalWatermark(xml, operation.getExtractDigitalWatermark());
    addMediaDigitalWatermark(xml, operation.getDigitalWatermark());
    addOutput(xml, operation.getOutput());
    addPicProcess(xml, operation.getPicProcess());
    addSnapshot(xml, operation.getSnapshot());
    addSegment(xml, operation.getSegment());
    addSmartCover(xml, operation.getSmartCover());
    addVideoMontage(xml, operation.getVideoMontage());
    xml.end();
}

我们看下 addWatermar 的逻辑看看

private static void addWatermar(XmlWriter xml, MediaWatermark watermark) {
  if (objIsNotValid(watermark)) {
      xml.start("Watermark");
      addIfNotNull(xml, "Type", watermark.getType());
      addIfNotNull(xml, "Dx", watermark.getDx());
      addIfNotNull(xml, "Dy", watermark.getDy());
      addIfNotNull(xml, "EndTime", watermark.getEndTime());
      addIfNotNull(xml, "LocMode", watermark.getLocMode());
      addIfNotNull(xml, "Pos", watermark.getPos());
      addIfNotNull(xml, "StartTime", watermark.getStartTime());

      if ("Text".equalsIgnoreCase(watermark.getType())) {
          MediaWaterMarkText text = watermark.getText();
          xml.start("Text");
          addIfNotNull(xml, "FontColor", text.getFontColor());
          addIfNotNull(xml, "FontSize", text.getFontSize());
          addIfNotNull(xml, "FontType", text.getFontType());
          addIfNotNull(xml, "Text", text.getText());
          addIfNotNull(xml, "Transparency", text.getTransparency());
          xml.end();
      } else if ("Image".equalsIgnoreCase(watermark.getType())) {
          MediaWaterMarkImage image = watermark.getImage();
          xml.start("Image");
          addIfNotNull(xml, "Height", image.getHeight());
          addIfNotNull(xml, "Mode", image.getMode());
          addIfNotNull(xml, "Transparency", image.getTransparency());
          addIfNotNull(xml, "Url", image.getUrl());
          addIfNotNull(xml, "Width", image.getWidth());
          xml.end();
      }
      xml.end();
  }
}

我们会发现,这里的重点是在于 objIsNotValid 只有这个为true 才会去添加 Watermark 的标签的。

public static Boolean objIsNotValid(Object obj) {
    //查询出对象所有的属性
    Field[] fields = obj.getClass().getDeclaredFields();
    //用于判断所有属性是否为空,如果参数为空则不查询
    for (Field field : fields) {
        //不检查 直接取值
        field.setAccessible(true);
        try {
            Object o = field.get(obj);
            if (!isEmpty(o)) {
                //不为空
                return true;
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    return false;
}

代码的解释也很清楚了,就是通过反射的方式判断所传递的对象中的所有成员变量的属性值是否为空,如果不为空,就会进行添加水印等操作。

我们也断点去看下。

在这里插入图片描述

结合上图,很容易就能看出,MediaWaterMark 类在插桩后,将通过反射获取到多一个 $jacococData 成员变量。由于它不为空,前面的判断逻辑就会出现问题,导致水印标签被添加上去。

解决这个问题非常简单,因为网上已经有很多相应的解决措施了。

public static Boolean objIsNotValid(Object obj) {
        //查询出对象所有的属性
        Field[] fields = obj.getClass().getDeclaredFields();
        //用于判断所有属性是否为空,如果参数为空则不查询
        for (Field field : fields) {
            //不检查 直接取值
            field.setAccessible(true);
            try {
                Object o = field.get(obj);
								// 如果是一个合成变量就跳过
                if (field.isSynthetic()) {
                    continue;
                }
                if (!isEmpty(o)) {
                    //不为空
                    return true;
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

总结

本文讨论了在使用jacoco覆盖率工具时,由于其插桩导致的反射问题。通过分析传递参数不合法的错误提示,比较开启和未开启覆盖率两次请求的数据,发现插桩后的请求多出了很多多余的标签,最终发现是由于插桩后的类中多了一个 $jacococData 成员变量导致的。解决方法是在判断对象属性是否为空时,跳过合成变量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/538580.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RocketMQ】004-Spring Boot 集成 RocketMQ

【RocketMQ】004-Spring Boot 集成 RocketMQ 文章目录 【RocketMQ】004-Spring Boot 集成 RocketMQ一、基本使用1、创建 Spring Boot 项目&#xff0c;并引入 RocketMQ 依赖2、application.yml 配置3、消息生产者4、消息消费者5、消息调用接口6、启动 RocketMQ7、启动项目&…

(1)LED

LED正负极&#xff1a;大红旗——负极&#xff0c;小红旗——正极 如何看原理图电阻/电容值&#xff1a; eg&#xff1a; 102 10 2 10 * 10 ^ 2 1000 473 47 3 47 * 10 ^ 3 47000单片机使用TTL电频&#xff1a;高电平&#xff08;逻辑1&#xff09;5V 低电平&#xff…

C语言操作符详解(上)

C语言操作符详解&#xff08;上&#xff09; 前言1. 算术操作符2. 移位操作符2.1 左移操作符(<<)2.2 右移操作符&#xff08;>>&#xff09; 3. 位操作符3.1 按位与&#xff08;&&#xff09;3.2 按位或&#xff08;|&#xff09;3.4 按位异或&#xff08;^&am…

(4)定时器

51单片机的定时器属于单片机的内部资源&#xff0c;其电路的连接和运转均在单片机内部完成 作用&#xff1a; 用于计时系统替代长时间Delay&#xff0c;提高运行效率和速度任务切换 STC89C52定时器资源&#xff1a; 定时器个数&#xff1a;3个&#xff08;T0,T1,T2&#xf…

【MySQL】MySQL 运算符

目录 一、运算符简述 二、运算符使用 1.算术运算符 1.1 加法运算符 1.2 减法运算符 1.3 乘法与除法运算符 1.4 求模&#xff08;求余&#xff09;运算符 2.比较运算符 2.1 等号运算符 2.2 安全等于运算符 2.3 不等于运算符 2.4 空运算符 2.5 非空运算符 2.6 最小…

深度剖析Mybatis-plus Injector SQL注入器

背景 在项目中需要同时操作Sql Server 以及 MySQL 数据库&#xff0c;可能平时直接使用 BaseMapper中提供的方法习惯 了&#xff0c;不用的话总感觉影响开发效率&#xff0c;但是两个数据库的SQL语法稍微有点差别&#xff0c;有些暴露的方法并不能直接使用&#xff0c;所以便想…

WebSocket的那些事(3-STOMP实操篇)

目录 一、序言二、STOMP详解1、STOMP简单介绍2、STOMP协议内容3、使用STOMP的好处 三、代码示例1、Maven依赖2、开启WebSocket消息代理3、控制器4、前端页面greeting.html 四、测试1、连接服务端2、发送消息 五、STOMP消息传播流程六、结语 一、序言 上节中我们在 WebSocket的…

(11)LCD1602液晶显示屏

LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是一种字符型液晶显示模块&#xff0c;可以显示ASCII码的标准字符和其它的一些内置特殊字符&#xff0c;还可以有8个自定义字符&#xff0c;自带芯片扫描 显示容量&#xff1a;162个字符&#xff0c;每个字符…

【C++】STL六大组件简介

STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 1.STL的版本介绍 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本&#xff…

Unity里面CG和HLSL在写法上的一些区别

大家好&#xff0c;我是阿赵。这里继续讲URP相关的内容。 这次想讲的是CG和HLSL在写法上的一些区别。 一、为什么开始用HLSL 首先&#xff0c;基本上大家都知道的事情再说一遍。 三种Shader编程语言&#xff1a; 1、基于OpenGL的OpenGL Shading Language&#xff0c;缩写GLSL…

接口测试中postman环境和用例集

postman的环境使用 postman里有环境的设置&#xff0c;就是我们常说的用变量代替一个固定的值&#xff0c;这样做的好处是可以切换不同的域名、不同的环境变量&#xff0c;不同的线上线下账户等等场景。下面就看下怎么用吧。 创建一个Environment postman有一个envrionment&am…

Java是如何实现双亲委托机制的

Java 是一种面向对象的编程语言&#xff0c;它有一套独特的类加载机制。其中&#xff0c;双亲委托加载机制是 Java 类加载机制中的一个重要概念。本文将介绍 Java 的双亲委托加载机制是如何实现的&#xff0c;并解释其作用和优点。 Java 类加载机制 在 Java 中&#xff0c;类的…

瀑布流组件陷入商品重复怪圈?我是如何用心一解的!

目录 背景 瀑布流组件 什么是瀑布流组件 如何实现一个瀑布流组件 商品重复的原因 解决方案 方法一&#xff08;复杂&#xff0c;不推荐&#xff09;&#xff1a;标记位大法 方法二&#xff08;优雅&#xff0c;推荐&#xff09;&#xff1a;Promise 队列 大法 总结 背…

解决新思路#报错:ping: www.baidu.com: 未知的名称或服务--照着步骤来还是ping不通的可能原因

最近由ubantu转为centos7,配置hadoop&#xff0c;配置静态ip的过程中一直ping不通。之前配置ubantu的也是&#xff0c;终于这次在重启虚拟机和主机后发现了原因。 中途尝试过: 1.三次以上命令行反复操作 2.图形界面设置 3.看是否主机的网络适配器的网关与设置的IP地址产生冲突…

JavaScript实现计算100之间能被5整除的数的代码

以下为实现计算100之间能被5整除的数的程序代码和运行截图 目录 前言 一、计算100之间能被5整除的数 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本博文代码可以根据题…

2023最新100道渗透测试面试题(附答案)

眨眼间2023年快过去一半了&#xff0c;不知道大家有没有找到心仪的工作呀&#xff0c;今天我给大家整理了100道渗透测试面试题给大家&#xff0c;需要答案的话可以在评论区给我留言哦~ 第一套渗透面试题 什么是渗透测试&#xff1f;它的目的是什么&#xff1f; 渗透测试的五个…

DirectX12 简单入门(一)

在很久以前写过关于DirectX9的一些应用&#xff0c;直到现在DirectX12已经普及了。写完几个DirectX12测试代码之后&#xff0c;写一篇DirectX12简单入门介绍一下基本概念&#xff0c;以及环境搭建和编程过程。 编程环境 与DirectX9不同&#xff0c;在DirectX12开发中微软将需…

『MySQL 实战 45 讲』“order by” 是怎么工作的

“order by” 是怎么工作的 首先创建一个表 CREATE TABLE t ( id int(11) NOT NULL, city varchar(16) NOT NULL, name varchar(16) NOT NULL, age int(11) NOT NULL, addr varchar(128) DEFAULT NULL, PRIMARY KEY (id), KEY city (city) ) ENGINEInnoDB;全字段排序 在 cit…

自己搭建go web 框架

思想base部分day1:封装gee封装context上下文封装tree路由树分组封装group与中间件封装文件解析封装封装错误处理 思想 web框架服务主要围绕着请求与响应来展开的 搭建一个web框架的核心思想 1 便捷添加响应路径与响应函数(base) 2 能够接收多种数据类型传入(上下文context) 3 构…

【Linux】Linux入门学习之常用命令五

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…