Перейти к основному содержанию

Примеры

note

Чтобы запустить примеры, необходимо клонировать ActiveJ с GitHub

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

И импортируйте его как проект Maven. Посмотрите тег v5.0-beta2. Перед запуском примеров выполните сборку проекта. Эти примеры расположены по адресу activej/examples/core/serializer.

Простая сериализация объектов#

Для создания классов, экземпляры которых могут быть сериализованы/десериализованы, необходимо использовать специальные аннотации:

  • @Serialize аннотация с порядковым номером в геттере свойства. Параметр `order` обеспечивает лучшую совместимость в случае изменения классов.
  • @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 Serializer может просто управлять более сложными объектами. Например, давайте посмотрим, как это работает с интерфейсами и дженериками.

Сначала создайте простой класс Skill :

public static class Skill<K, V> {  private final K key;  private final V value;
  public Skill(@Deserialize("key") K key,      @Deserialize("value") V value) {    this.key = key;    this.value = value;  }
  @Serialize  public K getKey() {    return key;  }
  @Serialize  public V getValue() {    return value;  }}

Затем создайте интерфейс 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>:

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

Сериализация на основе пути#

Иногда вам нужно сериализовать поле, представляющее общий тип. Допустим, есть Map. Все, что вам нужно сделать, это поместить аннотацию @Serialize на это поле.

@Serializepublic Map<Integer, String>> map;

Но что если вы хотите, чтобы map содержала нулевые значения? Вы можете использовать аннотацию @SerializeNullable . Однако, если вы просто поместите эту аннотацию на поле, это будет означать, что вся карта может быть nullable.

Мы должны поместить аннотацию непосредственно на String! Начиная с ActiveJ v5.0-beta1 некоторые аннотации сериализатора применимы к использованию типа. Поэтому всякий раз, когда вам нужно пометить какой-то тип дополнительной информацией о сериализаторе, просто поместите аннотацию на тип:

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

В старых версиях ActiveJ единственным способом указать, какой тип должен быть nullable, например, было использование опции path из аннотации. Чтобы сериализовать map с нулевыми значениями, нужно сделать примерно следующее:

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

Каждый параметр типа имеет свой индекс, начиная с 0. Так, Integer имеет индекс 0, а String - индекс 1. Для того чтобы указать, что String может быть null, мы добавляем в аннотацию path = 1 .

Параметр path фактически является массивом ints, поэтому во вложенном объявлении можно указать тип nullable. Вы также можете поместить несколько аннотаций на основе пути на поле (или геттер).

Вот пример:

@Serialize@SerializeNullable // refers to Map<String, Map<Integer, Float[]>>@SerializeNullable(path = 1) // refers to Map<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 могут неправильно определить, какой параметр типа аннотирует аннотация, если аннотируется тип. В этом случае вы можете использовать подход на основе пути, если вы запускаете свое приложение, используя версию Java меньше 12.

В этом уроке мы покажем вам, как писать сериализаторы, используя либо подход "путь", либо подход "использование типа".

Сначала рассмотрим подход с использованием типа.

Мы определим параметризованный класс Nested:

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;}

Класс имеет одно поле, которое представляет собой List nullable Nested. Кроме того, второй параметр типа класса Nested (String) сам по себе является nullable. Мы создаем сериализатор следующим образом:

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

Затем мы создаем класс Storage и добавляем в список nullable элементы. После запуска примера мы должны увидеть следующий результат:

[Nested{1, abc}, null, Nested{5, null}][Nested{1, abc}, null, Nested{5, null}]

Здесь показано как оригинальное содержимое Storage , так и десериализованное.

note

При аннотировании массивов следует проявлять особую осторожность.

@Foo String @Bar []

Здесь @Foo аннотирует String , а @Bar аннотирует весь массив String[].

Это соответствует спецификации языка Java .

Вы можете изучить исходный код примеров на GitHub

Теперь давайте рассмотрим подход, основанный на пути.

Мы будем использовать один и тот же класс Nested . Однако мы будем аннотировать поле класса Storage с помощью аннотаций с пути:

``java url=/examples/core/serializer/src/main/java/SerializePathExample.java tag=Storage

**При создании сериализатора должен быть включен режим совместимости аннотаций:**
````mdx-code-block``java url=/examples/core/serializer/src/main/java/SerializePathExample.java tag=Serializer
После выполнения метода `main` мы должны увидеть тот же результат, что и при использовании метода type use.```bash[Вложенные{1, abc}, null, Вложенные{5, null}][Вложенные{1, abc}, null, Вложенные{5, null}]```
Вы можете изучить полные исходные тексты примеров на <Githublink text='GitHub' url='/examples/core/serializer/src/main/java/SerializePathExample.java'/>
:::note
Вы не можете смешивать оба стиля аннотаций. Если вы хотите использовать подход типа "использовать", то используйте его как есть. Только убедитесь, что ваша версия Java может разрешать аннотации на типы.
Для подхода на основе путей не забудьте включить режим совместимости аннотаций на `SerializerBuilder`.
:::
## Сериализация с фиксированным размером и нулевыми полямиActiveJ Serializer имеет некоторые <Githublink text='helper annotations' url='/core-serializer/src/main/java/io/activej/serializer/annotations'/>, например:* <Githublink text='@SerializeNullable' url='/core-serializer/src/main/java/io/activej/serializer/annotations/SerializeNullable.java'/> на свойствах, которые могут иметь нулевые значения. Эта аннотация также имеет специальный параметр `path` . Он представляет собой путь по дереву типов данных переменной. Он позволяет указать, какой из "узлов" является нулевым.

Как видите, вы можете написать несколько аннотаций для различных путей одной и той же структуры данных.* <Githublink text='@SerializeFixedSize' url='/core-serializer/src/main/java/io/activej/serializer/annotations/SerializeFixedSize.java'/> на свойствах, которые должны иметь фиксированный размер после сериализации
Давайте создадим простой пример, иллюстрирующий использование этих аннотаций:
````mdx-code-block``java url=/examples/core/serializer/src/main/java/FixedSizeFieldsExample.java tag=REGION_1
Теперь давайте сериализуем и десериализуем экземпляр `Storage` аналогично предыдущим примерам. Мы создадим экземпляр `Storage`, байтовый массив для хранения результата сериализации и экземпляр `BinarySerializer<Storage>` сериализатора:
````mdx-code-block``java url=/examples/core/serializer/src/main/java/FixedSizeFieldsExample.java tag=REGION_2```''

Наконец, сериализуйте и десериализуйте экземпляр Storage :

``java url=/examples/core/serializer/src/main/java/FixedSizeFieldsExample.java tag=REGION_3

Давайте посмотрим, как сериализация повлияла на хранилище **:
````mdx-code-block``java url=/examples/core/serializer/src/main/java/FixedSizeFieldsExample.java tag=REGION_4
Если вы запустите пример, вы увидите следующий результат:
```bash[abc, null, 123, лишний] -> [abc, null, 123][1, 2, 3, 4, 5, 6] -> [1, 2, 3, 4]```
Как видно из первой строки, *storage* отличается от *limitedStorage*. Это происходит потому, что аннотация `@SerializeFixedSize` была установлена в значение **3** для свойства *strings* . Таким образом, "лишнее" было удалено из массива, пока происходила сериализация.
Вы можете изучить полные исходные тексты примеров на <Githublink text='GitHub' url='/examples/core/serializer/src/main/java/FixedSizeFieldsExample.java'/>
## Пользовательский сериализаторВ этом примере мы продемонстрируем, как можно написать пользовательский сериализатор для класса `LocalDate` . Вы можете использовать этот пример в качестве справочника для написания сериализаторов для других классов, которые вам могут понадобиться для сериализации.
Представим, что нам нужно сериализовать класс, содержащий поле `LocalDate` :
````mdx-code-block``java url=/examples/core/serializer/src/main/java/LocalDateSerializerExample.java tag=HOLDER
По умолчанию ActiveJ Serializer не знает, как сериализовать класс `LocalDate` , поэтому он выдаст исключение, если вы наивно попытаетесь его сериализовать. Мы должны предоставить пользовательский сериализатор для класса `LocalDate` , чтобы сериализовать класс `LocalDateHolder` :
````mdx-code-block``java url=/examples/core/serializer/src/main/java/LocalDateSerializerExample.java tag=SERIALIZER
Мы расширяем класс <Githublink text='AbstractSerializerDef' url='/core-serializer/src/main/java/io/activej/serializer/AbstractSerializerDef.java'/> и реализуем методы: - `Class<?> getEncodeType()` - указываем `LocalDate.class` в качестве типа сериализуемых данных - `Expression encoder(...)` - здесь мы инструктируем сериализатор, как сериализовать экземпляр `LocalDate` . На самом деле нам нужно сериализовать 3 `int` значения (`year`, `month`, and `dayOfMonth`) и записать их в <Githublink text='BinaryOutput' url='/core-serializer/src/main/java/io/activej/serializer/BinaryOutput.java'/> Используя Lisp-подобный `Expression` API, мы делаем именно это. - `Expression decoder(...)` - здесь нам нужно указать сериализатору, как десериализовать необработанные байты в экземпляр `LocalDate` . Этот процесс является обратным по отношению к кодированию. Сначала мы должны прочитать 3 `int` значения (`год`, `месяц`, и `dayOfMonth`) из <Githublink text='BinaryInput' url='/core-serializer/src/main/java/io/activej/serializer/BinaryInput.java'/> Затем мы можем создать новый экземпляр `LocalDate` , вызвав статический фабричный метод `static LocalDate of(int year, int month, int dayOfMonth)` и передав ранее десериализованные `int` значения. Для этой задачи мы снова используем API `Expression` .Наконец, нам нужно добавить наш сериализатор `LocalDate` к <Githublink text='SerializerBuilder' url='/core-serializer/src/main/java/io/activej/serializer/SerializerBuilder.java'/>
````mdx-code-block```java url=/examples/core/serializer/src/main/java/LocalDateSerializerExample.java tag=SERIALIZER_CREATE```

Если мы запустим метод LocalDateSerializerExample#main , то увидим следующий результат:

Serializing LocalDateHolder: LocalDateHolder{date=2021-03-17}Byte array with serialized LocalDateHolder: [-27, 15, 3, 17]Deserialized LocalDateHolder: LocalDateHolder{date=2021-03-17}

Вы можете изучить исходный код примеров на GitHub