解决jacoco agent遇到反射的问题

embedded/2024/11/22 7:19:20/

问题分析

在我们使用jacoco的agent的过程中,经常越到代码里使用了反射,而jacoco在插桩时会动态插入字段$jacocoData来记录探针信息,这样我们在使用反射遍历类的属性时会因为多了一个字段而导致业务代码出错,那么遇到这样的问题,我们应该怎么解决的。

问题解决

在清楚了问题后,我们就可以尝试去解决,小伙伴们动手能力是毋庸置疑的,内事不决问百度,外事不决问谷歌,于是快速有了答案。

  1. 方案一,因为jacoco在插桩字段的时候标注了$jacocoData是SYNTHETIC类型的字段,我们只需要在反射获取后进行排除即可
for (Field f : obj.getClass().getDeclaredFields()) {f.setAccessible(true);//过滤jacoco编译期间加入的 JacocoData 字段if (f.isSynthetic()) {continue;}
}

这个方法很容易实现,如果是你个开发的话,恭喜你解决了问题,但是我猜在看的各位大部分是测试,所以你还得继续看下去,听我继续啰嗦,作为一个工具侧,不可能通知所有业务线开发进行业务逻辑的变更,一个是工作量不小,二个是推进太难,所以面对大量的即存业务代码,我们只能尝试其他解决方案。

  1. 方案二,我选择退步,一般被反射的对象都是dto类或者bean对象,一般本身不会包含方法,这样的类进行代码覆盖检查没有意义,我们可以在进行插桩的时候进行排除
 -javaagent:/inject/jacoco/agent/jacocoagent.jar=includes=com.dr.*:dr,output=tcpserver,port=36300,address=0.0.0.0

这样就可以对被反射的类不进行插桩,进而不影响业务代码的正常运行,但是,如果不出意外,意外就发生了,开发怎么写的,我怎么知道?代码里遍地反射,撅起高高的头颅,我也是无言以对,这样肯定不能把所有的被反射的类排除,同样治标不治本。
3. 方案三,给你的反射偷偷做个手脚,我们进行插桩就是修改类的字节码,那我们是不是同时可以修改我们反射做个修改,让我们的反射过滤掉$jacocoData字段,恭喜你,找到了究极解决方案。
我们首先在jacoco agent里创建一个方法,作用就是getDeclaredFields的作用,获取类的所有字段,只不过过滤掉SYNTHETIC类型的字段。

    public static Field[] getDeclaredFields(Class<?> clazz) {Field[] fields = clazz.getDeclaredFields();return Arrays.stream(fields).filter(f -> !f.isSynthetic()).toArray(Field[]::new);}
}

然后替换掉我们调用getDeclaredFields方法的地方,在MethodInstrumenter类里面我们对类进行了插桩,有个visitMethodInsn表示方法内部调用的方法,我们去匹配到调用getDeclaredFields的地方,进行替换即可

/*** 访问方式* 这里主要解决反射调用字段$jacocoData的问题** @param opcode           操作码* @param owner            老板* @param methodName       方法名称* @param methodDescriptor 方法描述符* @param isInterface*/public void visitMethodInsn(int opcode, String owner, String methodName,String methodDescriptor, boolean isInterface) {if (owner.equals("java/lang/Class") && methodName.equals("getDeclaredFields")&& methodDescriptor.equals("()[Ljava/lang/reflect/Field;")) {super.visitMethodInsn(Opcodes.INVOKESTATIC,"org/jacoco/core/internal/util/ReflectionUtils","getDeclaredFields","(Ljava/lang/Class;)[Ljava/lang/reflect/Field;",false);} else {super.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface);}}

这样,每次调用jdk的getDeclaredFields方法被替换成调用我们自定义的getDeclaredFields,就解决了我们所有的问题。我们看看效果
在这里插入图片描述
这是我们的业务底代码
这是我们插桩后返回的属性在这里插入图片描述

这是我们对jacoco agent修改后返回的属性
在这里插入图片描述
好了,是不是很简单,聪明的你肯定早已经想到过这个办法,只是缺乏一点我的引导而已。


http://www.ppmy.cn/embedded/139556.html

相关文章

【C++】从C到C++

C和C一些语法区别 1.三目运算符&#xff1a;在C语言中返回的是一个常量&#xff0c;是不能被赋值的&#xff1b;而C中返回的是变量&#xff0c;可以被赋值 2.C中的函数必须要写返回值类型 3.在全局下&#xff0c;C不允许int a;和int a10;等这种重定义二义性操作 4.在C中不要…

-bash: ./kafka-topics.sh: No such file or directory--解决方案

使用./kafka-topics.sh脚本出现以下错误 其实就是没在该脚本所在的目录运行&#xff0c;使用docker安装kafka的话&#xff0c;该脚本一般放在Kafka安装目录中的/opt/bitnami/kafka/bin 先启动并进去一个Kafka容器&#xff1a; docker start kafka-0 docker exec -it kafka-0…

Makefile 之 join

join $(join <list1>,<list2> ) 名称&#xff1a;连接函数——join。 功能&#xff1a;把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多&#xff0c; 那么&#xff0c;<list1>中的多出…

“闲置经济”成新消费趋势,万物新生长期成长性如何?

2024年&#xff0c;“以旧换新”成为消费热词。以“史上最长”双11为分界点&#xff0c;以旧换新进入下半场&#xff0c;消费市场将怎么发展&#xff0c;依然备受关注。 值得关注的是&#xff0c;自以旧换新工作启动以来&#xff0c;新产品销售量上行的同时&#xff0c;也带来…

【MATLAB】续行符号对字符串失效

前言 之前对于遇到的问题一般都是在之前的一篇文章后面更新&#xff0c;比如这样的: (原文链接) 现在想想这样其实不够直观&#xff0c;也不方便查找&#xff0c;所以计划之后如果遇到问题就直接记录成一篇文章&#xff0c;反正在一个专栏也好找。 问题描述 一般来说&#xff…

Android开发实战班 -网络编程 - Retrofit 网络请求 + OkHttp 使用详解

在现代 Android 应用开发中&#xff0c;网络编程是必不可少的一部分。Retrofit 是 Square 公司推出的一款类型安全的 HTTP 客户端库&#xff0c;简化了与 RESTful API 的交互。Retrofit 基于 OkHttp&#xff0c;并提供了简洁的接口定义和强大的功能&#xff0c;如异步请求、请求…

ubuntu, 安装部署comfyui,记录2:下载模型GGuf及测试

0.清除工作流 1.安装manager 2024年最新ComfyUI汉化及manager插件安装详解&#xff01;_comfyui-manager-CSDN博客 ComfyUI Manager安装 转到ComfyUI的安装目录ComfyUI/custom_nodes; 使用git拉取ComfyUI Manager&#xff0c;git clone https://github.com/ltdrdata/Comf…

Java Database Connectivity (JDBC + Servlet)

Java Database Connectivity (JDBC)是一个Java API&#xff0c;用于与数据库进行连接和操作。通过JDBC&#xff0c;Java程序可以与各种关系型数据库进行通信&#xff0c;执行SQL查询、更新数据等操作。 一、Java连接数据库两种方式 ​​​​​ ​​ 二、Java中…