dubbo序列化的一点注意
最近工作中遇见了一个小问题,在此记录一下,大致是这样的,有一父类,有一个属性traceId,主要是记录日志号,这样可以把所有日志串起来,利于排查问题,所有的pojo对象继承于此,但是其中一同事在子类pojo中也增加了这一个属性,在消费者端给traceId设置了值,但经过序列化解析后,提供者端这个traceId时,值为空,解决问题很简单啊,把子类中的traceId属性去掉搞定。
虽然问题很好解决但是这让笔者很懵逼啊,什么状况,都清楚地,实例化的子类,私有属性,取的肯定是实例设定的值,虽然我对此深信不疑,但是这还是让我怀疑了我自己,于是写了如下一些代码的验证这个问题。
1.先把问题抛出来一下。
Consumer端代码
@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(); }
Provider端代码
public class DemoServiceImpl implements DemoService { public String serTest(Bean bean) { System.out.println(bean); return "123"; } }
运行结果如下
2.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的反序列化的源码吧,看看到底是咋回事
3.具体原因研究
代码比较多,挑几点重要的记录下,首先反序列化的类是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); } }
其中注释俩句是笔者加的,在此原因也找到了,问题很好解决,这部分源码也不是很难,这些全是自己翻源码看的,可能我的理解也不完全对,如果不对谁看见了,欢迎交流。
作者: 重名 出处: http://www.cnblogs.com/daily-note