0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

引入JaCoCo导致的类型转换问题分析

京东云 来源:jf_75140285 作者:jf_75140285 2024-08-06 10:30 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、问题描述

JaCoCo是一款被广泛应用于公司内部的开源覆盖率工具,将其引用至测试环境后,机器启动正常,但在操作下单时出现异常,阻塞下单流程。

去除JaCoCo配置、重新编译和部署后下单功能恢复正常。堆栈信息显示,问题源于系统对请求字段进行加密时出现异常,因为无法完成类型转换抛出异常,“[Z cannot be cast to [Ljava.lang.Object”,从而阻塞下单流程。

以下为报错堆栈信息:

java.lang.ClassCastException: [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.报错代码

定位报错信息显示的代码位置,确认该部分代码并没有被修改过。报错提示指出属性应为数组类型,但在需要加密的类属性中并没有涉及数组类型的处理。那么“[Z”这个类型又是从何而来呢?这种情况下不禁让人怀疑,在某个时刻类可能被修改过。

报错信息中的"[Z"代表的是Java中的boolean类型数组。在Java中,基本数据类型的数组也会被表示为类似于"[Z"、"[B"、"[L"等形式的字符串,这可能是因为在程序运行过程中对类进行了动态修改或者反射操作导致的。

以下为报错处的代码片段,在将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.分析路径

阅读代码可以看出encryptObject方法是通过递归实现的,其主要功能是对有效集合进行遍历,所以问题的重点不是递归的过程,而是推进递归过程的元素集合,集合中的元素无法正常进行类型转换导致报错,这就需要检查getDeclaredFieldsAll方法,该方法在运行时返回的集合中可能包含意料之外的元素,以下为具体实现代码:

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后对类进行了修改,只需触发任一流程以获取类的所有属性,通过设置断点并观察集合中的元素,即可查看具体修改情况。





此时已经可以解释为什么引入JaCoCo会导致异常。报错中的类型“[Z”为合成的属性,引入JaCoCo会给类添加一个名为$jacocoData的bool数组类型属性,回到报错代码位置,出现报错是因为在识别到一个数组类型时进行了类型转换,在这里也找到了问题的答案。

涉及到合成属性/方法和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 = "手机";
    }
}

将上面的代码编译一下,可以看到有三个文件,Pack$Goods.class、Pack.class、Pack$1.class,前两个一个是内部类,一个是外部类,但是最后一个类并没有被定义过,接下来分别将内部类和外部类进行反编译:

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; 
    }
}

内部类被反编译后,可以发现access$100的方法并没有被定义,但是分析来看name是内部类Goods的私有属性,但是外部类可以直接引用这个属性,从语法结构上讲这是被允许的,这就需要编译器在编译过程处理这种操作,在编译器看来,外部类和内部类是两个独立的类,如果外部类想要访问内部类的私有属性,其实是与封装原则相悖的。那接着看外部类的反编译结果:

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属性值,来看这行指令:

“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;”

从字节码中表明是通过调用了内部类的access$100方法,这个方法是一个静态方法,它可以返回内部类的name属性,是Goods的私有属性,所以access$100就是编译器用来做内部访问生成的一个合成方法。

编译器可以通过生成合成属性和方法来实现一些内部优化或者内部实现,所以在使用反射机制实现一些工具时,在运行时拿到的类属性信息还可能会有一些未知的属性或者方法,这就需要工具类的代码具备一定的健壮性,对获取到的类属性进行类型转换时应该考虑到非业务字段的情况,并且能够对运行时异常进行捕获,让工具聚焦在可以处理的范围,不能影响正常的业务流程。

(2)JaCoCo原理简述

JaCoCo利用ASM在字节码中插入探针指针(Probe指针),每个探针都是一个布尔变量(true表示执行,false表示未执行)。程序运行时通过修改这些指针来检测代码的执行情况,而不会改变原始代码的行为。提到的$jacocoData数组用于保存这些执行结果,JaCoCo根据控制流类型采用不同的探针插入策略,这些探针不会改变方法的行为,只是记录它们已经执行的事实。

本文不再深入介绍JaCoCo的工作原理,感兴趣的同学可以查阅资料。

三、解决办法

通过问题分析已经确定是$jacocoData导致的,那就需要在获取属性集合的的时对这类属性进行过滤,实现方法通过isSynthetic()方法区分field属性类型,isSynthetic是Java中的一个修饰符,用于标记一个类、方法或字段是否由编译器生成。

List< Field > fieldsList = Arrays.stream(declaredFields)
                               .filter(field -> !field.isSynthetic())
                               .collect(Collectors.toList());

代码修改后,测试环境添加JaCoCo相关配置,编译部署发布后可正常下单,从断点信息来看,$jacocoData已经被过滤掉了。


审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 测试
    +关注

    关注

    9

    文章

    6415

    浏览量

    131684
  • 开源
    +关注

    关注

    3

    文章

    4350

    浏览量

    46446
  • 编译
    +关注

    关注

    0

    文章

    696

    浏览量

    35284
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    VirtualLab:光栅的优化与分析

    效率的算法: TEA和FMM(也称为RCWA)。比较了不同周期的两种类型的光栅(正弦和闪耀)结果。倾斜光栅的参数优化及公差分析 以傅里叶模态法(FMM)作为参数优化的核心,设计了一个倾斜光栅来实现高
    发表于 04-23 08:17

    嵌入式中的浮点型数据转换为字节类型的三种方法

    MCU的存储模块中去。所以说要保存一个浮点型数据到MCU前需要先把浮点型数据转换成字节的形式。下面就来说一说浮点型转换为字节类型的一些方法: 方法一 强制转换,就是把浮点型数据直接赋
    发表于 01-07 06:28

    枚举类型的讲解

    green, 3. blue): 3 你喜欢的颜色是蓝色 也可以把整数转换为枚举类型: //Example 07 #include #include int main() { enum
    发表于 12-08 08:14

    C语言自动类型转换

    一、自动类型转换 数据类型存在自动转换的情况. 自动转换发生在不同数据类型运算时,在编译的时候
    发表于 11-25 08:04

    C语言强制类型转换

    强制类型转换是通过定义类型转换运算来实现的。其一般形式为: (数据类型) (表达式) 其作用是把表达式的运算结果强制
    发表于 11-24 06:32

    模数转换器主要类型有哪些

    模数转换器采用逐步比较逼近策略,通过二进制搜索算法将模拟信号转换为数字值。这类转换器在转换速度和精度之间实现了良好平衡,广泛应用于中等采样速率和中等精度的场景,如工业控制系统和医疗仪器
    的头像 发表于 11-03 16:36 817次阅读

    vivado时序分析相关经验

    vivado综合后时序为例主要是有两种原因导致: 1,太多的逻辑级 2,太高的扇出 分析时序违例的具体位置以及原因可以使用一些tcl命令方便快速得到路径信息
    发表于 10-30 06:58

    PPKTP晶体在波长转换中的特性分析与应用选择

    波长转换技术在激光系统、量子光学和光谱分析等领域具有重要作用,其核心是通过非线性光学效应实现高效、灵活的波段调谐。在众多非线性晶体中,周期极化磷酸氧钛钾(PPKTP)以其高损伤阈值和可见波段的低光
    的头像 发表于 09-29 17:36 1076次阅读
    PPKTP晶体在波长<b class='flag-5'>转换</b>中的特性<b class='flag-5'>分析</b>与应用选择

    工业相机的类型及不同类型的应用

    工业相机根据不同的分类标准,可分为多种类型,每种类型都有其独特的应用场景。
    的头像 发表于 08-29 17:07 944次阅读
    工业相机的<b class='flag-5'>类型</b>及不同<b class='flag-5'>类型</b>的应用

    【电磁兼容技术案例分享】TVS选型导致浪涌问题整改分析案例

    【电磁兼容技术案例分享】TVS选型导致浪涌问题整改分析案例
    的头像 发表于 06-11 17:29 870次阅读
    【电磁兼容技术案例分享】TVS选型<b class='flag-5'>导致</b>浪涌问题整改<b class='flag-5'>分析</b>案例

    光纤头可以直接转换网线头吗

    光纤头不能直接转换网线头,需要通过光电转换设备(如光纤收发器或光电交换机)将光信号转换为网络信号,才能与网线连接。以下是具体分析: 一、光纤与网线的传输特性差异 信号
    的头像 发表于 06-03 10:27 3489次阅读

    VirtualLab:光栅的优化与分析

    的算法: TEA和FMM(也称为RCWA)。比较了不同周期的两种类型的光栅(正弦和闪耀)结果。 倾斜光栅的参数优化及公差分析 以傅里叶模态法(FMM)作为参数优化的核心,设计了一个倾斜光栅来实现高衍射效率将光耦合到光波导中的目的。此外,还
    发表于 05-23 08:49

    【电磁兼容技术案例分享】因视频光电转换导致的BCI问题案例

    【电磁兼容技术案例分享】因视频光电转换导致的BCI问题案例
    的头像 发表于 05-14 18:11 499次阅读
    【电磁兼容技术案例分享】因视频光电<b class='flag-5'>转换</b>器<b class='flag-5'>导致</b>的BCI问题案例

    如何操作时域网络分析仪进行故障检测?

    操作时域网络分析仪(TDNA)进行故障检测需结合仪器设置、校准、时域转换及数据分析等步骤。以下为系统化操作指南,涵盖关键流程、参数配置及典型案例:一、操作前准备1. 仪器连接与配置 硬件连接
    发表于 04-30 14:15

    LTspice里压敏电阻MOV怎么引入?

    LTspice里压敏电阻MOV怎么引入
    发表于 04-28 08:26