首先说一下java在序列化的注意

      大家都知道如果需要序列化的话只需要实现Serializable接口就可以了。但是在实现这个接口的时候有也需要注意知道serialVersionUID这个属性

SerialVersionUID是一个标识符,当它通常使用对象的哈希码序列化时会标记在对象上。这个标识符可以加可以不加,但是会有不同的影响。

如果你不添加serialVersionUID,也不会影响使用,但是存在即合理,既然存在这个字段,那么他一定是有用的,当你序列化的时候这个UID会被写入文件,当反序列话的时候会去读取这个ID,并与反序列化的类中的UID对比,如果相同,那么反序列化就成功,如果不同,反序列化就会失败

当你不指定UID的时候,系统会根据类的结构生成相应的hash值赋值给UID,但是当你的类的结构发生变化,比如增加一个字段或者减少一个字段的时候,UID就会发生变化,那么反序列话的时候两个类的UID就不一样了,就会反序列化失败

所以手动指定UID,主要就是在类结构发生变化时,减少反序列化失败的几率(如果类发生了非常规的结构变化,比如类名变化,成员变量的类型变化,就算是指定了UID,反序列化也会失败)

 

然后在再说我在使用dubbo时遇到的一点问题

           最近工作中遇见了一个小问题,在此记录一下,大致是这样的,有一父类,有一个Map类型的属性dataMap,主要是记录入参的参数,这样就把所有的入参放在Map中使用,利于子类使用,所有的pojo对象继承于此,但是其中一个子类pojo中也增加了这一个属性,在消费者端给传入参数且在controller层可以看到dataMap这个属性中是有值的,但是经过调用rpc的远程服务后进入到实现类的时候发现该属性的值为空,遇到这个问题的时候第一时间我是懵逼的   经过查资料得知,在调用rpc远程服务的时候会对对象参数进行序列化和反序列化,所以猜想可能是在序列化的时候出现了问题。查看代码发现子类中的属性名称及类型和父类中完全一致。故修改子类的属性名称后有值。问题搞定。

        虽然问题很好解决但是这让我很懵逼啊,什么状况,都清楚地,实例化的子类,私有属性,取的肯定是实例设定的值,虽然我对此深信不疑,但是这还是让我怀疑了我自己,于是写了如下一些代码的验证这个问题。


消费端代码

@Setter
@Getter
@ToString
public class BaseBean implements Serializable {
    private String xxx;
    private String yyy;
    private Integer zzz;
}
@Setter
@Getter
@ToString
public class Bean extends BaseBean {
    private String xxx;
    private String yyy;
    private Integer zzz;
    private String myStr;
}
public class Consumer {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                new String[] { "applicationContext.xml" });
        context.start();

        DemoService demoService = (DemoService) context.getBean("demoService");
        Bean bean = new Bean();
        bean.setMyStr("123");
        bean.setXxx("xxx");
        bean.setYyy("yyy");
        bean.setZzz(789);
        String hello = demoService.serTest(bean);
        System.out.println(hello);
        System.in.read();
}

 服务端代码

public class DemoServiceImpl implements DemoService {
    public String serTest(Bean bean) {
        System.out.println(bean);
        return "123";
    }
}

运行结果如下

dubbo 指定序列 dubbo默认序列化_dubbo 指定序列

然后我顺便验证了一下java中的反序列化是不是也存在这样的问题

代码如下

public class TestSeriali {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Bean bean = new Bean();
        bean.setMyStr("123");
        bean.setXxx("xxx");
        bean.setYyy("yyy");
        bean.setZzz(789);
        ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("chongming"));
        out.writeObject(bean);
        System.out.println("序列化完毕..");
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("chongming"));
        Bean beanResult = (Bean) in.readObject();
        System.out.println("反序列化完毕..");
        System.out.println(beanResult);
    }
}

结果如下

dubbo 指定序列 dubbo默认序列化_dubbo 指定序列_02

 

注:dubbo支持的其余集中序列化方式也做了验证,结果都是一样的,在这里就略过了。

这段代码证实了我的想法还是对的,问题就是出在dubbo的反序列化了。好吧翻翻dubbo的反序列化的源码吧,看看到底是咋回事

 

具体原因

代码比较多,挑几点重要的记录下,首先反序列化的类是JavaSerializer。

这个类的构造方法里调用了这样的方法getFieldMap,把里面本类和父类的所有方法放到一个fieldMap里,因为是HashMap,为了保证方法名不覆盖,这个方法里做了一个操作就是fieldMap.get(field.getName()) != null,有的话就继续循环下去不覆盖,这样的话如果有同名的方法,那只有子类的方法在里面。还有这个类Hessian2Input要说下,其中的方法readObjectInstance,它会取到本类和父类的所有方法放到一个数组fieldNames下,这些说完了说到这里面反序列化的方法JavaSerializer的readObject,是按fieldNames数组循环取值,在流里面挨个取出来,一直赋给本类的set方法,先是有值的,到父类时,取到的为空,就把本类的值覆盖了。到这里原因就清楚了。

主要的代码贴下来好了,如下

JavaSerializer构造方法及getFieldMap方法,获取到fieldMap

public JavaDeserializer(Class cl)
  {
    _type = cl;
    _fieldMap = getFieldMap(cl);
.......

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

Hessian2Input的readObjectInstance

private Object readObjectInstance(Class cl, ObjectDefinition def)
    throws IOException
  {
    String type = def.getType();
    String []fieldNames = def.getFieldNames();
......

JavaSerializer的readObject,这个贴的全一点

public Object readObject(AbstractHessianInput in,
               Object obj,
               String []fieldNames)
    throws IOException
  {
    try {
      int ref = in.addRef(obj);

      for (int i = 0; i < fieldNames.length; i++) {
        String name = fieldNames[i];
        
        //重名的话,取出的都是私有的属性
        FieldDeserializer deser = (FieldDeserializer) _fieldMap.get(name);

        if (deser != null)
      // 当in读到父类时,把本类的属性值覆盖掉了
      deser.deserialize(in, obj);
        else
          in.readObject();
      }

      Object resolve = resolve(obj);

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

      return resolve;
    } catch (IOException e) {
      throw e;
    } catch (Exception e) {
      throw new IOExceptionWrapper(obj.getClass().getName() + ":" + e, e);
    }
  }

 

其中注释俩句是自己加上去的,在此原因也找到了,问题完美解决。

这里记录了自己遇到的问题,顺便温故了java的序列化实现。