Иконка ресурса

Bazon 2019-12-14

Нет прав для скачивания
Введение
Вот вы щас зашли в тему и думаете, что за слово такое "Bazon". Щас объясню.
Bazon это помесь Klaxon и GSON с отсылкой на байт-код. потому кончается на ON, начинается на B а в середине az(ну так сложилось).
Теперь к тому что это за зверь. Bazon решает задачу бойлерплейта в сериализации пакетов, но в отличии от многих других решений он не бьет по производительности, так как вся его задача заключается в том, чтобы сгенерировать байт-код, который сможет сериализовывать класс.
Штуку я эту делал довольно давно. В продакшене она уже побывала. Баги если и будут, то не критичные(либо я скажу, что это фича даяда).
Обзорчик
Теперь посмотрим как оно работает интереса ради.
Типичная такая ситуация: я пакет и у меня есть всякие страшные поля
Java:
public class ScaryPacket implements IMessage {

    private int a;
    private int b;
    private int c;
    private SomeDataCompound data;

    public ScaryPacket() {
    }

    public ScaryPacket(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
        a = buf.readInt();
        b = buf.readInt();
        c = buf.readInt();
        data = new SomeDataCompound(buf.readInt(), buf.readInt(), buf.readInt());
    }

    @Override
    public void toBytes(ByteBuf buf) {
        buf.writeInt(a);
        buf.writeInt(b);
        buf.writeInt(c);
        buf.writeInt(data.a);
        buf.writeInt(data.b);
        buf.writeInt(data.c);
    }


    public class SomeDataCompound {
        private final int c;
        public int b;
        private int a;

        public SomeDataCompound(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }
}
Получилось громоздко, противно и я наверняка мог ошибиться. Но тут к нам на помощь приходит Bazon! И эти 2 противных метода превращаются...
Java:
@Override
    public void fromBytes(ByteBuf buf) {
        PacketSerializationManager
                .INSTANCE
                .fetchPersistenceInator(getClass())
                .read(this, new DataInputStream(new ByteBufInputStream(buf)));
    }

    @Override
    public void toBytes(ByteBuf buf) {
        PacketSerializationManager
                .INSTANCE
                .fetchPersistenceInator(getClass())
                .write(this, new DataOutputStream(new ByteBufOutputStream(buf)));
    }
Мне кажется классно.
Риски
Теперь когда все воодушевились тем на сколько это удобно, пора перейти к более технической составляющей вопроса.

Bazon - штука, которой я изначально заменил существующий на тот момент метод сериализации пакетов на сталкрафте. Изначально предполагалось использовать его где то после компиляции, но до деплоя. Из этого проистекают несколько побочных эффектов:
1) базон совершенно не задумывается над тем что б быть быстрым на момент сборки компиляции класса. это мелочи, которые мало кто почувствует но если дахака заинтересуется, я предупредил.
2) базон написан на котлине. на сталкрафте котлин, котлин > жава, а скалу мне запретили потому вот так.
3) из коробки он никак не борется с приватными или файнал полями, а так же ему плевать на отсутствие дефолтного конструктора. если поле приватное или файнал, он будет юзать рефлексию. вопрос дефолтных конструкторов вы должны решать сами.

Теперь поговорим как это работает в версии, которая лежит в репо. Там на самом деле лежит костыль, но очень веселый. Изначально базон генерировал сериализацию пакетов непосредственно в самих пакетах, но программистов задолбало каждый раз запускать генерировалку байт-кода перед запуском клиента, да и хот своп не работал. Было принято решение как то двинуть это чудовище в рантайм. Ничего лучше не придумалось кроме как генерировать в рантайме класс, который будет сериализовывать другой класс.

Типичный такой сериализатор:
Java:
public class PersistentInator$HelloPacket$0 implements PersistentInator {
    public void read(Object var1, DataInput var2) {
        read((HelloPacket)var1, var2);
    }

    public void write(Object var1, DataOutput var2) {
        write((HelloPacket)var1, var2);
    }

    private static void read(HelloPacket var0, DataInput var1) {
        var0.msg = var1.readUTF();
        SomeDataCompound var5 = new SomeDataCompound();
        var5.read(var1);
        var0.a = var5;
        Field var10000 = HelloPacket.class.getDeclaredField("uuid");
        var10000.setAccessible(true);
        var10000.set((Object)var0, (Object)Serializers.readUUID(var1));
    }

    private static void write(HelloPacket var0, DataOutput var1) {
        String var5 = var0.msg;
        var1.writeUTF(var5);
        var0.a.write(var1);
        Field var10000 = HelloPacket.class.getDeclaredField("uuid");
        var10000.setAccessible(true);
        Serializers.writeUUID((UUID)var10000.get((Object)var0), var1);
    }

    public PersistentInator$HelloPacket$0() {
    }
}
Апи достаточно простое. Нужно просто дернуть метод ru.justagod.serialization.PacketSerializationManager#fetchPersistenceInator, и вы получите готовый к работе кусок базона.
Проблемы и их решения
Теперь когда вы примирились с рисками и +- поняли как оно работает, поговорим почему вы должны эту штуку тащить к себе, особенно если у вас в проекте уже есть котлин.
На самом деле я щас просто расскажу как правильно его использовать.

Изначальная задумка базона опиралась на UX гсона, а значит должна просто брать и сериализовывать всё, все вложенные объекты, листы, мапы и прочее, и изначально так оно и было. Но потом ко мне на голову свалился злой Александр и сказал вырубай нахрен. Теперь работает все чуть иначе. Вы все еще можете достать PersistenceInator для любого объекта, но все его поля должны быть либо примитивами, либо иметь своего саб базона.

Теперь о том кто такие саб базоны. Знаете в Gson есть TypeAdapter(вроде так называется) суть которого в том, чтобы дать вам самим написать сериализацию. Вот собственно у саб базонов ровно такое же назначение.
Примерчик:
Kotlin:
object ByteSubBazon : SubBazon {

    private val type = ClassTypeReference(Byte::class.java.name)

    override fun deserialize(model: TypeReference, appender: BytecodeAppender, context: DeserializationContext, parent: AbstractModel) {
        appender += NewInstanceInstruction(type)
        appender += DupInstruction(type)
        context.reader.readByte(appender)
        appender += InvokeInstanceMethodInstruction(type, "<init>", "(B)V", Opcodes.INVOKESPECIAL)
    }

    override fun serialize(model: TypeReference, appender: BytecodeAppender, context: SerializationContext, parent: AbstractModel) {
        val valueBuilder = appender.makeSetBuilder()
        context.workWithTop(type, appender) {
            update(valueBuilder)
            valueBuilder += InvokeInstanceMethodInstruction(type, "byteValue", "()B", Opcodes.INVOKEVIRTUAL)
        }
        context.writer.writeByte(appender, valueBuilder.build())

    }
}
(ета штука обертку байта сериализует)

Так вот, из-за требования Александа, нужно чуть усложнять все, чтобы сохранить все в изначальной задумке.

Допустим у нас есть такой класс:
Java:
public class PersistentUnit {

    public void read(DataInput input) {
        Class<?> clazz = this.getClass();
        while (PacketBase.class.isAssignableFrom(clazz)) {
            PacketSerializationManager.INSTANCE.fetchPersistenceInator(clazz).read(this, input);
            clazz = clazz.getSuperclass();
        }
    }


    public void write(DataOutput output) {
        Class<?> clazz = this.getClass();
        while (PacketBase.class.isAssignableFrom(clazz)) {
            PacketSerializationManager.INSTANCE.fetchPersistenceInator(clazz).write(this, output);
            clazz = clazz.getSuperclass();
        }
    }

}
(точнее он у нас точно есть и он вам точно нужен)
Смысл сей штуки прост. Все ваши классы, которые хотелось бы передавать в пакетах или в чем либо еще, нужно наследовать от данного класса. В репозитории я сделал саб базон, который при нахождении поля, тип которого наследует от PersistentUnit, делегирует сериализацию внутрь этого класса.
Не знаю вообщем на сколько я хорошо донес свою мысль, но правило простое: хочешь сериализовывать поле не примитивного типа - наследуй от PersistentUnit.

Далее еще одна проблема. Понятное дело что не все вы сможете унаследовать от PersistentUnit, а всунуть в пакет хотелось бы. Так же я понимаю, что извращенцев, которые полезут писать саб базоны, тоже не найти. Для этого я придумал делегированные саб базоны. Суть простая: они вызывают метод, которому дают объект и OutputStream и ожидают, что метод сделает за них всю работу.
В ru.justagod.serialization.PacketSerializationManager вы найдете много таких, и думаю по контексту будет понятно, что они делают и как сделать свои.

Далее еще одна проблема. ГеНеРиКи. Те еще сволочи. Не люблю генерики. Если будете использовать базон, старайтесь не злоупотреблять ими. Тем не менее некая поддержка генериков в базоне есть. Мне почему то очень захотелось добавить листы. Потому поля типа public List<SomeDataClass> list; вполне неплохо сериализуются.

Далее еще одна проблема. Нуллябельность. На самом деле на уровне байт кода точно определить нуллебельность поля фактически невозможно, а потому это остается на совести разработчиков. На этом моменте нам нужно вспомнить, что базон писался на сталкеркрафте, а значит имел фичи котлина, а потому я решил, что наиболее удобоваримым выходом из данной ситуации будет аннотация Nullable.
Таким образом, чтобы сказать базону, что поле может быть null, нужно повесить над полем аннотацию Nullable. Когда базон находит такую штуку он помимо самого поля пишет в аутпут еще булеан исходя из которого уже делает выводы на момент десериализации.

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

Если сей пролив кода станет популярным, и кого то внезапно не устроит скорость работы, мы с вами поговорим как сувать этого товарища в компайл тайм, а пока все.
До новых встреч. Ставьте лайки, подписывайтесь на канал и не забывайте про колокольчики.
Автор
JustAGod
Скачивания
7
Просмотры
847
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

Другие ресурсы пользователя JustAGod

Сверху