[轉]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
此文為轉載,如有侵權請聯絡刪除