01 问题描述
JaCoCo是一款被广泛应用于公司内部的开源覆盖率工具,将其引用至测试环境后,机器启动正常,但在操作下单时出现异常,阻塞下单流程。
去除JaCoCo配置、重新编译和部署后下单功能恢复正常。堆栈信息显示,问题源于系统对请求字段进行加密时出现异常,因为无法完成类型转换抛出异常,“[Z cannot be cast to [Ljava.lang.Object”,从而阻塞下单流程。
以下为报错堆栈信息:
[Z cannot be cast to [Ljava.lang.Object; :
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:93)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:90)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:90)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.$$FastClassBySpringCGLIB$$4fa3c52.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
..省略
02 问题分析
问题描述
JaCoCo是一款被广泛应用于公司内部的开源覆盖率工具,将其引用至测试环境后,机器启动正常,但在操作下单时出现异常,阻塞下单流程。
去除JaCoCo配置、重新编译和部署后下单功能恢复正常。堆栈信息显示,问题源于系统对请求字段进行加密时出现异常,因为无法完成类型转换抛出异常,“[Z cannot be cast to [Ljava.lang.Object”,从而阻塞下单流程。
[Z cannot be cast to [Ljava.lang.Object; :
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:93)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:90)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:90)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.encryptObject(TdeProxy.java:133)
at com.jd.**.TdeProxy.$$FastClassBySpringCGLIB$$4fa3c52.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
..省略
问题分析
1.报错代码
以下为报错处的代码片段,在将obj转换为Object[]时出现异常,既然已经识别出是数组,但是又无法完成类型转换,具体的原因需要进一步分析。
public void encryptObject(Object obj, String type) throws IllegalAccessException {
/***省略***/
if (Map.class.isAssignableFrom(clazz)) {
/***省略***/
} else if(Iterable.class.isAssignableFrom(clazz)) {
/***省略***/
} else if(clazz.isArray()) {
/**********************报错代码行****************/
for (Object o : (Object[]) obj) {
/**********************报错代码行****************/
this.encryptObject(o, type);
}
} else {
Boolean encryptFlag = null;
Field[] fields = this.getDeclaredFieldsAll(clazz);
for (Field field : fields) {
/***省略***/
}
/***省略***/
for (Field field : fields) {
Class fieldClazz = field.getType();
if (fieldClazz == String.class) {
/***省略***/
} else {
field.setAccessible(true);
Object fieldValue = field.get(obj);
this.encryptObject(fieldValue, type);
}
}
}
}
2.分析路径
public Field[] getDeclaredFieldsAll(Class clazz) {
List<Field> fieldsList = new ArrayList<Field>();
while (clazz != null) {
Field[] declaredFields = clazz.getDeclaredFields();
fieldsList.addAll(Arrays.asList(declaredFields));
clazz = clazz.getSuperclass();
}
return fieldsList.toArray(new Field[fieldsList.size()]);
}
由于已确认引入JaCoCo后对类进行了修改,只需触发任一流程以获取类的所有属性,通过设置断点并观察集合中的元素,即可查看具体修改情况。
3.追本溯源
(1)合成属性和方法
合成属性/方法是由Java编译器在编译过程中自动生成,并不是研发显示编写的,而是为了支持编译器内部的实现细节而生成的,下面针对合成方法进行一个举例说明。
public class Pack {
public static void main(String[] args) {
Pack.Goods goods = new Pack.Goods();
System.out.println(goods.name);
}
private static class Goods {
private String name = "手机";
}
}
import com.jd.ryan.test.Pack.1;
class Pack$Goods {
private String name;
private Pack$Goods() {
this.name = "手机";
}
Pack$Goods(1 x0) {
this();
}
static String access$100(Pack$Goods x0) {
return x0.name;
}
}
public class com.jd.ryan.test.Pack {
public com.jd.ryan.test.Pack();
Code:
0: aload_0
1: invokespecial #1 //Method java/lang/Object."<init>":()V
public static void main(java.lang.String[]);
Code:
0: new #2 //class com/jd/ryan/test/Pack$Goods
3: dup
4: aconst_null
5: invokespecial #3 //Method com/jd/ryan/test/Pack$Goods."<init>":(Lcom/jd/ryan/test/Pack$1;V
8: astore_1
9: getstatic #4 //Field java/lang/System.out:Ljava/io/Printstream;
12: aload_1
13: invokestatic #5 //Method com/jd/ryan/test/Pack$Goods.access$100:(Lcom/jd/ryan/test/Pack$Goods.access$100:(Lcom/jd/ryan/test/Pack$Goods;)Ljava/lang/String;
16: invokevirtual #6//Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: return
}
在代码实现中外部类直接打印内部类的name属性值,来看这行指令:
(2)JaCoCo原理简述
本文不再深入介绍JaCoCo的工作原理,感兴趣的同学可以查阅资料。
解决方法
List<Field> fieldsList = Arrays.stream(declaredFields)
.filter(field -> !field.isSynthetic())
.collect(Collectors.toList());
总结
感谢阅读