记一次$jacocoData引起的bug

bug描述:

项目开发过程中, 遇到了一个奇怪的现象, 在测试环境去利用反射拿一个类的字段时, 发现拿到的field数组中多了一个奇怪的变量: $jacocoData, 它是一个boolean数组.

在开发环境中没有这个问题, 但是部署到测试环境之后有这个问题.

发现这个问题的缘由: 对接了一个第三方系统, 可能他们系统对于数据类型比较严格, 要求传的字段是规定的类型且不能为null. 然后部署到测试环境发现报错了, 

报错内容: com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException: The property 'groupBuy_e_$jacocoData' should be a basic type: Number, String, Date, Boolean, List<String>.

因为我们自己业务系统中要求字段前缀是以"groupBuy_e_"开头, 所以我利用反射写了个方法, 对字段属性名称进行拼接.

public class BeanToMapUtils {
    private static Logger LOGGER = LoggerFactory.getLogger(BeanToMapUtils.class);
    /**
     * 属性名前缀
     */
    private static final String PREFIX = "groupBuy_e_";

    /**
     * 将一个 JavaBean 对象转化为一个  Map
     *
     * @param bean 要转化的JavaBean 对象
     * @return 转化出来的  Map 对象
     * @throws IntrospectionException    如果分析类属性失败
     * @throws IllegalAccessException    如果实例化 JavaBean 失败
     * @throws InvocationTargetException 如果调用属性的 setter 方法失败
     */
    public static Map<String, Object> beanToMap(Object bean) {
        Class type = bean.getClass();
        Map<String, Object> returnMap = new HashMap<>(16);
        Field[] fieldArray = type.getDeclaredFields();
        for (Field field : fieldArray) {
            field.setAccessible(true);
            try {
                Object value = field.get(bean);
                if (ObjectUtils.isEmpty(value)) {
                    // 为空是设置默认值
                    if (field.getType().isInstance(new BigDecimal(0))) {
                        value = BigDecimal.ZERO;
                    }
                    if (field.getType().isInstance(new String())) {
                        value = "";
                    }
                    if (field.getType().isInstance(new Integer(0))) {
                        value = new Integer(0);
                    }
                    if (field.getType().isInstance(new Boolean(false))) {
                        value = new Boolean(false);
                    }
                }
                returnMap.put(PREFIX + field.getName(), value);
            } catch (Exception e) {
                LOGGER.error("BeanToMapUtils beanToMap fail: {}", e);
            }
        }
        return returnMap;
    }
}

莫名其妙多出这个$jacocoData属性, 技术菜鸟表示很懵逼.

于是开始了寻找解决方案之路.

首先我在网上看到一篇关于类似问题的博客: https://zhanglijun1217.github.io/blog/2019/03/10/%E4%B8%80%E6%AC%A1%E6%8E%92%E6%9F%A5-jacocoData%E7%9A%84%E8%BF%87%E7%A8%8B/

原来和JaCoco有关系, 对JaCoco了解不甚多, 只知道它是代码覆盖率工具. 了解了一下它的基本原理, 它使用的ASM技术修改字节码方法, 可以修改Jar文件、class文件字节码文件(详细可以参考博客https://cloud.tencent.com/developer/article/1038055)

JaCoco官网FAQ中有一条是关于$jacocoData的, 如下:

 https://www.eclemma.org/jacoco/trunk/doc/faq.html

官方建议是修改代码, 忽略synthetic成员.

至于什么是synthetic, 你可以参考一下:https://www.cnblogs.com/bethunebtj/p/7761596.html 和 https://www.baeldung.com/java-synthetic

"Any constructs introduced by a Java compiler that do not have a corresponding construct in the source code must be marked as synthetic, except for default constructors, the class initialization method, and the values and valueOf methods of the Enum class."

解决方案自然是忽略synthetic成员了, 可以用下面这个方法判断是否是synthetic field:

所以BeanToMapUtils类加几行代码:

public class BeanToMapUtils {
    private static Logger LOGGER = LoggerFactory.getLogger(BeanToMapUtils.class);
    /**
     * 属性名前缀
     */
    private static final String PREFIX = "groupBuy_e_";

    /**
     * 将一个 JavaBean 对象转化为一个  Map
     *
     * @param bean 要转化的JavaBean 对象
     * @return 转化出来的  Map 对象
     * @throws IntrospectionException    如果分析类属性失败
     * @throws IllegalAccessException    如果实例化 JavaBean 失败
     * @throws InvocationTargetException 如果调用属性的 setter 方法失败
     */
    public static Map<String, Object> beanToMap(Object bean) {
        Class type = bean.getClass();
        Map<String, Object> returnMap = new HashMap<>(16);
        Field[] fieldArray = type.getDeclaredFields();
        for (Field field : fieldArray) {
            // 忽略synthetic成员
            if (field.isSynthetic()) {
                continue;
            }
            field.setAccessible(true);
            try {
                Object value = field.get(bean);
                if (ObjectUtils.isEmpty(value)) {
                    // 为空是设置默认值
                    if (field.getType().isInstance(new BigDecimal(0))) {
                        value = BigDecimal.ZERO;
                    }
                    if (field.getType().isInstance(new String())) {
                        value = "";
                    }
                    if (field.getType().isInstance(new Integer(0))) {
                        value = new Integer(0);
                    }
                    if (field.getType().isInstance(new Boolean(false))) {
                        value = new Boolean(false);
                    }
                }
                returnMap.put(PREFIX + field.getName(), value);
            } catch (Exception e) {
                LOGGER.error("BeanToMapUtils beanToMap fail: {}", e);
            }
        }
        return returnMap;
    }
}

总结与反思:

When your code uses reflection, please  ignore synthetic members. This is a good practice anyways as also the Java compiler creates synthetic members in certain situation.

...... 以上仅供参考, 欢迎批评与指正!

 

参考:

https://zhanglijun1217.github.io/blog/2019/03/10/%E4%B8%80%E6%AC%A1%E6%8E%92%E6%9F%A5-jacocoData%E7%9A%84%E8%BF%87%E7%A8%8B/

https://cloud.tencent.com/developer/article/1038055

https://github.com/jacoco/jacoco/issues/168

https://www.baeldung.com/java-synthetic

https://www.cnblogs.com/bethunebtj/p/7761596.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值