序列化问题,重复定义对象

hessian的序列化问题

最近在项目中发现一个很奇怪的问题,将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时如果没有指定序列化协议,则也要注意这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值