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

    文章

    5027

    浏览量

    126210
  • 开源
    +关注

    关注

    3

    文章

    3177

    浏览量

    42227
  • 编译
    +关注

    关注

    0

    文章

    646

    浏览量

    32736
收藏 人收藏

    评论

    相关推荐

    不同类型AD转换器的比较

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

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

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

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

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

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

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

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

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

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

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

    如何判断自己需要哪种类型的CAN转换器呢?

    确定接口类型:首先,确定你需要与CAN总线进行转换的接口类型
    的头像 发表于 01-11 09:13 494次阅读

    电平转换芯片属于什么芯片类型

    个电压级别的信号转换为另一个电压级别。 电平转换芯片在电子设备中发挥着重要的作用。它们可以用于各种场景,例如串行通信、I2C总线、SPI总线、GPIO接口等。电平转换芯片还可以用于不同类型
    的头像 发表于 01-05 16:38 1530次阅读

    几种不同类型的A/D转换器的转换原理

    A/D转换器是将模拟信号变换成相应的数字信号的装置。今天来介绍几种不同类型的A/D转换器的转换原理。
    的头像 发表于 12-31 16:33 2714次阅读
    几种不同<b class='flag-5'>类型</b>的A/D<b class='flag-5'>转换</b>器的<b class='flag-5'>转换</b>原理

    bigdecimal转string类型

    将BigDecimal转换为String类型是在Java编程中常常遇到的一个问题。BigDecimal是Java中用于表示高精度十进制数的类,而String则是用于表示文本字符串的数据类型。在某些
    的头像 发表于 11-30 11:09 5964次阅读

    CLOB类型的数据转换为VARCHAR类型

    VARCHAR字段则适用于存储小于或等于某个长度的字符数据。当我们需要将CLOB类型的数据转换为VARCHAR类型时,可以使用以下方法: 使用数据库函数:不同的数据库系统提供了不同的函数来实现CLOB
    的头像 发表于 11-21 10:39 4873次阅读

    string类型转换成日期

    在数据处理和分析的过程中,我们常常会遇到将字符串类型转换为日期类型的需求。字符串类型的日期数据在数据库、日志文件、文本等源文件中广泛存在,而
    的头像 发表于 11-17 16:34 1233次阅读

    噪声分析、误差分析中,什么类型的噪声、误差可以用均方根计算?

    噪声分析、误差分析中,什么类型的噪声、误差可以用均方根计算?什么类型的噪声需要直接加在总噪声中? 在噪声分析和误差
    的头像 发表于 11-09 09:50 1233次阅读

    怎么把int类型的数据转换成字符串?

    怎么把 int 类型的数据转换成字符串: 看项目有用 micrilib,itoa() 函数 和 sprintf() 能不能用呢?怎么用 比如把 int a=10;转换成字符串 charbuf[]=\"10\";
    发表于 11-01 08:27

    PCB布线对模拟信号传输的影响如何分析

    PCB布线对模拟信号传输的影响如何分析,如何区分信号传输过程中引入的噪声是布线导致还是运放器件导致? PCB布线对模拟信号传输的影响是一项非常复杂的任务,需要考虑诸如电容、电感、阻抗、
    的头像 发表于 10-31 14:34 829次阅读