首先说一下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";
}
}
运行结果如下
然后我顺便验证了一下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的反序列化的源码吧,看看到底是咋回事
具体原因
代码比较多,挑几点重要的记录下,首先反序列化的类是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的序列化实现。