Hessian序列化的一個潛在問題
一. 最近的用rpc框架的時候,當用hessian序列化對象是一個對象繼承另外一個對象的時候,當一個屬性在子類和有一個相同屬性的時候,反序列化後子類屬性總是為null。
二. 示例代碼:
DTO對象
public class User implements Serializable { private String username ; private String password; private Integer age; }
public class UserInfo extends User { private String username; }
序列化代碼
UserInfo user = new UserInfo(); user.setUsername("hello world"); user.setPassword("buzhidao"); user.setAge(21);
ByteArrayOutputStream os = new ByteArrayOutputStream(); //Hessian的序列化輸出 HessianOutput ho = new HessianOutput(os); ho.writeObject(user);byte[] userByte = os.toByteArray(); ByteArrayInputStream is = new ByteArrayInputStream(userByte); //Hessian的反序列化讀取對象 HessianInput hi = new HessianInput(is); UserInfo u = (UserInfo) hi.readObject(); System.out.println("姓名:" + u.getUsername()); System.out.println("年齡:" + u.getAge());
輸出結果:
姓名:null
年齡:21
三. 看來這個結果一開始的反應就是不應該啊,後來自己帶著好奇查看了網上資料和源碼終於找到了原因。
1. hessian序列化的時候會取出對象的所有自定義屬性,相同類型的屬性是子類在前父類在後的順序。
2. hessian在反序列化的時候,是將對象所有屬性取出來,存放在一個map中 key = 屬性名 value是反序列類
3. 相同名字的屬性 子類的屬性總是會被父類的覆蓋,由於java多態屬性,在上述例子中父類 User.username = null
四、 下面是關鍵源碼分析 ,hessian版本是4.0.7
1.序列化
當序列化對象是一個java自定對象時,默認的序列化類是 UnsafeSerializer
調用writeObject
public void writeObject(Object obj, AbstractHessianOutput out) throws IOException { if (out.addRef(obj)) { return; } Class<?> cl = obj.getClass(); int ref = out.writeObjectBegin(cl.getName()); if (ref >= 0) { writeInstance(obj, out); } else if (ref == -1) { writeDefinition20(out); out.writeObjectBegin(cl.getName()); writeInstance(obj, out); } else { writeObject10(obj, out); } }
以上代碼會調用 writeObject10(obj, out);
protected void writeObject10(Object obj, AbstractHessianOutput out) throws IOException { for (int i = 0; i < _fields.length; i++) { Field field = _fields[i]; out.writeString(field.getName()); _fieldSerializers[i].serialize(out, obj); } out.writeMapEnd(); }
2.反序列化的時候
當反序列化時,默認的反序列化類是 UnsafeSerializer
會首先根據反序列化類型,創建一個map
protected HashMap<String,FieldDeserializer> getFieldMap(Class<?> cl) { HashMap<String,FieldDeserializer> fieldMap = new HashMap<String,FieldDeserializer>(); 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; // XXX: could parameterize the handler to only deal with public try { field.setAccessible(true); } catch (Throwable e) { e.printStackTrace(); } Class<?> type = field.getType(); FieldDeserializer deser; if (String.class.equals(type)) { deser = new StringFieldDeserializer(field); } else if (byte.class.equals(type)) { deser = new ByteFieldDeserializer(field); } 。。。。。。
fieldMap.put(field.getName(), deser); } } return fieldMap; }
如果是String類型的屬性,使用的是StringFieldDeserializer
StringFieldDeserializer(Field field) { _field = field; _offset = _unsafe.objectFieldOffset(_field); //這個會把屬性對象對象的偏移量設置好 }
接下來會對每個屬性用map對應序列化方式進行反序列化和賦值
public Object readMap(AbstractHessianInput in, Object obj) throws IOException { try { int ref = in.addRef(obj); while (! in.isEnd()) { Object key = in.readObject(); FieldDeserializer deser = (FieldDeserializer) _fieldMap.get(key); if (deser != null) deser.deserialize(in, obj); else in.readObject(); } in.readMapEnd(); Object resolve = resolve(in, obj); if (obj != resolve) in.setRef(ref, resolve); return resolve; } catch (IOException e) { throw e; } catch (Exception e) { throw new IOExceptionWrapper(e); } }
這個是StringFieldDeserializer 反序列化,由於名字相同的屬性,反序列化是第一個子類,往後父類的發現map中有就會忽略,所以在屬性序列化的時候,先序列化子類的,接著是父類的,但是他們在對象中的偏移量是一樣的(用的是同一個反序列化類),所以相同名字的屬相,子類總是會被父類覆蓋掉。
@SuppressWarnings("restriction") void deserialize(AbstractHessianInput in, Object obj) throws IOException { String value = null; try { value = in.readString(); _unsafe.putObject(obj, _offset, value); } catch (Exception e) { logDeserializeError(_field, obj, value, e); } }
五. 總結
使用hessian序列化時,一定要註意子類和父類不能有同名字段
Hessian序列化的一個潛在問題