`
jameswxx
  • 浏览: 776388 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

hessian序列化bug

 
阅读更多

       最近在项目中发现一个很奇怪的问题,将ProductDraftDO对象传输到远程服务上,远程服务获取的ProductDraftDO对象的ActionTrace为null。而在传输之前明明是有值的。ActionTrace类已经实现了序列化接口,它的所有属性都是可序列化的。
最后查明了原因,是序列化的问题。由于项目中的远程服务用dubbo实现,Hessian是dubbo的默认序列化协议,它比java的序列化性能要高很多。
当hessian序列化一个对象时,默认的序列化类是com.caucho.hessian.io. JavaSerializer。
JavaSerializer的writeObject方法代码片段如下:

    try {
      out.writeMapBegin(cl.getName());
      for (int i = 0; i < _fields.length; i++) {
        Field field = _fields[i];
        out.writeString(field.getName());
        out.writeObject(field.get(obj));
      }
      out.writeMapEnd();
    } catch (IllegalAccessException e) {
      throw new IOException(String.valueOf(e));
}
 


将属性和属性值都写入到流中,这里看起来似乎没有什么问题,可是这些Field是怎么来的呢?看看JavaSerializer的构造函数:
  

public JavaSerializer(Class cl) {
        _writeReplace = getWriteReplace(cl);
        if (_writeReplace != null) _writeReplace.setAccessible(true);

        ArrayList primitiveFields = new ArrayList();
        ArrayList compoundFields = new ArrayList();
        for (; cl != null; cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) continue;

                // XXX: could parameterize the handler to only deal with public
                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 fields = new ArrayList();
        fields.addAll(primitiveFields);
        fields.addAll(compoundFields);

        _fields = new Field[fields.size()];
        fields.toArray(_fields);
    }
 


获取当前class的所有字段,接着获取父类的所有字段。序列化的时候,所有字段都放在一个ArrayList里,然后依次写入到二进制流中,反序列化的时候,所有字段放在了一个HashMap里,HashMap的key不能重复,悲剧就出现了,如果子类和父类有同名的字段就会有问题,父类的值会把子类的值覆盖掉。

看看JavaDeserializer的getFieldMap方法,父类字段会把子类字段覆盖掉。

/**
   * Creates a map of the classes fields.
   */
  protected HashMap getFieldMap(Class cl)
  {
    HashMap fieldMap = new HashMap();
   
    for (; cl != null; cl = cl.getSuperclass()) {
      Field []fields = cl.getDeclaredFields();
      for (int i = 0; i < fields.length; i++) {
        Field field = fields[i];

        if (Modifier.isTransient(field.getModifiers()) ||
            Modifier.isStatic(field.getModifiers()))
          continue;
        else if (fieldMap.get(field.getName()) != null)
          continue;

        // XXX: could parameterize the handler to only deal with public
        try {
          field.setAccessible(true);
        } catch (Throwable e) {
          e.printStackTrace();
        }

        fieldMap.put(field.getName(), field);
      }
    }

    return fieldMap;
  }
 



然后根据字段接收值,看看JavaDeserializer的readMap方法的代码片段
   

  while (!in.isEnd()) {
            Object key = in.readObject();
            Field field = (Field) _fieldMap.get(key);
            if (field != null) {
                Object value = in.readObject(field.getType());
                try {
                    field.set(obj, value);
                } catch (Throwable e) {
                    IOException e1 = new IOException("Failed setting: " + field + " with " + value + "\n"
                                                     + e.toString());
                    e1.initCause(e);
                    throw e1;
                }
            } else {
                Object value = in.readObject();
            }
        }
 

发送二进制数据时,子类数据在前,父类数据在后。
接收二进制数据时,子类数据在前,父类数据在后。
所以对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失。分析完了,再回过头来看看,ProductDraftDO有一个ActionTrace类型字段actionTrace,父类ProductDO也有一个ActionTrace类型字段actionTrace。由于反序列化时,ProductDraftDO的actionTrace一直被ProductDO的actionTrace覆盖,所以ProductDraftDO的actionTrace总是空的。至于为什么父类和子类都有一个ActionTrace,这是历史原因,这里不讨论。

所以,使用hessian序列化时,一定要注意子类和父类不能有同名字段。在使用dubbo时如果没有指定序列化协议,则也要注意这个问题。

分享到:
评论
9 楼 zhaoxiaoxiao 2017-04-24  
非常赞,帮助理解了问题。今天也是遇到了这样的问题
8 楼 wnli350 2013-10-10  
除了避免同名,有什么解决办法么?
7 楼 orte-001 2012-09-04  
FutureInHands 写道
"所以对于同名字段,子类的该字段值会被赋值两次"
子类同名字段应该不会被赋值才对,因为map里根本没有子类的Field

应该是没有父类的
6 楼 FutureInHands 2012-07-12  
"所以对于同名字段,子类的该字段值会被赋值两次"
子类同名字段应该不会被赋值才对,因为map里根本没有子类的Field
5 楼 sohua2000 2012-02-24  
我又重蹈覆辙了。
今天调试几个小时确定了问题,网上一查看到你的文章。
怎么不早点看到你文章啊。
4 楼 sodarfish 2011-12-04  
赞!分析的很好
3 楼 jameswxx 2011-06-13  
liuzhiqiangruc 写道
是发过邮件不?


什么?
2 楼 liuzhiqiangruc 2011-06-11  
是发过邮件不?
1 楼 kingkan 2011-06-08  
确实有这个问题,Mark

相关推荐

    hessian4.0.7结合spring2.5.6的bug

    2. **序列化/反序列化问题**:Hessian基于二进制序列化,如果Spring中的bean或者数据模型在序列化过程中出现问题,可能导致服务调用失败或者数据解析错误。例如,自定义类型的序列化没有正确实现,或者某些字段在反...

    Hessian多个版本打包下载

    Hessian是一种高效的二进制序列化协议,常用于远程过程调用(RPC)和服务之间的通信。这个压缩包包含了Hessian的多个版本,分别是Hessian3.1.6、Hessian3.2.1以及Hessian4.0.7。每个版本都有其特定的功能改进和优化...

    Hessian的jar包.zip

    3. 类型编码:Hessian定义了一套编码规则,可以有效地序列化和反序列化各种Java类型,包括基本类型、数组、集合、Map以及自定义对象。 4. 安全性:虽然Hessian本身并不直接提供安全性,但可以通过在HTTP层添加SSL/...

    hession例子,源码,最新的jar包,还有web例子与spring结合的例子

    源码可能包含了Hessian序列化和反序列化的实现细节,以及处理异常和错误的方式。通过阅读源码,你可以了解到Hessian如何处理不同类型的数据,以及它是如何确保数据安全和完整性的。 4. **最新的jar包**: 提供的最新...

    dubbo-2.5.3版本及构建工具依赖

    - **功能**:Hessian-lite使得远程调用更加高效,特别是在处理Java对象序列化和反序列化时,它能将Java对象直接转换成二进制流进行网络传输。 - **在Dubbo中的作用**:作为Dubbo的序列化协议之一,Hessian-lite...

    基于Dubbo的跨语言服务治理框架概述.pdf

    3. **序列化方式**:Dubbo支持多种序列化方式,如FastJson、Hessian2和JavaSerialization。每种方式有独特的标识符,例如,DubboSerialization的标识为0001。 4. **粘包和半包处理**:Dubbo通过请求ID和消息体长度...

    dubbo-用户指南.pdf

    5. **协议与序列化**:Dubbo支持多种通信协议,如RMI、Hessian、HTTP等,其中最常用的是基于TCP的Dubbo协议,它具有低开销、高吞吐的特点。序列化是RPC调用中的重要环节,Dubbo提供了多种序列化方式,如Java自带的...

    dubbo-admin-2.5.8.war

    《Dubbo Admin 2.5.8:JDK8 兼容版本的...在实际工作中,我们可以结合Dubbo的其他组件,如Zookeeper作为注册中心,Hessian或Protobuf作为序列化方式,以及Spring进行服务的声明式配置,构建出高效、稳定的分布式系统。

    SOFARPC框架 v5.11.1.zip

    - **序列化(Serialization)**:负责服务调用过程中的数据转换,如JSON、Hessian、Protobuf等。 2. **SOFARPC核心特性** - **动态配置**:通过注册中心实现服务的动态注册与发现,无需重启即可感知服务变化。 -...

    dubbo开发指南

    在技术上,Dubbo还支持一系列额外的扩展功能,如调用拦截扩展、引用监听扩展、暴露监听扩展等,以及多种协议扩展、序列化扩展和网络传输扩展。这些功能让Dubbo能够更好地与现有系统集成,提供更强大的服务能力。 ...

Global site tag (gtag.js) - Google Analytics