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已经被过滤掉了。


审核编辑 黄宇

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

    关注

    8

    文章

    5336

    浏览量

    126799
  • 开源
    +关注

    关注

    3

    文章

    3371

    浏览量

    42582
  • 编译
    +关注

    关注

    0

    文章

    659

    浏览量

    32914
收藏 人收藏

    评论

    相关推荐

    【电磁兼容技术案例分享】LVDS信号因经连接器转接导致RS问题整改分析案例

    【电磁兼容技术案例分享】LVDS信号因经连接器转接导致RS问题整改分析案例
    的头像 发表于 12-12 12:01 168次阅读
    【电磁兼容技术案例分享】LVDS信号因经连接器转接<b class='flag-5'>导致</b>RS问题整改<b class='flag-5'>分析</b>案例

    不同类型ACDC转换器优缺点 ACDC转换器负载能力分析

    ACDC转换器是将交流电(AC)转换为直流电(DC)的设备,在电力电子领域具有广泛的应用。以下是不同类型ACDC转换器的优缺点以及ACDC转换
    的头像 发表于 12-09 10:53 499次阅读

    不同类型adc的优缺点分析

    ADC(模数转换器)是将模拟信号转换为数字信号的电路,根据转换原理和应用需求的不同,ADC可以分为多种类型,每种类型都有其独特的优缺点,以下
    的头像 发表于 11-19 16:58 994次阅读

    不同类型AD转换器的比较

    在电子系统中,模数转换器(Analog-to-Digital Converter,简称ADC或A/D转换器)是至关重要的一环,它负责将连续的模拟信号转换为离散的数字信号,以便于数字系统进行处理和
    的头像 发表于 10-05 11:36 2062次阅读

    【电磁兼容技术案例分享】因唤醒线导致的CE电压法测试超标整改分析案例

    【电磁兼容技术案例分享】因唤醒线导致的CE电压法测试超标整改分析案例
    的头像 发表于 09-28 08:03 411次阅读
    【电磁兼容技术案例分享】因唤醒线<b class='flag-5'>导致</b>的CE电压法测试超标整改<b class='flag-5'>分析</b>案例

    静态转换开关类型有哪些

    转换开关
    深圳市宝威特电源有限公司
    发布于 :2024年07月24日 14:38:14

    DCAC电源模块在不同的电源类型之间进行转换

    BOSHIDA DC/AC电源模块在不同的电源类型之间进行转换 电力转换是现代社会不可或缺的一部分,它使我们能够在不同的电源类型之间进行转换
    的头像 发表于 07-05 14:42 510次阅读
    DCAC电源模块在不同的电源<b class='flag-5'>类型</b>之间进行<b class='flag-5'>转换</b>

    CAN不通讯导致CE电流法测试超标整改分析案例

    CAN不通讯导致CE电流法测试超标整改分析案例
    的头像 发表于 07-05 08:17 846次阅读
    CAN不通讯<b class='flag-5'>导致</b>CE电流法测试超标整改<b class='flag-5'>分析</b>案例

    AC-AC转换器的工作原理与主要类型

    AC-AC转换器,即交流-交流转换器,是一种将一种交流电转换为另一种交流电的电子设备。在现代电子系统中,AC-AC转换器扮演着至关重要的角色,它们被广泛应用于电力电子、工业自动化、通信
    的头像 发表于 05-23 17:09 6216次阅读

    逆变器的常见类型及其特点分析

    逆变器,作为电力电子领域的重要设备,其功能是将直流电(DC)转换为交流电(AC)。随着电力电子技术的不断发展,逆变器的类型也日益多样化,以满足不同领域和场景的需求。本文将对逆变器的常见类型进行详细
    的头像 发表于 05-21 16:29 1651次阅读

    电源转换器的类型及其特点

    电源转换器,作为电子设备中不可或缺的一部分,其重要性不言而喻。无论是在日常生活中,还是在工业生产中,电源转换器都发挥着至关重要的作用。本文将详细介绍电源转换器的类型及其特点,旨在帮助读
    的头像 发表于 05-21 15:22 912次阅读

    C语言如何掌握强制类型转换的精髓

    强制类型转换是把变量从一种类型转换为另一种数据类型。例如,如果您想存储一个 long 类型的值到
    的头像 发表于 02-26 11:00 535次阅读
    C语言如何掌握强制<b class='flag-5'>类型</b><b class='flag-5'>转换</b>的精髓

    arcgis值类型与字段类型不兼容

    ArcGIS是一个地理信息系统软件,可以用来处理、分析和可视化地理数据。在ArcGIS中,值类型和字段类型之间需要相互匹配,否则会导致不兼容的错误。 在ArcGIS中,值
    的头像 发表于 02-25 11:14 1764次阅读

    频谱分析类型和技术工作原理

    与任何测试设备一样,可以找到几种类型的频谱分析仪。各种类型分析仪以不同的方式运行,尽管它们的目的相同:分析频谱,查看信号。
    发表于 02-08 09:52 380次阅读
    频谱<b class='flag-5'>分析</b>仪<b class='flag-5'>类型</b>和技术工作原理

    压频转换和频压转换的原理和电路分析

    这是通过什么电路方法检测的?或者在进行直接转换时用RC低通滤波器将这些脉冲进行平均,根据平均后的脉冲频率按照比例进行电压输出,这样做会导致纹波,滤波器的选择会导致纹波的大小,请问下图RC电路 左边这个电路
    发表于 01-30 14:51