记一次FastJson报错

报错内容

起因是一段很普通的字符串转Java对象的代码,在本地和内网测试都没有问题,偏偏外网一跑就报错,错误如下:
在这里插入图片描述
报错的代码特别简单,涉及到公司代码这里用测试代码演示,就是将Json字符串转成java对象,示例代码:

List<PojoTest> list = JSONObject.parseObject(json, new TypeReference<>() {});
PojoTest pojo = JSONObject.parseObject(json, PojoTest.class);

PojoTest就是一个特别简单的类:

public class PojoTest {
    private long id;
    private int sn;
    private int num;

    public PojoTest(long id)
    {
        this.id = id;
    }

    public  boolean isGood()
    {
        return id > 100;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public int getSn() {
        return sn;
    }

    public void setSn(int sn) {
        this.sn = sn;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

一个没有默认构造函数的简单对象.

原因探寻

翻看错误日志,可以找到最终报错的代码是ASM试图读取一个class源文件,执行ClassReader构造函数,报了数组越界,ClassReader构造函数源码如下:

    public ClassReader(InputStream is) throws IOException {
        {
        //is是class文件的二进制流
        //这个大括号内的代码是把二进制流读取到this.b的byte数组内
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            for (; ; ) {
                int len = is.read(buf);
                if (len == -1) {
                    break;
                }
                if (len > 0) {
                    out.write(buf, 0, len);
                }
            }
            is.close();
            this.b = out.toByteArray();
        }
        //items数组存放的是Class的常量池
        items = new int[readUnsignedShort(8)];
        int n = items.length;
        strings = new String[n];
        // parses the constant pool
        int max = 0;
        int index = 10;
        try {
        //这个for循环就是根据class文件的二进制数组读取常量池并且存放到items数组中
            for (int i = 1; i < n; ++i) {
                items[i] = index + 1;
                int size;
                switch (b[index]) {//报错的就是这一行,index过大导致数组越界
                    case 9: // FIELD:
                    case 10: // METH:
                    case 11: //IMETH:
                    case 3: //INT:
                    case 4: //FLOAT:
                    case 18: //INVOKEDYN:
                    case 12: //NAME_TYPE:
                        size = 5;
                        break;
                    case 5: //LONG:
                    case 6: //DOUBLE:
                        size = 9;
                        ++i;
                        break;
                    case 15: //MHANDLE:
                        size = 4;
                        break;
                    case 1: //UTF8:
                        size = 3 + readUnsignedShort(index + 1);
                        if (size > max) {
                            max = size;
                        }
                        break;
                    // case HamConstants.CLASS:
                    // case HamConstants.STR:
                    default:
                        size = 3;
                        break;
                }
                index += size;
            }
        } catch (Exception e) {
            System.out.println("加载class报错,className:");
            throw e;
        }
        maxStringLength = max;
        // the class header information starts just after the constant pool
        header = index;
    } 

关于字节码相关知识可以查看之前的文章字节码详解.
通过源码可以看出ClassReader初始化报错的代码并没有做其他操作,只是要把class文件对应的常量池读取出来,而读取常量池这个操作也没有任何问题。因为字节码技术保证生成的class文件需要跨平台使用,达到一次编译,到处运行的效果,所以class文件的读取解析方式不会因为平台不同而出现字段不同含义的情况。这个不同平台包括windows与Linux操作系统的不同,也包括大小端的不同,class不管什么样的平台编译,都只会以大端形式存储。
也就是说,只要class正常编译后,都是可以按照大端顺序通过字节顺序读取出来,那上面的报错就只能是class文件格式被修改导致的。

原因及解决方案

开发者本地环境和内网环境之所以没有报错,是因为使用的都是原始的class文件,而为了保证代码安全性,公司运维会在拉取项目jar包时对jar包进行加密,运行时加上-agent解密保证项目本身可以稳定运行。但是对于第三方直接拉取class二进制并按照原始顺序去解析的行为就不支持了,因为加密行为是公司层面为了杜绝代码外泄而进行的,所以不会因为这个报错而选择不加密。
目前最简单的解决方案是:通过修改代码,json转换时确保FastJson不会走asm相关读取class文件的逻辑,比如先将String转成JsonObject对象,再读取对象相关属性赋值到自己的类中,或者保证要转换的java对象有默认构造函数,如例子中的PojoTest类,加上默认构造函数后便不会再报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值