Dubbo序列化之hessian2


title: Dubbo序列化之hessian2 tags:

  • dubbo
  • hessian2
  • 序列化
  • 丢失字段 categories: dubbo date: 2017-06-25 18:18:54

根据前面的说明可以知道序列化功能依然使用spi,我们来查看一下

    package com.alibaba.dubbo.common.serialize;
     
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
     
    import com.alibaba.dubbo.common.URL;
    import com.alibaba.dubbo.common.extension.Adaptive;
    import com.alibaba.dubbo.common.extension.SPI;
     
    /**
     * Serialization. (SPI, Singleton, ThreadSafe)
     *
     * @author ding.lid
     * @author william.liangf
     */
    @SPI("hessian2")
    public interface Serialization {
     
        /**
         * get content type id
         *
         * @return content type id
         */
        byte getContentTypeId();
     
        /**
         * get content type
         *
         * @return content type
         */
        String getContentType();
     
        /**
         * create serializer
         * @param url
         * @param output
         * @return serializer
         * @throws IOException
         */
        @Adaptive
        ObjectOutput serialize(URL url, OutputStream output) throws IOException;
     
        /**
         * create deserializer
         * @param url
         * @param input
         * @return deserializer
         * @throws IOException
         */
        @Adaptive
        ObjectInput deserialize(URL url, InputStream input) throws IOException;
     
    }
复制代码

很明显 默认情况我们使用hession2来作为序列化

    dubbo=com.alibaba.dubbo.common.serialize.support.dubbo.DubboSerialization
    hessian2=com.alibaba.dubbo.common.serialize.support.hessian.Hessian2Serialization
    java=com.alibaba.dubbo.common.serialize.support.java.JavaSerialization
    compactedjava=com.alibaba.dubbo.common.serialize.support.java.CompactedJavaSerialization
    json=com.alibaba.dubbo.common.serialize.support.json.JsonSerialization
    fastjson=com.alibaba.dubbo.common.serialize.support.json.FastJsonSerialization
    nativejava=com.alibaba.dubbo.common.serialize.support.nativejava.NativeJavaSerialization
复制代码

关于hession是caucho提供的协议,知名服务器resin也是他家的产品

据dubbo官方研究表名hession2在系统中推荐生产使用。但是我们发现在序列化过程中出现了丢字段的情况。

下面分析一下序列化

首先从正向序列化来看:

    Serializer serializer;
     
        serializer = (Serializer) _staticSerializerMap.get(cl);
        if (serializer != null)
          return serializer;
     
        if (_cachedSerializerMap != null) {
          synchronized (_cachedSerializerMap) {
       serializer = (Serializer) _cachedSerializerMap.get(cl);
          }
           
          if (serializer != null)
       return serializer;
        }
     
        for (int i = 0;
        serializer == null && _factories != null && i < _factories.size();
        i++) {
          AbstractSerializerFactory factory;
     
          factory = (AbstractSerializerFactory) _factories.get(i);
     
          serializer = factory.getSerializer(cl);
        }
     
        if (serializer != null) {
        }
     
        else if (JavaSerializer.getWriteReplace(cl) != null)
          serializer = new JavaSerializer(cl, _loader);
     
        else if (HessianRemoteObject.class.isAssignableFrom(cl))
          serializer = new RemoteSerializer();
     
    //    else if (BurlapRemoteObject.class.isAssignableFrom(cl))
    //      serializer = new RemoteSerializer();
     
        else if (Map.class.isAssignableFrom(cl)) {
          if (_mapSerializer == null)
       _mapSerializer = new MapSerializer();
           
          serializer = _mapSerializer;
        }
        else if (Collection.class.isAssignableFrom(cl)) {
          if (_collectionSerializer == null) {
       _collectionSerializer = new CollectionSerializer();
          }
     
          serializer = _collectionSerializer;
        }
     
        else if (cl.isArray())
          serializer = new ArraySerializer();
     
        else if (Throwable.class.isAssignableFrom(cl))
          serializer = new ThrowableSerializer(cl, getClassLoader());
     
        else if (InputStream.class.isAssignableFrom(cl))
          serializer = new InputStreamSerializer();
     
        else if (Iterator.class.isAssignableFrom(cl))
          serializer = IteratorSerializer.create();
     
        else if (Enumeration.class.isAssignableFrom(cl))
          serializer = EnumerationSerializer.create();
         
        else if (Calendar.class.isAssignableFrom(cl))
          serializer = CalendarSerializer.create();
         
        else if (Locale.class.isAssignableFrom(cl))
          serializer = LocaleSerializer.create();
         
        else if (Enum.class.isAssignableFrom(cl))
          serializer = new EnumSerializer(cl);
     
        if (serializer == null)
          serializer = getDefaultSerializer(cl);
     
        if (_cachedSerializerMap == null)
          _cachedSerializerMap = new HashMap(8);
     
        synchronized (_cachedSerializerMap) {
          _cachedSerializerMap.put(cl, serializer);
        }
     
        return serializer;
      }
复制代码

很明显当正常的javabean过来将会获得getDefaultSerializer也就是JavaSerializer

    public JavaSerializer(Class cl, ClassLoader loader)
     {
       introspectWriteReplace(cl, loader);
        
       if (_writeReplace != null)
         _writeReplace.setAccessible(true);
     
       ArrayList primitiveFields = new ArrayList();
       ArrayList compoundFields = new ArrayList();
        
       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;
     
    // XXX: could parameterize the handler to only deal with public
    field.setAccessible(true);
     
    if (field.getType().isPrimitive()
        || (field.getType().getName().startsWith("java.lang.")
       && ! field.getType().equals(Object.class)))
      primitiveFields.add(field);
    else
      compoundFields.add(field);
         }
       }
     
       ArrayList fields = new ArrayList();
       fields.addAll(primitiveFields);
       fields.addAll(compoundFields);
     
       _fields = new Field[fields.size()];
       fields.toArray(_fields);
     
       _fieldSerializers = new FieldSerializer[_fields.length];
     
       for (int i = 0; i < _fields.length; i++) {
         _fieldSerializers[i] = getFieldSerializer(_fields[i].getType());
       }
     }
复制代码
  1. 首先查找是否包含writeReplace方法

  2. 将所有的包含超类的字段全部放入arrayList中(去除静态和瞬态),先放入原生类型后放入包装类型

  3. 根据field类型放入对应的序列化器

    以上初始化完成后将javabean的元信息写入流并且进行实例的序列化

分析完序列化步骤我们来看一下反序列化步骤

同样的道理,默认将会返回JavaDeserializer

    public JavaDeserializer(Class cl)
     {
       _type = cl;
       _fieldMap = getFieldMap(cl);
     
       _readResolve = getReadResolve(cl);
     
       if (_readResolve != null) {
         _readResolve.setAccessible(true);
       }
     
       Constructor []constructors = cl.getDeclaredConstructors();
       long bestCost = Long.MAX_VALUE;
     
       for (int i = 0; i < constructors.length; i++) {
         Class []param = constructors[i].getParameterTypes();
         long cost = 0;
     
         for (int j = 0; j < param.length; j++) {
    cost = 4 * cost;
     
    if (Object.class.equals(param[j]))
      cost += 1;
    else if (String.class.equals(param[j]))
      cost += 2;
    else if (int.class.equals(param[j]))
      cost += 3;
    else if (long.class.equals(param[j]))
      cost += 4;
    else if (param[j].isPrimitive())
      cost += 5;
    else
      cost += 6;
         }
     
         if (cost < 0 || cost > (1 << 48))
    cost = 1 << 48;
     
         cost += (long) param.length << 48;
     
         if (cost < bestCost) {
           _constructor = constructors[i];
           bestCost = cost;
         }
       }
     
       if (_constructor != null) {
         _constructor.setAccessible(true);
         Class []params = _constructor.getParameterTypes();
         _constructorArgs = new Object[params.length];
         for (int i = 0; i < params.length; i++) {
           _constructorArgs[i] = getParamArg(params[i]);
         }
       }
     }
复制代码
  1. 获取字段集合
  2. 获取readResolve方法
  3. 获取最佳构造函数

获取字段集合代码如下

    /**
      * Creates a map of the classes fields.
      */
     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;
     
           // 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);
    }
    else if (short.class.equals(type)) {
      deser = new ShortFieldDeserializer(field);
    }
    else if (int.class.equals(type)) {
      deser = new IntFieldDeserializer(field);
    }
    else if (long.class.equals(type)) {
      deser = new LongFieldDeserializer(field);
    }
    else if (float.class.equals(type)) {
      deser = new FloatFieldDeserializer(field);
    }
    else if (double.class.equals(type)) {
      deser = new DoubleFieldDeserializer(field);
    }
    else if (boolean.class.equals(type)) {
      deser = new BooleanFieldDeserializer(field);
    }
    else if (java.sql.Date.class.equals(type)) {
      deser = new SqlDateFieldDeserializer(field);
     }
    else if (java.sql.Timestamp.class.equals(type)) {
      deser = new SqlTimestampFieldDeserializer(field);
     }
    else if (java.sql.Time.class.equals(type)) {
      deser = new SqlTimeFieldDeserializer(field);
     }
    else {
      deser = new ObjectFieldDeserializer(field);
    }
     
           fieldMap.put(field.getName(), deser);
         }
       }
     
       return fieldMap;
     }
复制代码

hashmap很明显如果此处出现了同名覆盖的字段,此处将覆盖

获取构造函数参数方法如下

    /**
     * Creates a map of the classes fields.
     */
    protected static Object getParamArg(Class cl)
    {
      if (! cl.isPrimitive())
        return null;
      else if (boolean.class.equals(cl))
        return Boolean.FALSE;
      else if (byte.class.equals(cl))
        return new Byte((byte) 0);
      else if (short.class.equals(cl))
        return new Short((short) 0);
      else if (char.class.equals(cl))
        return new Character((char) 0);
      else if (int.class.equals(cl))
        return Integer.valueOf(0);
      else if (long.class.equals(cl))
        return Long.valueOf(0);
      else if (float.class.equals(cl))
        return Float.valueOf(0);
      else if (double.class.equals(cl))
        return Double.valueOf(0);
      else
        throw new UnsupportedOperationException();
    }
复制代码

当构造函数为原生类型默认为0或false,否则传入null(构造函数如果是包含参数的请注意,很可能出现问题)

当读取实例时代码如下

    private Object readObjectInstance(Class cl, ObjectDefinition def)
      throws IOException
    {
      String type = def.getType();
      String []fieldNames = def.getFieldNames();
       
      if (cl != null) {
        Deserializer reader;
        reader = findSerializerFactory().getObjectDeserializer(type, cl);
     
        return reader.readObject(this, fieldNames);
      }
      else {
        return findSerializerFactory().readObject(this, type, fieldNames);
      }
    }
复制代码

明显此处fields是从hessionInput中读取(即序列化时的arraylist)

那么如果存在和父类同名的属性时,由于前面的说明会将之类的属性排在前面。那么通常在写到父类的属性通常都会为null(父类的同名字段通常都是没有赋值)

那么在读取的时候自然会将原来的已经设置的值给重新设置为null。

总结hession2存在的两个问题

  1. 构造函数如果包含参数,那么在hession初始化的时候将会给引用类型传入null,其他类型传入0或者false,要保证不要产生空指针
  2. 如果序列化的不是普通类型包含多层级的 父类,要确保子类不要包含父类同名字段,否则很容易出现子类字段在赋值后被重新赋值为空,造成字段丢失

比如如下

    /**
     * Created by Geekkiller on 2017/3/16.
     */
    public class TmRemindSo extends So {
     
        private static final long serialVersionUID = 1L;
        /**
         * 是否分页:
         */
        private boolean isPage = false;
        /**
         * 上次保养起始日
         */
        private String beginMaintainDate;
        /**
         * 上次保养结束日
         */
        private String endMaintainDate;
        /**
         * 下次保养起始日
         */
        private String beginNextMaintainDate;
        /**
         * 下次保养结束日
         */
        private String endNextMaintainDate;
        /**
         * 客户生日起始
         */
        private String beginBirthday;
        /**
         * 客户生日结束
         */
        private String endBirthday;
     
        private String beginLastServiceTime;
     
        private String endLastServiceTime;
     
        private String beginCardEndTime;
        private String endCardEndTime;
        private String cardName;
        private String cardType;
     
        private String beginDriverExpiryDate;
        private String endDriverExpiryDate;
     
        private String beginAnnualDate;
        private String endAnnualDate;
     
        private String idStore; //门店id
     
        private String idOwnOrg;            //组织ID
    }
    /**
     * Created by qixiaobo on 2016/10/11.
     * 数据库查询基类
     */
    public class So implements Serializable, Pagable, Sortable {
     
        private static final long serialVersionUID = 436513842210828356L;
        /*
         * 当前页码
         */
        private int currentPage = 1;
        private int pageSize = AppConstant.MIDPAGESIZE;
        private Integer id;
        private String pkId;
        /**
         * pkId的列表
         */
        private List<String> pkIdList;
        private String idOwnOrg;
        /**
         * 创建人
         */
        private String creator;
        private List<Sort> sorts = new LinkedList<>();
    }
复制代码

如上idownorg字段存在同名,在hessian环境下就会出现字段丢失

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值