跳到主要内容

实例

note

要运行例子,你需要从GitHub克隆ActiveJ

git clone https://github.com/activej/activej

并将其作为一个Maven项目导入。 查看标签 v5.0-beta2。 在运行这些例子之前,先建立项目。 这些例子位于 activej/examples/core/serializer。

简单的对象序列化#

为了创建其实例可以被序列化/反序列化的类,你应该使用特殊的注解。

  • @Serialize 在属性获取器上添加订单号的注释。 参数 `顺序` 在类被改变的情况下提供更好的兼容性。
  • @Deserialize 注解,在构造函数中使用属性名称(应该与getter中的名称相同)。

这足以创建可序列化的POJO,例如。

public static class Person {  public Person(@Deserialize("age") int age,      @Deserialize("name") String name) {    this.age = age;    this.name = name;  }
  @Serialize  public final int age;
  @Serialize  public final String name;
  private String surname;
  @Serialize  public String getSurname() {    return surname;  }
  public void setSurname(String surname) {    this.surname = surname;  }}

现在,让我们做一些序列化工作。 我们将创建一个 Person 实例,一个存储序列化结果的字节数组,以及一个 BinarySerializer 实例,代表一个将 <T> 值编码和解码为字节数组的序列化器(本例中是<Person> 值)。

Person john = new Person(34, "Jim");john.setSurname("Smith");byte[] buffer = new byte[200];BinarySerializer<Person> serializer = SerializerBuilder.create()    .build(Person.class);

就这样,现在我们可以序列化和反序列化我们的 Person 实例。

serializer.encode(buffer, 0, john);Person johnCopy = serializer.decode(buffer, 0);

让我们做一个简单的测试来检查一切是否正常工作。

System.out.println(john.age + " " + johnCopy.age);System.out.println(john.name + " " + johnCopy.name);System.out.println(john.getSurname() + " " + johnCopy.getSurname());

在你运行这个例子后,你会收到以下输出。

34 34Jim JimSmith Smith

这意味着序列化和反序列化工作正常。

你可以在以下网站上探索完整的例子来源 GitHub

泛型和接口#

ActiveJ串行器可以简单地管理更复杂的对象。 例如,让我们看看它是如何与接口和 泛型的工作。

首先,创建一个简单的 技能 类。

System.out.println(john.age + " " + johnCopy.age);System.out.println(john.name + " " + johnCopy.name);System.out.println(john.getSurname() + " " + johnCopy.getSurname());

接下来,创建一个 Person 接口,它有一个返回技能列表的方法。

public interface Person<K, V> {  @Serialize  List<Skill<K, V>> getSkills();}

最后创建一个 Developer 类,实现 Person 接口。

public static class Developer implements Person<Integer, String> {  private List<Skill<Integer, String>> list;
  @Serialize  @Override  public List<Skill<Integer, String>> getSkills() {    return list;  }
  public void setSkills(List<Skill<Integer, String>> list) {    this.list = list;  }}

让我们继续进行序列化。 与前面的例子类似,我们将创建一个 Developer的实例,一个 字节数组来存储序列化的结果,以及一个 BinarySerializer<Developer> serializer 的实例。

Developer developer = new Developer();developer.setSkills(Arrays.asList(    new Skill<>(1, "Java"),    new Skill<>(2, "ActiveJ")));
byte[] buffer = new byte[200];BinarySerializer<Developer> serializer = SerializerBuilder.create()    .build(Developer.class);

现在让我们对我们的 Developer 实例进行序列化和反序列化。

serializer.encode(buffer, 0, developer);Developer developer2 = serializer.decode(buffer, 0);

检查序列化的工作是否正确。

for (int i = 0; i < developer.getSkills().size(); i++) {  System.out.println(developer.getSkills().get(i).getKey() + " - " + developer.getSkills().get(i).getValue() +      ", " + developer2.getSkills().get(i).getKey() + " - " + developer2.getSkills().get(i).getValue());}

如果你运行这个例子,你会收到以下输出。

1 - Java, 1 - Java2 - ActiveJ, 2 - ActiveJ

这意味着序列化工作是正确的。

你可以在以下网站上探索完整的例子来源 GitHub

基于路径的序列化#

有时你需要序列化一个代表通用类型的字段。 比方说,一个 地图。 你需要做的就是在该字段上放一个 @Serialize 注释。

@Serializepublic Map<Integer, String>> map;

但是,如果你想让一个地图包含可归零的值怎么办? 你可以使用 @SerializeNullable 注释。 然而,如果你只是把这个注释放在一个字段上,那就意味着整个地图都可以是空的。

我们应该把注释直接放在一个 String! 从ActiveJ v5.0-beta1开始,一些序列化注解适用于一个类型的使用。 因此,每当你需要用额外的序列化信息来标记一些类型时,只要把注解放在一个类型上即可。

@Serializepublic Map<Integer, @SerializeNullable String>> map;

在旧版本的ActiveJ中,指定哪种类型应该是nullable的唯一方法,例如,通过使用注解中的 path 选项。 要序列化一个具有可忽略值的地图,你需要做这样的事情。

@Serialize@SerializeNullable(path = 1)public Map<Integer, String>> map;

每个类型参数都有自己的索引,从0开始。 因此, 整数 的索引为0, 字符串 的索引为1。 为了指定String可以为空,我们在注解中添加 path = 1

路径 参数实际上是一个ints数组,所以你可以在嵌套声明中指定一个nullable类型。 你也可以在一个字段(或获取器)上放置多个基于路径的注释。

下面是一个例子。

@Serialize@SerializeNullable //指地图<String, Map<Integer, Float[]>>@SerializeNullable(path = 1) //指地图<Integer, Float[]@SerializeNullable(path = 0) // refers to String@SerializeNullable(path = {1, 0}) // refers to Integer@SerializeNullable(path = {1, 1}) // refers to Float[]@SerializeNullable(path = {1, 1, 0}) //refers to the Float elements of the arraypublic Map<String, Map<Integer, Float[]>> complexMap;
note

不幸的是,在某些情况下,如果一个类型被注解了,旧版本的Java可能无法正确解决注解所注解的类型参数。 在这种情况下,如果你使用小于12的Java版本运行你的应用程序,你可以使用基于路径的方法。

在本教程中,我们将向你展示如何使用路径方法或类型使用方法来编写序列化器。

首先,让我们来看看一个类型的使用方法。

我们将定义一个参数化的类 嵌套

public static class Nested<T1, T2> {  @Serialize  public final T1 first;  @Serialize  public final T2 second;
  public Nested(@Deserialize("first") T1 first, @Deserialize("second") T2 second) {    this.first = first;    this.second = second;  }
  @Override  public String toString() {    return "Nested{" + first + ", " + second + '}';  }}

然后我们定义一个 Storage 类来进行序列化。

public static class Storage {  @Serialize  public List<@SerializeNullable Nested<Integer, @SerializeNullable String>> listOfNested;}

一个类有一个单一的字段,它作为一个 列表 ,可归零 嵌套。 此外, 嵌套 类的第二个类型参数(String)本身是可以归零的。 我们创建一个序列化器,如下所示。

BinarySerializer<Storage> serializer = SerializerBuilder.create(definingClassLoader)    .build(Storage.class);

然后我们构建一个 Storage 类,并在列表中添加可忽略的元素。 一旦我们运行这个例子,我们应该看到以下输出。

[嵌套{1, abc}, null, 嵌套{5, null}][嵌套{1, abc}, null, 嵌套{5, null}]

这既显示了原始的 Storage 内容,也显示了反序列化的内容。

note

在对数组进行注释时,应特别注意。

@Foo String @Bar []

这里, @Foo 注解了 String ,而 @Bar 注解了整个数组 String[]

这符合 《Java语言规范》的规定。

你可以在以下网站上探索完整的例子来源 GitHub

现在,让我们看看基于路径的方法。

我们将使用相同的 嵌套 类。 然而,我们将使用带有 路径的注解来注解 存储 类的一个字段。

public static class Storage {  @Serialize  @SerializeNullable(path = 0)  @SerializeNullable(path = {0, 1})  public List<Nested<Integer, String>> listOfNested;}

当创建一个序列化器时,应该启用注解兼容模式。

BinarySerializer<Storage> serializer = SerializerBuilder.create(definingClassLoader)    .withAnnotationCompatibilityMode() // Compatibility mode has to be enabled    .build(Storage.class);

运行 main 方法后,我们应该看到与类型使用方法相同的输出。

[嵌套{1, abc}, null, 嵌套{5, null}][嵌套{1, abc}, null, 嵌套{5, null}]

你可以在以下网站上探索完整的例子来源 GitHub

note

你不能混合两种注释方式。 如果你想使用类型使用方法,那么就按原样使用。 只要确保你的Java版本可以解决类型上的注解。

对于基于路径的方法,不要忘记在 SerializerBuilder上启用注释兼容模式

固定大小和可置换字段的序列化#

ActiveJ串行器有一些 helper annotations,例如。

  • @SerializeNullable 在可以有空值的属性上。 这个注释也有一个特殊的 `路径` 参数。 它代表了变量的数据类型树的路径。 它允许指出哪些 "节点 "是可忽略的。

正如你所看到的,你可以为同一个数据结构的不同路径写几个注释。

让我们创建一个简单的例子,说明如何使用这些注释。

public static class Storage {  @Serialize  public @SerializeNullable String @SerializeFixedSize(3) [] strings;
  @Serialize  public byte @SerializeFixedSize(4) [] bytes;}

现在让我们对 Storage 的一个实例进行序列化和反序列化,与前面的例子类似。 我们将创建一个 Storage的实例,一个字节数组来存储序列化的结果,以及一个 BinarySerializer<Storage> serializer 的实例。

Storage storage = new Storage();storage.strings = new String[]{"abc", null, "123", "superfluous"};storage.bytes = new byte[]{1, 2, 3, 4, 5, 6};
byte[] buffer = new byte[200];BinarySerializer<Storage> serializer = SerializerBuilder.create()    .build(Storage.class);

最后,对 Storage 实例进行序列化和反序列化。

serializer.encode(buffer, 0, storage);Storage limitedStorage = serializer.decode(buffer, 0);

让我们看看序列化是如何影响 存储

System.out.println(Arrays.toString(storage.strings) + " -> " + Arrays.toString(limitedStorage.strings));System.out.println(Arrays.toString(storage.bytes) + " -> " + Arrays.toString(limitedStorage.bytes));

如果你运行这个例子,你会看到以下输出。

[abc, null, 123, superfluous] -> [abc, null, 123][1, 2, 3, 4, 5, 6] -> [1, 2, 3, 4]

正如你在第一行看到的, 存储limitedStorage不同。 这是因为 @SerializeFixedSize 注解被设置为值 3 ,用于 strings 属性。 因此,在进行序列化的同时,"多余的 "被从数组中移除。

你可以在以下网站上探索完整的例子来源 GitHub

自定义序列化器#

在这个例子中,我们将演示如何为一个 LocalDate 类写一个自定义的序列化器。 你可以把这个例子 作为参考,为你可能需要序列化的其他类编写序列化程序。

让我们设想一下,我们需要序列化一个包含 LocalDate 字段的类。

public static class LocalDateHolder {  @Serialize  public final LocalDate date;
  public LocalDateHolder(@Deserialize("date") LocalDate date) {    this.date = date;  }
  @Override  public String toString() {    return "LocalDateHolder{date=" + date + '}';  }}

默认情况下,ActiveJ Serializer不知道如何序列化一个 LocalDate 类,所以如果你 天真地试图序列化它,它会抛出一个异常。 我们必须为一个 LocalDate 类提供一个自定义的序列化器,来序列化 LocalDateHolder 类。

public static class SerializerDefLocalDate extends AbstractSerializerDef {
  @Override  public Class<?> getEncodeType() {    return LocalDate.class;  }
  @Override  public Expression encoder(final StaticEncoders staticEncoders,      final Expression buf,      final Variable pos,      final Expression localDate,      final int version,      final CompatibilityLevel compatibilityLevel) {    return sequence(        writeVarInt(buf, pos, call(localDate, "getYear")),        writeVarInt(buf, pos, call(localDate, "getMonthValue")),        writeVarInt(buf, pos, call(localDate, "getDayOfMonth"))    );  }
  @Override  public Expression decoder(final StaticDecoders staticDecoders,      final Expression input,      final int version,      final CompatibilityLevel compatibilityLevel) {    return staticCall(LocalDate.class, "of",        readVarInt(input),        readVarInt(input),        readVarInt(input)    );  }}

我们扩展 AbstractSerializerDef 类并实现方法: - 类<?> getEncodeType() - 指定 本地日期。 lass 为要序列化的数据类型 - 表达式编码器(... - 这里我们指示序列化器如何序列化 本地日期 实例。 我们实际上需要 序列化3个 int 值(year, month, and dayOfMonth) 并把它们写到 BinaryOutput 使用类似Lisp的 Expression API,我们就这样做。 - Expression decoder(...) - 这里我们需要指示序列化器如何将原始字节反序列化为一个 LocalDate 实例。 这个过程是与编码相反的。 首先,我们必须阅读3 int values (year, monthsdayOfmonth来自 BinaryInput 然后我们可以创建一个新的 本地日期 实例通过调用静态出厂方法 静态本地日期 (整年) 整月份,整日Ofmonth) 和过去反序列化的 int 值。 我们再次使用 Expression API来完成这项任务。 最后,我们需要将我们的 LocalDate 的序列化器添加到 SerializerBuilder

BinarySerializer<LocalDateHolder> serializer =    SerializerBuilder.create()        .with(LocalDate.class, ctx -> new SerializerDefLocalDate())        .build(LocalDateHolder.class);

如果我们运行 LocalDateSerializerExample#main 方法,我们应该看到以下输出。

将LocalDateHolder序列化。LocalDateHolder{date=2021-03-17}带有序列化的LocalDateHolder的字节数组。[-27, 15, 3, 17]解序列化的LocalDateHolder。LocalDateHolder{date=2021-03-17}

你可以在以下网站上探索完整的例子来源 GitHub