問題描述
訂單服務(wù)收到支付系統(tǒng)的消息,消息通過Hessian序列化刽漂,發(fā)現(xiàn)交易金額字段BigDecimal amount為0.00?出現(xiàn)這個問題大概分析一下芹枷,檢查消息發(fā)送日志,交易金額是不為零的莲趣,問題可能是Hessian序列化BigDecimal數(shù)據(jù)丟失鸳慈,臨時解決方案,把BigDecimal類型換成String喧伞。Hessian 4.0.33
深究原因
表面現(xiàn)象是Hessian序列化BigDecimal類型數(shù)據(jù)丟失蝶涩,但是到底是序列化丟失還是反序列化丟失?
通過對比序列化字節(jié)數(shù)組(轉(zhuǎn)成String絮识,便于查看),發(fā)現(xiàn)同樣的的bean只是amount不同绿聘,序列化的結(jié)果是一樣的,由此可以判斷Hessian序列化數(shù)據(jù)丟失
/**
* hessian 序列化
* @param obj
* @return
*/
public static byte[] serialize(Object obj) {
ByteArrayOutputStream bos = null;
Hessian2Output hessianOutput = null;
try {
bos = new ByteArrayOutputStream();
hessianOutput = new Hessian2Output(bos);
hessianOutput.writeObject(obj);
if (hessianOutput != null) {
try {
hessianOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 反序列
* @param bytes
* @return
*/
public static Object deserialize(byte[] bytes) {
ByteArrayInputStream bis = null;
Hessian2Input hessianInput = null;
try {
bis = new ByteArrayInputStream(bytes);
hessianInput = new Hessian2Input(bis);
return hessianInput.readObject();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bis !=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(hessianInput !=null){
try {
hessianInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public static void main(String[] args) throws Exception {
TestBean bean = new TestBean();
bean.setAmount(new BigDecimal("20.99"))
.setName("aaa")
.setNum(100);
TestBean beanZero = new TestBean();
beanZero.setAmount(new BigDecimal("0.00"))
.setName("aaa")
.setNum(100);
byte[] b1 = serialize(bean);
byte[] b2 = serialize(beanZero);
System.out.println("serialize Normal : "+new String(b1,"UTF-8"));
System.out.println("serialize zero : "+new String(b2,"UTF-8"));
}
結(jié)果對比:
C0/com.cloudy.chapter2.utils.HessianUtils$TestBean??num?name?amount`?d?aaaC?java.math.BigDecimal??scale?intVala?N
C0/com.cloudy.chapter2.utils.HessianUtils$TestBean??num?name?amount`?d?aaaC?java.math.BigDecimal??scale?intVala?N
源碼分析
通過查看Hessian序列化源碼次舌,可以知道Hessian序列化對象熄攘,獲取每個對象的Field列表和FieldSerializer列表,每種類型都有自己的序列化Serializer彼念,比如:ObjectFieldSerializer挪圾。通過XXSerializer序列化具體類型。
FieldSerializer []fieldSerializers = _fieldSerializers;
int length = fieldSerializers.length;
for (int i = 0; i < length; i++) {
fieldSerializers[i].serialize(out, obj);
}
TestBean的BigDecimal字段序列化方式就是ObjectFieldSerializer逐沙,這種序列化方式會獲取對象的非靜態(tài)和非transient字段哲思。而BigDecimal滿足的序列化屬性只有intVal和scale,new BigDecimal("20.99")對象的這兩個值intVal=null,scale=2,這個兩個字段無法表示一個BigDecimal對象吩案。所以以這種方式序列化BigDecimal都會是0.00棚赔,這應(yīng)該是Hessian的一個bug。
private final BigInteger intVal;
private final int scale;
private transient int precision;
private transient String stringCache;
/**
* If the absolute value of the significand of this BigDecimal is
* less than or equal to {@code Long.MAX_VALUE}, the value can be
* compactly stored in this field and used in computations.
*/
private final transient long intCompact;
解決方案
查看高版本代碼4.0.60徘郭,發(fā)現(xiàn)BigDecimal的序列化方式已改成StringValueSerializer序列化方式靠益,高版本增加從jar的META-INF/hessian目錄中deserializers和serializers加載配置的序列化和反序列化方式,其中java.math.BigDecimal=com.caucho.hessian.io.StringValueSerializer就存在該配置中残揉。
dubbo中也使用了hessian,但是dubbo不存在BigDecimal序列化丟失的問題胧后,查看dubbo源碼發(fā)現(xiàn),dubbo中的BigDecimal序列化使用也是StringValueSerializer抱环。
#SerializerFactory
_staticSerializerMap.put(BigDecimal.class, new StringValueSerializer());