美文网首页
Hessian序列化BigDecimal字段数据丢失

Hessian序列化BigDecimal字段数据丢失

作者: Freedom_2725 | 来源:发表于2019-08-11 18:57 被阅读0次

问题描述

订单服务收到支付系统的消息,消息通过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());

相关文章

网友评论

      本文标题:Hessian序列化BigDecimal字段数据丢失

      本文链接:https://www.haomeiwen.com/subject/vsqdjctx.html