最近工作中遇見了一個小問題,在此記錄一下,大致是這樣的,有一父類,有一個屬性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); } }
其中注釋倆句是筆者加的,在此原因也找到了,問題很好解決,這部分源碼也不是很難,這些全是自己翻源碼看的,可能我的理解也不完全對,如果不對誰看見了,歡迎交流。