hessian序列化的坑

2021-01-07  本文已影响0人  PataPataPa_2718

子类和父类不能有同名字段

可以参看这篇文章:
https://blog.51cto.com/tianya23/582256
学习记录,自我总结一下

hessian序列化对象的时候,默认使用的com.caucho.hessian.io.JavaSerializer,这里只说普通对象,
JavaSerializerwriteObject方法会调用writeInstance方法来序列化字段,代码如下:

  @Override
  public void writeInstance(Object obj, AbstractHessianOutput out)
    throws IOException
  {
    try {
      for (int i = 0; i < _fields.length; i++) {
        Field field = _fields[i];

        _fieldSerializers[i].serialize(out, obj, field);
      }
    } catch (RuntimeException e) {
      throw new RuntimeException(e.getMessage() + "\n class: "
                                 + obj.getClass().getName()
                                 + " (object=" + obj + ")",
                                 e);
    } catch (IOException e) {
      throw new IOExceptionWrapper(e.getMessage() + "\n class: "
                                   + obj.getClass().getName()
                                   + " (object=" + obj + ")",
                                   e);
    }
  }

有循环可以看出,这里是按照字段顺序依次序列化,而_fields又是从哪儿来的呢?
我们找到JavaSerializer的构造方法:

  public JavaSerializer(Class<?> cl)
  {
    introspect(cl);

    _writeReplace = getWriteReplace(cl);

    if (_writeReplace != null)
      _writeReplace.setAccessible(true);
  }

继续跟踪,可以看到introspect方法,它的代码片段如下:

protected void introspect(Class<?> cl)
  {
    if (_writeReplace != null)
      _writeReplace.setAccessible(true);

    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];

        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中
    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];

    //依次获取fieldSerializer
    for (int i = 0; i < _fields.length; i++) {
      _fieldSerializers[i] = getFieldSerializer(_fields[i].getType());
    }
  }

introspect方法也是先获得当前class的字段,然后再获得父类的字段。所有字段放到arraylist中,因此,序列化的时候是子类数据在前,父类数据在后。



这时候,我们看看反序列化类com.caucho.hessian.io.JavaDeserializer,它的构造函数如下:
  public JavaDeserializer(Class<?> cl, FieldDeserializer2Factory fieldFactory)
  {
    _type = cl;
      //获取字段map
    _fieldMap = getFieldMap(cl, fieldFactory);

    _readResolve = getReadResolve(cl);

    if (_readResolve != null) {
      _readResolve.setAccessible(true);
    }
    
    _constructor = getConstructor(cl);
    _constructorArgs = getConstructorArgs(_constructor);
  }

里面getFieldMap方法如下:

protected HashMap<String,FieldDeserializer2> 
  getFieldMap(Class<?> cl, FieldDeserializer2Factory fieldFactory)
  {
    HashMap<String,FieldDeserializer2> fieldMap
      = new HashMap<String,FieldDeserializer2>();
      //for循环先获得子类字段,然后再获得父类字段
    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();
        }
    */

        FieldDeserializer2 deser = fieldFactory.create(field);

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

    return fieldMap;
  }

这里也是先获得当前类的字段,然后再获得父类的字段,但是如果子类和父类字段同名,fieldMap只会有一个字段名称key。

在反序列化字段接收值的时候,会调用readMap:

public Object readMap(AbstractHessianInput in, Object obj)
    throws IOException
  {
    try {
      int ref = in.addRef(obj);

      while (! in.isEnd()) {
        Object key = in.readObject();
        
        FieldDeserializer2 deser = _fieldMap.get(key);

        if (deser != null)
          deser.deserialize(in, obj);
        else
          in.readObject();
      }
      
      in.readMapEnd();

      Object resolve = resolve(in, obj);

      if (obj != resolve)
        in.setRef(ref, resolve);

      return resolve;
    } catch (IOException e) {
      throw e;
    } catch (Exception e) {
      throw new IOExceptionWrapper(e);
    }
  }

\color{red}{重点!!!}

\color{red}{序列化的时候,子类数据在前,父类数据在后}
\color{red}{反序列化的时候,先读到的是子类数据,而后读的是父类数据}
\color{red}{因此,对于同名字段,子类字段会被赋值两次,第二次会被父类字段覆盖,导致子类字段值丢失。}

\color{red}{总结:}
\color{red}{这个坑的原因是,序列化的时候子类数据在前,父类数据在后。} \color{red}{反序列化的时候,也是读取到子类数据,然后是父类数据,父类字段将子类字} \color{red}{段值覆盖了,从而导致子类字段丢失。}

上一篇下一篇

猜你喜欢

热点阅读