• Hessian序列化的一个潜在问题


    一. 最近的用rpc框架的时候,当用hessian序列化对象是一个对象继承另外一个对象的时候,当一个属性在子类和有一个相同属性的时候,反序列化后子类属性总是为null。

    二. 示例代码:

    DTO对象

    public class User implements Serializable {
        private String username ;
        private String password;
        private Integer age;
    }
    public class UserInfo extends User {
        private String username;
    }

    序列化代码

          UserInfo user = new UserInfo();
            user.setUsername("hello world");
            user.setPassword("buzhidao");
            user.setAge(21);
    ByteArrayOutputStream os
    = new ByteArrayOutputStream(); //Hessian的序列化输出 HessianOutput ho = new HessianOutput(os); ho.writeObject(user); byte[] userByte = os.toByteArray(); ByteArrayInputStream is = new ByteArrayInputStream(userByte); //Hessian的反序列化读取对象 HessianInput hi = new HessianInput(is); UserInfo u = (UserInfo) hi.readObject(); System.out.println("姓名:" + u.getUsername()); System.out.println("年龄:" + u.getAge());

    输出结果:

        姓名:null 

        年龄:21

     三.  一看这个结果一开始的反应就是不应该啊,后来自己带着好奇查看了网上资料终于找到了原因。

           1. hessian序列化的时候会取出对象的所有自定义属性,相同类型的属性是子类在前父类在后的顺序。

           2. hessian在反序列化的时候,是将对象所有属性取出来,存放在一个map中   key = 属性名  value是反序列类,相同名字的会以子类为准进行反序列化。

           3. 相同名字的属性 在反序列化的是时候,由于子类在父类前面,子类的属性总是会被父类的覆盖,由于java多态属性,在上述例子中父类 User.username = null

     四、 下面是关键源码分析 ,hessian版本是4.0.7

         1.序列化

         当序列化对象是一个java自定对象时,默认的序列化类是 UnsafeSerializer

         调用writeObject

     public void writeObject(Object obj, AbstractHessianOutput out)
        throws IOException
      {
        if (out.addRef(obj)) {
          return;
        }
        
        Class<?> cl = obj.getClass();
    
        int ref = out.writeObjectBegin(cl.getName());
    
        if (ref >= 0) {
          writeInstance(obj, out);
        }
        else if (ref == -1) {
          writeDefinition20(out);
          out.writeObjectBegin(cl.getName());
          writeInstance(obj, out);
        }
        else {
          writeObject10(obj, out);
        }
      }
     以上代码会调用 writeObject10(obj, out);
     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();
      }

     

    2.反序列化的时候

    当反序列化时,默认的反序列化类是 UnsafeSerializer

     会首先根据反序列化类型,创建一个map 

     

      protected HashMap<String,FieldDeserializer> getFieldMap(Class<?> cl)
      {
        HashMap<String,FieldDeserializer> fieldMap
          = new HashMap<String,FieldDeserializer>();
    
        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();
            }
    
            Class<?> type = field.getType();
            FieldDeserializer deser;
    
            if (String.class.equals(type)) {
              deser = new StringFieldDeserializer(field);
            }
            else if (byte.class.equals(type)) {
              deser = new ByteFieldDeserializer(field);
            }
            。。。。。。

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

     如果是String类型的属性,使用的是StringFieldDeserializer   

      StringFieldDeserializer(Field field)
        {
          _field = field;
          _offset = _unsafe.objectFieldOffset(_field);  //这个会把属性对象对象的偏移量设置好
        }

    接下来会对每个属性用map对应序列化方式进行反序列化和赋值

      public Object readMap(AbstractHessianInput in, Object obj)
        throws IOException
      {
        try {
          int ref = in.addRef(obj);
    
          while (! in.isEnd()) {
            Object key = in.readObject();
    
            FieldDeserializer deser = (FieldDeserializer) _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);
        }
      }

    这个是StringFieldDeserializer 反序列化,由于名字相同的属性,反序列化是第一个子类,往后父类的发现map中有就会忽略,所以在属性序列化的时候,先序列化子类的,接着是父类的,但是他们在对象中的偏移量是一样的(用的是同一个反序列化类),所以相同名字的属相,子类总是会被父类覆盖掉。

        @SuppressWarnings("restriction")
        void deserialize(AbstractHessianInput in, Object obj)
          throws IOException
        {
          String value = null;
          
          try {
            value = in.readString();
    
            _unsafe.putObject(obj, _offset, value);
          } catch (Exception e) {
            logDeserializeError(_field, obj, value, e);
          }
        }

    五. 总结

        使用hessian序列化时,一定要注意子类和父类不能有同名字段

  • 相关阅读:
    TFS二次开发-基线文件管理器(5)-源码文件的读取
    TFS二次开发-基线文件管理器(4)-标签的创建
    TFS二次开发-基线文件管理器(3)-源码文件的读取
    TFS二次开发-基线文件管理器(2)-TFS登录
    TFS二次开发-基线文件管理器(1)-设计
    学习实践:使用模式,原则实现一个C++自动化测试程序
    学习实践:使用模式,原则实现一个C++数据库访问类
    C++字符转换等常用方法
    sqlserver 创建 aspstate的方法
    双网卡只有一个能ping通的解决办法
  • 原文地址:https://www.cnblogs.com/yfyzy/p/7197679.html
Copyright © 2020-2023  润新知