Dubbo Hessian序列化问题 参数为null

 在项目中发现一个很奇怪的问题,将 BizWrapperBean 参数  对象传输到远程服务上,远程服务获取的BizWrapperBean对象为null。而在传输之前明明是有值的。BizWrapperBean类已经实现了序列化接口,它的所有属性都是可序列化的。

最后查明了原因,是序列化的问题。由于项目中的远程服务用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();

            }

        }

发送二进制数据时,子类数据在前,父类数据在后。

接收二进制数据时,子类数据在前,父类数据在后。

所以对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失。分析完了,再回过头来看看,BizWrapperBean有一个String类型字段orderNumber子类也有一个String类型字段orderNumber。

 

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

建议dubbo对这个做强制检查。

猜你喜欢

转载自blog.csdn.net/a1179785335/article/details/45644775