解决jacoco agent遇到反射的问题

问题分析

在我们使用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修改后返回的属性
在这里插入图片描述
好了,是不是很简单,聪明的你肯定早已经想到过这个办法,只是缺乏一点我的引导而已。

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值