jacoco引发的血案

终于回归java了!


情景复现:109机器管理后台操作时报异常,后台报错。
这里写图片描述
很明显,这是类型转换异常,顺着日志找代码。
这里写图片描述
显示转换抛了,然后本机调试,没有出现这个问题,顺利通过。又试了下121机器,没有问题。疑惑来了,为什么同样的代码无法复现,只有109有问题?
加日志,观察154行的参数究竟是什么,为什么会报错?
这里写图片描述
日志信息如下
这里写图片描述
这个时候还要看看传入的这个obj也就是RuleReqPam这个东西究竟是个啥
这里写图片描述
看上去也没啥问题,那 “[Z cannot be cast to java.lang.String”这个错误是怎么出现的???还是要看日志
最后一个字段信息,即日志中的fields 10的内容有问题。
首先,可以数一下RuleReqPam中字段个数:10个,日志中的fields顺序是for循环的次数,fields 10 显然是有问题的,表示这个参数中有11个字段,为什么?
其次,field 10 的内容:
这里写图片描述
什么鬼???
综上,field 10 肯定是有问题的,哪里冒出来的10呢?还要看日志
这里写图片描述
可以看到,之前的field的name都是RuleReqPam中的字段,field 10 的 name 却是 $jacocoData,对,它自己高傲的站出来了!就是jacoco!
哪里用jacoco了?pom?看了下,没有,再想想,只有109有这个现象,果断打开jenkins,看!!!
这里写图片描述
ant?
这里写图片描述
这里写图片描述
(感谢百度)
你以为这就结束了?注释掉就可以了?too young,根本没用的,测试大兄弟搞了tomcat
这里写图片描述
找到原因了,开心不?这就结束了?no!你知道啥是jacoco么?为啥要用jacoco?jacoco是怎么影响到代码执行的?我猜很多人都不知道,当然,我也不知道,所以我学(bai)习(du)了一下,供大家参考。
jacoco是一个代码覆盖率工具,它会在字节码文件中植入统计覆盖率的代码=修改字节码,还记得问题代码不?通过反射进行操作,很显然jacoco很尽职的对这个对象进行了操作,导致程序异常。

惯例,甩链接:https://blog.csdn.net/ohcezzz/article/details/78416125

2022.6.13更新
评论区有同学问怎么解决,我是把jacoco的启动参数干掉了,相当于不用它,但其实这样并没有解决问题,相信当时提问的同学看到这个回答也不会很满意。我搜了一下,这片博客写的很清晰,大家可以参考下:https://blog.csdn.net/m0_37772518/article/details/113124275

另外,有同学针对出问题的原因也提出了疑问:“他不是插入一些bool类型的probe吗,为啥这里会有这种错误”。当时只想着解决问题,确实忽视了问题发生的原因,感谢这位同学发现问题后及时抛出来。通过上一个问题,可以大概找到原因——jacoco会修改编译后的class文件。结合这个原因,再看看出问题的代码

String field = (String)fields[i].get(obj);

程序通过反射拿到类中所有成员变量,并强转为String。因为jacoco修改了class文件,导致类中多了一个名为 $jacocoData 的成员变量,此变量是布尔类型的数组,所以强转报错了。
“[Z cannot be cast to java.lang.String”至于报错中的“[Z”,在 CondyProbeArrayStrategy 类中是这么做的

public int storeInstance(final MethodVisitor mv, final boolean clinit,
			final int variable) {
		final Handle bootstrapMethod = new Handle(Opcodes.H_INVOKESTATIC,
				className, InstrSupport.INITMETHOD_NAME, B_DESC, isInterface);
		// As a workaround for https://bugs.openjdk.java.net/browse/JDK-8216970
		// constant should have type Object
		mv.visitLdcInsn(new ConstantDynamic(InstrSupport.DATAFIELD_NAME,
				"Ljava/lang/Object;", bootstrapMethod));
		mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");
		mv.visitVarInsn(Opcodes.ASTORE, variable);
		return 1;
	}

看下InstrSupport.DATAFIELD_NAME是啥

public static final String DATAFIELD_NAME = "$jacocoData";

源码我也只是看了个大概,有说的不对的地方欢迎大家指正。

———————————— 分割线 ————————————
多年之后又遇到了这个问题,记一下解决方法,只需要在 for 循环的地方加一个判断就可以了

for(Field field : fields){
	// 忽略synthetic成员
	if (field.isSynthetic()) {
		continue;
	}
	// 后续为业务逻辑
}
  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值