问题描述
订单服务收到支付系统的消息,消息通过Hessian序列化,发现交易金额字段BigDecimal amount为0.00?出现这个问题大概分析一下,检查消息发送日志,交易金额是不为零的,问题可能是Hessian序列化BigDecimal数据丢失,临时解决方案,把BigDecimal类型换成String。Hessian 4.0.33
深究原因
表面现象是Hessian序列化BigDecimal类型数据丢失,但是到底是序列化丢失还是反序列化丢失?
通过对比序列化字节数组(转成String,便于查看),发现同样的的bean只是amount不同,序列化的结果是一样的,由此可以判断Hessian序列化数据丢失
/**
* 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"));
}
结果对比:
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,这种序列化方式会获取对象的非静态和非transient字段。而BigDecimal满足的序列化属性只有intVal和scale,new BigDecimal("20.99")对象的这两个值intVal=null,scale=2,这个两个字段无法表示一个BigDecimal对象。所以以这种方式序列化BigDecimal都会是0.00,这应该是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,发现BigDecimal的序列化方式已改成StringValueSerializer序列化方式,高版本增加从jar的META-INF/hessian目录中deserializers和serializers加载配置的序列化和反序列化方式,其中java.math.BigDecimal=com.caucho.hessian.io.StringValueSerializer就存在该配置中。
dubbo中也使用了hessian,但是dubbo不存在BigDecimal序列化丢失的问题,查看dubbo源码发现,dubbo中的BigDecimal序列化使用也是StringValueSerializer。
#SerializerFactory
_staticSerializerMap.put(BigDecimal.class, new StringValueSerializer());
网友评论