Skip to content

自定义序列化和反序列化

小马哥 edited this page Aug 4, 2016 · 23 revisions

概述

Hprose 对 Java 内置的大部分可序列化类型都提供了序列化和反序列化支持。对于用户自定义的类型,只要需要序列化属性提供了 getter 和 setter 方法,Hprose 的默认序列化方式就可以提供序列化支持。但是有时候,你用的可能是第三方库,比如 apache.commons 中的一些类型,它们没有提供可以用的 gettersetter 方法,但是你又希望使用 Hprose 传输它们,难道就没办法了吗?

办法总会有的,下面我就就来看一个例子,如何来通过自定义序列化和反序列化方式来实现对这种类型的序列化。

我们这里以 org.apache.commons.lang3.tuple.ImmutablePair<L, R> 这个类为例子,来看一下如何对它进行序列化和反序列化的支持。

首先我们来看一下这个类为什么不能被默认的序列化方式所支持。

对于 ImmutablePair 这个类来说,我们要对它进行序列化,我们关注的应该是对它 keyvalue 属性的序列化,但是我们发现它的 key 只有 getter 没有 setter,而 value 虽然既有 getter 也有 setter,但是它的 setter 方法并不能工作,如果调用它,会抛出 UnsupportedOperationException 异常,也就是说,这个类的 keyvalue 属性的 setter 是不能工作的。

那如何来让该类型支持序列化呢?Hprose 提供了三个接口:

  • hprose.io.serialize.Serializer
  • hprose.io.unserialize.Unserializer
  • hprose.io.convert.Converter

只要你自定义三个类,来实现这三个接口,然后使用这三个方法:

void SerializerFactory.register(Class<?> type, Serializer serializer);
void UnserializerFactory.register(Class<?> type, Unserializer unserializer);
void ConverterFactory.register(Class<?> type, Converter converter);

来注册它们就可以让你的自定义序列化和反序列化工作了。

实现自定义序列化

序列化的实现相对于反序列化要简单一些,我们要先想好我们的类型要序列化为什么样的 Hprose 类型,对于 ImmutablePair 类型来说,我们有多种选择,假设我们现在有一个 ImmutablePair 类型的数据对象,它的 key'name',它的值为 'Tom',那么我们可以将它序列化为:

  1. 只有两个元素的 List,例如:a2{s4"name"s3"Tom"}
  2. 只有一个键值对的 Map,例如:m1{s4"name"s3"Tom"}
  3. 两个键值对的 Map,例如:m2{s3"key"s4"name"s5"value"s3"Tom"}
  4. 一个类名为 ImmutablePair 的一个对象。例如:c13"ImmutablePair"2{s3"key"s5"value"}o0{s4"name"s3"Tom"}

关于 Hprose 的序列化格式可以参见:Hprose 规范——序列化格式

这四种格式都能够表示这个数据,我们这里选择第 2 种,因为这种方式比较容易跟其他语言交互,而且数据量小。

前面我们说了,自定义序列化可以直接实现 hprose.io.serialize.Serializer 接口,但如果我们要实现的是对引用类型的序列化的话,我们有更简单的方式,就是继承 ``hprose.io.serialize.ReferenceSerializer` 这个类,然后覆盖它的:

    @Override
    public void serialize(Writer writer, T obj) throws IOException {
        super.serialize(writer, obj);
        // write your actual serialization code in sub class
    }

方法就好了。

让我们来看一下 ImmutablePair 序列化的具体实现:

package hprose.example.io;

import static hprose.io.HproseTags.TagClosebrace;
import static hprose.io.HproseTags.TagMap;
import static hprose.io.HproseTags.TagOpenbrace;
import hprose.io.serialize.ReferenceSerializer;
import hprose.io.serialize.Writer;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.lang3.tuple.ImmutablePair;

public class ImmutablePairSerializer extends ReferenceSerializer<ImmutablePair> {

    public final static ImmutablePairSerializer instance = new ImmutablePairSerializer();

    @Override
    public void serialize(Writer writer, ImmutablePair pair) throws IOException {
        super.serialize(writer, pair);
        OutputStream stream = writer.stream;
        stream.write(TagMap);
        stream.write('1');
        stream.write(TagOpenbrace);
        writer.serialize(pair.left);
        writer.serialize(pair.right);
        stream.write(TagClosebrace);
    }
}

我们来详细解释一下上面这个程序。

public final static ImmutablePairSerializer instance = new ImmutablePairSerializer();

这一句定义了一个 ImmutablePairSerializer 静态实例,这样我们后面用到它时,就可以避免重复创建它。当然,你也可以用其它的单例模式来实现,但是考虑到一旦我们引用这个类,我们肯定是因为要使用它,使用这种饿汉模式的单例最简单直接,效率也高,所以没必要使用其它复杂的单例模式。

super.serialize(writer, pair);

这一句调用 serialize 父类实现,该父类实现的作用是把要序列化的对象添加到引用对象列表中去,如果是序列化 Hprose 的非对象类型的数据(这里不是指 Java 类型),那么这一句放在开头就可以了。如果是序列化对象类型的数据,那么,这一句应该放在序列化类信息之后,对象数据序列化之前。

在本例中,我们是将 ImmutablePair 序列化为 Hprose 的 Map 类型,所以是非对象类型。

在接下来的代码就是对数据的实际序列化操作。这里你需要对 Hprose 的数据格式有所了解才行。

stream.write(TagMap);

写入 Map 的开始标记。

stream.write('1');

写入 Map 的键值对个数,这里就 1 个键值对,所以写 '1',也就是这里写的是字符串形式的数字,如果你的键值对个数为 count(假设这里的 countint 类型变量),你可以用下面的方法写入:

ValueWriter.writeInt(stream, count);

我们这里直接写入字符 '1' 是偷懒的做法。

stream.write(TagOpenbrace);

表示 Map 数据开始。

writer.serialize(pair.left);
writer.serialize(pair.right);

写入键值对。

stream.write(TagClosebrace);

表示 Map 数据结束。

这样对 ImmutablePair 类型的自定义序列化就完成了。

实现自定义反序列化

在开始讲解反序列化实现之前,我们先来介绍一下类型转换器,对于 ImmutablePair 类型的数据,我们前面是作为 Hprose 的 Map 类型序列化的,但我们可能需要在反序列化时,支持更多其它方式序列化的数据也能够反序列化为 ImmutablePair 类型的数据。比如前面我们列举的四种方式,我们希望都能够反序列化为 ImmutablePair 类型的数据。

也就是说,对于反序列化来说,序列化数据和反序列化类型之间不存在一一对应关系。在不指定序列化类型时,序列化数据会对应一种默认序列化类型,当指定反序列化类型后,序列化数据会序列化为指定类型的数据。但是涉及到引用类型数据时,这里有一些复杂。

对于引用类型来说,严格意义上,对于同一数据的引用,应该为同一类型的数据。但是 Hprose 在反序列化时,允许放宽这个限制,也就是如果是相容的类型,引用了同一个数据,允许在反序列化时,将之前反序列化的数据转换为当前反序列化的类型的数据。因此,这里需要有一个转换器。

自定义转换器

听上去很复杂,实际上需要处理的类型转换并不多,通常来说:

如果支持反序列化字符串类型,那么需要处理的类型转换只有 Stringchar[] 这两种类型。

如果支持反序列化日期,时间类型,那么需要处理的类型转换只有 hprose.util.DateTime 类型。

如果支持 Hprose 的 List 类型,那么需要处理的类型转换有数组类型和 Collection 类型,在我们这个例子中,我们只处理 Object[] 就行了。

如果支持 Hprose 的 Map 类型,那么需要处理的类型转换只有 Map 类型。

如果支持 Hprose 的对象类型,如果我们是把对象作为对象反序列化的,那么通常我们不处理类型转换,如果我们是把对象作为 Map 反序列化的,那么我们只要处理 Map 类型转换就可以了,在我们后面实现的这个例子中,我们就是把对象类型作为 Map 来反序列化,然后再转换为 ImmutablePair 类型的。

说了这么多,我们来看一下 ImmutablePair 的转换器如何定义吧:

package hprose.example.io;

import hprose.io.convert.Converter;
import java.lang.reflect.Type;
import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair;

public class ImmutablePairConverter implements Converter<ImmutablePair> {

    public static final ImmutablePairConverter instance = new ImmutablePairConverter();

    public ImmutablePair convertTo(Object[] array) {
        return new ImmutablePair(array[0], array[1]);
    }

    public ImmutablePair convertTo(Map<?, ?> map) {
        if (map.size() == 1) {
            Map.Entry entry = map.entrySet().iterator().next();
            return new ImmutablePair(entry.getKey(), entry.getValue());
        }
        if (map.containsKey("key") && map.containsKey("value")) {
            return new ImmutablePair(map.get("key"), map.get("value"));
        }
        if (map.containsKey("left") && map.containsKey("right")) {
            return new ImmutablePair(map.get("left"), map.get("right"));
        }
        return null;
    }

    @Override
    public ImmutablePair convertTo(Object obj, Type type) {
        if (obj.getClass().isArray()) {
            return convertTo((Object[]) obj);
        }
        else if (obj instanceof Map) {
            return convertTo((Map<?, ?>) obj);
        }
        return (ImmutablePair) obj;
    }
}

这段代码很简单,对于数组,我们就是把数组的前两个元素作为 ImmutablePair 对象的键值对返回。对于 Map,如果这个 Map 只有一个键值对,那么我们就把它仅有的这个键值对作为 ImmutablePair 对象的键值对返回。如果这个 Map 有多个键值对,我们在里面寻找有没有键名为 "key""value" 的,或者键名为 "left""right" 的元素。有的话,我们把它们对应的值作为 ImmutablePair 对象的键值对返回,否则我们就返回 null

最后一句 return (ImmutablePair) obj 我们并不是想要把 obj 对象转换为 ImmutablePair 类型的对象,而只是想要产生一个漂亮的转换异常。

自定义反序列化

跟自定义序列化一样,我们也不需要直接实现 Unserializer 接口,Hprose 提供了一个已经实现了部分功能的反序列化基类 hprose.io.unserialize.BaseUnserializer

我们来看一下自定义 ImmutablePair 反序列化的实现类:

package hprose.example.io;

import hprose.common.HproseException;
import static hprose.io.HproseTags.TagEmpty;
import static hprose.io.HproseTags.TagList;
import static hprose.io.HproseTags.TagMap;
import static hprose.io.HproseTags.TagObject;
import hprose.io.unserialize.BaseUnserializer;
import hprose.io.unserialize.Reader;
import hprose.io.unserialize.ReferenceReader;
import hprose.io.unserialize.ValueReader;
import hprose.util.CaseInsensitiveMap;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair;

public class ImmutablePairUnserializer extends BaseUnserializer<ImmutablePair> {

    public static final ImmutablePairUnserializer instance = new ImmutablePairUnserializer();

    private static final ImmutablePairConverter converter = ImmutablePairConverter.instance;

    private Type[] getTypes(Type type) {
        if (type instanceof ParameterizedType) {
            return ((ParameterizedType)type).getActualTypeArguments();
        }
        return new Type[] { Object.class, Object.class };
    }

    private Map<String, Type> getTypeMap(Type[] types) {
        Map<String, Type> typeMap = new CaseInsensitiveMap();
        typeMap.put("key", types[0]);
        typeMap.put("value", types[1]);
        typeMap.put("left", types[0]);
        typeMap.put("right", types[1]);
        return typeMap;
    }

    private ImmutablePair readListAsImmutablePair(Reader reader, Type type) throws IOException {
        int count = ValueReader.readCount(reader);
        if (count != 2) throw new HproseException("Can't unserialize List to ImmutablePair.");
        Object[] array = new Object[2];
        ReferenceReader.readArray(reader, getTypes(type), array, 2);
        return new ImmutablePair(array[0], array[1]);
    }

    private ImmutablePair readMapAsImmutablePair(Reader reader, Type type) throws IOException {
        int count = ValueReader.readCount(reader);
        Type[] types = getTypes(type);
        if (count == 1) {
            Map<?, ?> map = new HashMap();
            ReferenceReader.readMap(reader, map, types[0], types[1], count);
            return converter.convertTo(map);
        }
        Map<String, Type> typeMap = getTypeMap(types);
        Map<String, Object> map = new CaseInsensitiveMap();
        ReferenceReader.readMap(reader, map, typeMap, count);
        return converter.convertTo(map);
    }

    private ImmutablePair readObjectAsImmutablePair(Reader reader, Type type) throws IOException {
        Type[] types = getTypes(type);
        Map<String, Type> typeMap = getTypeMap(types);
        Map<String, Object> map = ReferenceReader.readObjectAsMap(reader, CaseInsensitiveMap.class, typeMap);
        return converter.convertTo(map);
    }

    @Override
    public ImmutablePair unserialize(Reader reader, int tag, Type type) throws IOException {
        switch (tag) {
            case TagEmpty: return null;
            case TagList: return readListAsImmutablePair(reader, type);
            case TagMap: return readMapAsImmutablePair(reader, type);
            case TagObject: return readObjectAsImmutablePair(reader, type);
        }
        return super.unserialize(reader, tag, type);
    }
}

上面这段程序里面我们用了 ReferenceReader 类上的几个方法来读写数组和 Map,这些都是比 Reader 类上的方法更细粒度的反序列化方法,主要就是用于自定义反序列化,在其它情况下,一般是用不到的。

上面的代码虽然有点多,但是并不复杂,这里就不做进一步解释了。

我们来看一下如何使用。

使用自定义序列化和反序列化

我们如果要让 Hprose 支持 ImmutablePair 序列化和反序列化,除了定义上面三个类以外,还需要使用这三条语句:

ConverterFactory.register(ImmutablePair.class, ImmutablePairConverter.instance);
SerializerFactory.register(ImmutablePair.class, ImmutablePairSerializer.instance);
UnserializerFactory.register(ImmutablePair.class, ImmutablePairUnserializer.instance);

来注册这三个类,注册之后,我们就可以使用 Hprose 来序列化反序列化 ImmutablePair 类型了。

我们来看一个例子:

package hprose.example.io;

import hprose.io.ByteBufferStream;
import hprose.io.HproseReader;
import hprose.io.HproseWriter;
import hprose.io.convert.ConverterFactory;
import hprose.io.serialize.SerializerFactory;
import hprose.io.unserialize.UnserializerFactory;
import hprose.util.StrUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair;

public class ImmutablePairExam {
    private static class KeyValue {
        public String key;
        public String value;
    }
    public static void main(String[] args) throws IOException {
        ConverterFactory.register(ImmutablePair.class, ImmutablePairConverter.instance);
        SerializerFactory.register(ImmutablePair.class, ImmutablePairSerializer.instance);
        UnserializerFactory.register(ImmutablePair.class, ImmutablePairUnserializer.instance);
        ByteBufferStream stream = new ByteBufferStream();
        HproseWriter writer = new HproseWriter(stream.getOutputStream());
        writer.serialize(new ImmutablePair("Hello", "World"));
        User user = new User();
        user.name = "Tom";
        user.age = 18;
        writer.serialize(new ImmutablePair("User", user));
        Object[] array = new Object[] { "123", "234" };
        writer.serialize(array);
        writer.serialize(array);
        Map<String, Object> map = new HashMap();
        map.put("key", "User2");
        map.put("value", user);
        writer.serialize(map);
        Map<String, Object> map2 = new HashMap();
        map2.put("left", "User3");
        map2.put("right", user);
        writer.serialize(map2);
        writer.serialize(map2);
        KeyValue kv = new KeyValue();
        kv.key = "111";
        kv.value = "222";
        writer.serialize(kv);
        writer.serialize(kv);
        System.out.println(StrUtil.toString(stream));
        stream.flip();
        HproseReader reader = new HproseReader(stream.getInputStream());
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        System.out.println(reader.unserialize(ImmutablePair.class));
        stream.close();
    }
}

上面例子中的 User 类定义为:

package hprose.example.io;

public class User {
    public String name;
    public int age;
}

我们来看一下运行结果:

m1{s5"Hello"s5"World"}m1{s4"User"c22"hprose_example_io_User"2{s4"name"s3"age"}o0{s3"Tom"i18;}}a2{s3"123"s3"234"}r9;m2{s5"value"r7;s3"key"s5"User2"}m2{s4"left"s5"User3"s5"right"r7;}r16;c44"hprose_example_io_ImmutablePairExam_KeyValue"2{s3"key"s5"value"}o1{s3"111"s3"222"}r22;
(Hello,World)
(User,hprose.example.io.User@7daf6ecc)
(123,234)
(123,234)
(User2,hprose.example.io.User@7daf6ecc)
(User3,hprose.example.io.User@7daf6ecc)
(User3,hprose.example.io.User@7daf6ecc)
(111,222)
(111,222)

我们可以看到,不仅 ImmutablePair 类型的数据可以反序列化,而且其它类型的数据只要符合我们反序列化的条件,也可以反序列化为 ImmutablePair 类型的数据。

通过这种方式,我们自定义的序列化和反序列化也可以跟不同语言进行交互了,哪怕其他语言中并没有这种类型的定义。