前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo之hessian序列化数据丢失

dubbo之hessian序列化数据丢失

作者头像
yiduwangkai
发布2019-09-17 15:50:43
1.6K0
发布2019-09-17 15:50:43
举报
文章被收录于专栏:大数据进阶

最近有同事来找我,说同一个model中有一个字段值无法传递到调用方,其它的字段都可以传递过去,什么,还有这样的事,瞬间懵逼了,于是就想着是不是他给到客户端的API和他自己的不一致,是不是没有get和set方法,或者是不是打包的问题,或者是不是TCP传递时,数据发生了丢失等。在找不到原因时,先添加一个字段进行尝试,发现新加的字段是有值的。该字段相较于其它字段较特殊的地方是子类和父类有相同的字段,去掉继承,发现字段能够顺利传递过去了。问题是找到了,但是原因还有找到,于是只能进行baidu

下面是转载http://www.tuicool.com/articles/fERZbie

在使用hessian将一个对象序列化、反序列化之后,发现原本有值的一个属性变成了NULL,观察发现在子类、父类有同名属性时会出现。

问题重现

构造测试类:

代码语言:javascript
复制
// 父类
class A implements Serializable {
    public Integer a;
}
// 子类
class B extends A {
    public Integer a;
}

序列化方法如下:

代码语言:javascript
复制
public static byte[] serialize(Object obj) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    HessianOutput ho = new HessianOutput(os);
    ho.writeObject(obj);
    return os.toByteArray();
}

反序列化方法如下:

代码语言:javascript
复制
public static Object deserialize(byte[] by) throws IOException {
    ByteArrayInputStream is = new ByteArrayInputStream(by);
    HessianInput hi = new HessianInput(is);
    return hi.readObject();
}

测试代码:

代码语言:javascript
复制
@Test
public void testHessian() throws Exception {
    B obj = new B();
    obj.a = 0;
    byte[] bytes = serialize(obj); // 序列化
    obj = (B) deserialize(bytes); // 反序列化
    System.out.println(obj.a); // null
}

原因分析

首先看序列化的过程,在构造 UnsafeSerializer 时会遍历类及其父类的所有属性:

代码语言:javascript
复制
protected void introspect(Class<?> cl) {
    ArrayList<Field> primitiveFields = new ArrayList<Field>();
    ArrayList<Field> compoundFields = new ArrayList<Field>();
    // 遍历所有父类
    for (; cl != null; cl = cl.getSuperclass()) {
        // 通过反射获取所有的属性
        Field[] fields = cl.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            // 忽略transient和static的变量
            if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()))
                continue;
            field.setAccessible(true);
            // 基本类型和复杂类型分开(这段代码是不是写残了)
            if (field.getType().isPrimitive() || (field.getType().getName().startsWith("java.lang.") && !field.getType().equals(Object.class)))
                primitiveFields.add(field);
            else
                compoundFields.add(field);
        }
    }
    ArrayList<Field> fields = new ArrayList<Field>();
    fields.addAll(primitiveFields);
    fields.addAll(compoundFields);
    _fields = new Field[fields.size()];
    fields.toArray(_fields);
    _fieldSerializers = new FieldSerializer[_fields.length];
    // 构造序列化实现类
    for (int i = 0; i < _fields.length; i++) {
        _fieldSerializers[i] = getFieldSerializer(_fields[i]);
    }
}

然后,遍历序列化各个属性字段来分别进行序列化:

代码语言:javascript
复制
protected void writeObject10(Object obj, AbstractHessianOutput out) throws IOException {
    // 遍历属性
    for (int i = 0; i < _fields.length; i++) {
        Field field = _fields[i];
        out.writeString(field.getName());
        _fieldSerializers[i].serialize(out, obj); // 序列化
    }
    out.writeMapEnd();
}

处理完的结果如下:

接下来看反序列化,拿到值之后设置属性:

代码语言:javascript
复制
public Object readMap(AbstractHessianInput in, Object obj) throws IOException {
    // ....
    // 循环读取序列化的内容。
    while (!in.isEnd()) {
        Object key = in.readObject();
        // 相同名字的两个属性,拿到的是同一个desrializer。
        FieldDeserializer deser = (FieldDeserializer) _fieldMap.get(key);
        if (deser != null)
            deser.deserialize(in, obj); // 在这里拿到value后设置到对应的属性中。
        else
            in.readObject();
    }
    // .....
}

序列化之后字节中对属性a有两个值,第一个非空,第二个空,那么在反序列化时会对a做两次赋值,第一次的结果为:

第二次的结果为:

到这里,就已经知道了为啥属性值会丢了。

解决方法

比较简单的办法:

  • 避免在子类中出现与父类同名的属性

然并卵,现实中往往还是会出现,再想想办法:

  • 找一个没有BUG得HESSION版本或者自己动手改改代码重新打个包

相对简单的改法是:在出现同名时,如果子类中已经有了,那么父类中对应属性直接忽略,代码量很少,只需要加一个 continue 即可,但是这样改容易挖坑。

最笨的一个解决办法是:

  • 直接用Java原生的序列化方法

代码如下:

代码语言:javascript
复制
B obj = new B();
obj.setA(0);
// 序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
// 反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
obj = (B) objectInputStream.readObject();
System.out.println(obj.a); // 0
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题重现
  • 原因分析
  • 解决方法
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档